Skip to content

Commit e6ba333

Browse files
committed
Adds spp model apply command. Closes pnp#6119
1 parent da65889 commit e6ba333

File tree

7 files changed

+517
-1
lines changed

7 files changed

+517
-1
lines changed
+53
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
import Global from '/docs/cmd/_global.mdx';
2+
import Tabs from '@theme/Tabs';
3+
import TabItem from '@theme/TabItem';
4+
5+
# spp model remove
6+
7+
Deletes a document understanding model
8+
9+
## Usage
10+
11+
```sh
12+
m365 spp model remove [options]
13+
```
14+
15+
## Options
16+
17+
```md definition-list
18+
`-u, --siteUrl <siteUrl>`
19+
: The URL of the content center site.
20+
21+
`-i, --id [id]`
22+
: The unique ID of the model to delete. Specify either `id` or `title` but not both.
23+
24+
`-t, --title [title]`
25+
: The display name (case-sensitive) of the model to remove. Specify either `id` or `title` but not both.
26+
27+
`-f --force`
28+
: Don't prompt for confirming removing the model.
29+
```
30+
31+
<Global />
32+
33+
## Remarks
34+
35+
Note that this model will be removed from all libraries before it can be deleted.
36+
37+
## Examples
38+
39+
Delete a SharePoint Premium document understanding model using the model’s UniqueId.
40+
41+
```sh
42+
m365 spp model remove --siteUrl "https://contoso.sharepoint.com/sites/ContentCenter" --id "7645e69d-21fb-4a24-a17a-9bdfa7cb63dc"
43+
```
44+
45+
Delete a SharePoint Premium document understanding model using the model’s title.
46+
47+
```sh
48+
m365 spp model remove --siteUrl "https://contoso.sharepoint.com/sites/ContentCenter" --title "climicrosoft365Model.classifier"
49+
```
50+
51+
## Response
52+
53+
The command won't return a response on success.

docs/src/config/sidebars.ts

+8
Original file line numberDiff line numberDiff line change
@@ -3951,6 +3951,14 @@ const sidebars: SidebarsConfig = {
39513951
label: 'contentcenter list',
39523952
id: 'cmd/spp/contentcenter/contentcenter-list'
39533953
}
3954+
],
3955+
{
3956+
model: [
3957+
{
3958+
type: 'doc',
3959+
label: 'model apply',
3960+
id: 'cmd/spp/model/model-apply'
3961+
}
39543962
]
39553963
}
39563964
]

src/m365/spp/commands.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
const prefix: string = 'spp';
22

33
export default {
4-
CONTENTCENTER_LIST: `${prefix} contentcenter list`
4+
CONTENTCENTER_LIST: `${prefix} contentcenter list`,
5+
MODEL_APPLY: `${prefix} model apply`
56
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,248 @@
1+
import assert from 'assert';
2+
import sinon from 'sinon';
3+
import auth from '../../../../Auth.js';
4+
import { cli } from '../../../../cli/cli.js';
5+
import { CommandInfo } from '../../../../cli/CommandInfo.js';
6+
import { Logger } from '../../../../cli/Logger.js';
7+
import { CommandError } from '../../../../Command.js';
8+
import request from '../../../../request.js';
9+
import { telemetry } from '../../../../telemetry.js';
10+
import { pid } from '../../../../utils/pid.js';
11+
import { session } from '../../../../utils/session.js';
12+
import { sinonUtil } from '../../../../utils/sinonUtil.js';
13+
import commands from '../../commands.js';
14+
import command from './model-remove.js';
15+
16+
describe(commands.MODEL_REMOVE, () => {
17+
let log: string[];
18+
let logger: Logger;
19+
let commandInfo: CommandInfo;
20+
21+
before(() => {
22+
sinon.stub(auth, 'restoreAuth').resolves();
23+
sinon.stub(telemetry, 'trackEvent').returns();
24+
sinon.stub(pid, 'getProcessName').returns('');
25+
sinon.stub(session, 'getId').returns('');
26+
auth.connection.active = true;
27+
commandInfo = cli.getCommandInfo(command);
28+
});
29+
30+
beforeEach(() => {
31+
log = [];
32+
logger = {
33+
log: async (msg: string) => {
34+
log.push(msg);
35+
},
36+
logRaw: async (msg: string) => {
37+
log.push(msg);
38+
},
39+
logToStderr: async (msg: string) => {
40+
log.push(msg);
41+
}
42+
};
43+
});
44+
45+
afterEach(() => {
46+
sinonUtil.restore([
47+
request.get,
48+
request.delete,
49+
cli.promptForConfirmation
50+
]);
51+
});
52+
53+
after(() => {
54+
sinon.restore();
55+
auth.connection.active = false;
56+
});
57+
58+
it('has correct name', () => {
59+
assert.strictEqual(command.name, commands.MODEL_REMOVE);
60+
});
61+
62+
it('has a description', () => {
63+
assert.notStrictEqual(command.description, null);
64+
});
65+
66+
it('passes validation when required parameters are valid with id', async () => {
67+
const actual = await command.validate({ options: { siteUrl: 'https://contoso.sharepoint.com/sites/sales', id: '9b1b1e42-794b-4c71-93ac-5ed92488b67f' } }, commandInfo);
68+
assert.strictEqual(actual, true);
69+
});
70+
71+
it('passes validation when required parameters are valid with title', async () => {
72+
const actual = await command.validate({ options: { siteUrl: 'https://contoso.sharepoint.com/sites/sales', title: 'ModelName' } }, commandInfo);
73+
assert.strictEqual(actual, true);
74+
});
75+
76+
it('passes validation when required parameters are valid with id and force', async () => {
77+
const actual = await command.validate({ options: { siteUrl: 'https://contoso.sharepoint.com/sites/sales', id: '9b1b1e42-794b-4c71-93ac-5ed92488b67f', force: true } }, commandInfo);
78+
assert.strictEqual(actual, true);
79+
});
80+
81+
it('fails validation when siteUrl is not valid', async () => {
82+
const actual = await command.validate({ options: { siteUrl: 'invalidUrl', id: '9b1b1e42-794b-4c71-93ac-5ed92488b67f' } }, commandInfo);
83+
assert.notStrictEqual(actual, true);
84+
});
85+
86+
it('fails validation when id is not valid', async () => {
87+
const actual = await command.validate({ options: { siteUrl: 'https://contoso.sharepoint.com/sites/sales', id: 'foo' } }, commandInfo);
88+
assert.notStrictEqual(actual, true);
89+
});
90+
91+
it('correctly handles site is not Content Site', async () => {
92+
sinon.stub(request, 'get').callsFake(async (opts) => {
93+
if (opts.url === `https://contoso.sharepoint.com/sites/portal/_api/web?$select=WebTemplateConfiguration`) {
94+
return {
95+
WebTemplateConfiguration: 'SITEPAGEPUBLISHING#0'
96+
};
97+
}
98+
99+
throw 'Invalid request';
100+
});
101+
102+
await assert.rejects(command.action(logger, { options: { verbose: true, siteUrl: 'https://contoso.sharepoint.com/sites/portal', id: '9b1b1e42-794b-4c71-93ac-5ed92488b67f', force: true } }),
103+
new CommandError('https://contoso.sharepoint.com/sites/portal is not a content site.'));
104+
});
105+
106+
107+
it('correctly handles an access denied error', async () => {
108+
sinon.stub(request, 'get').callsFake(async (opts) => {
109+
if (opts.url === `https://contoso.sharepoint.com/sites/portal/_api/web?$select=WebTemplateConfiguration`) {
110+
throw {
111+
error: {
112+
"odata.error": {
113+
message: {
114+
lang: "en-US",
115+
value: "Attempted to perform an unauthorized operation."
116+
}
117+
}
118+
}
119+
};
120+
}
121+
122+
throw 'Invalid request';
123+
});
124+
125+
await assert.rejects(command.action(logger, { options: { verbose: true, siteUrl: 'https://contoso.sharepoint.com/sites/portal', id: '9b1b1e42-794b-4c71-93ac-5ed92488b67f', force: true } }),
126+
new CommandError('Attempted to perform an unauthorized operation.'));
127+
});
128+
129+
130+
it('deletes model by id', async () => {
131+
const confirmationStub = sinon.stub(cli, 'promptForConfirmation').resolves(true);
132+
sinon.stub(request, 'get').callsFake(async (opts) => {
133+
if (opts.url === `https://contoso.sharepoint.com/sites/portal/_api/web?$select=WebTemplateConfiguration`) {
134+
return {
135+
WebTemplateConfiguration: 'CONTENTCTR#0'
136+
};
137+
}
138+
throw 'Invalid request';
139+
});
140+
141+
const stubDelete = sinon.stub(request, 'delete').callsFake(async (opts) => {
142+
if (opts.url === `https://contoso.sharepoint.com/sites/portal/_api/machinelearning/models/getbyuniqueid('9b1b1e42-794b-4c71-93ac-5ed92488b67f')`) {
143+
return;
144+
}
145+
146+
throw 'Invalid request';
147+
});
148+
149+
await command.action(logger, { options: { siteUrl: 'https://contoso.sharepoint.com/sites/portal', id: '9b1b1e42-794b-4c71-93ac-5ed92488b67f' } });
150+
assert.strictEqual(stubDelete.lastCall.args[0].url, `https://contoso.sharepoint.com/sites/portal/_api/machinelearning/models/getbyuniqueid('9b1b1e42-794b-4c71-93ac-5ed92488b67f')`);
151+
assert(confirmationStub.calledOnce);
152+
});
153+
154+
it('does not delete model when confirmation is not accepted', async () => {
155+
const confirmationStub = sinon.stub(cli, 'promptForConfirmation').resolves(false);
156+
sinon.stub(request, 'get').callsFake(async (opts) => {
157+
if (opts.url === `https://contoso.sharepoint.com/sites/portal/_api/web?$select=WebTemplateConfiguration`) {
158+
return {
159+
WebTemplateConfiguration: 'CONTENTCTR#0'
160+
};
161+
}
162+
throw 'Invalid request';
163+
});
164+
165+
const stubDelete = sinon.stub(request, 'delete').callsFake(async (opts) => {
166+
if (opts.url === `https://contoso.sharepoint.com/sites/portal/_api/machinelearning/models/getbyuniqueid('9b1b1e42-794b-4c71-93ac-5ed92488b67f')`) {
167+
return;
168+
}
169+
170+
throw 'Invalid request';
171+
});
172+
173+
await command.action(logger, { options: { siteUrl: 'https://contoso.sharepoint.com/sites/portal', id: '9b1b1e42-794b-4c71-93ac-5ed92488b67f' } });
174+
assert(stubDelete.notCalled);
175+
assert(confirmationStub.calledOnce);
176+
});
177+
178+
it('deletes model by id with force', async () => {
179+
sinon.stub(request, 'get').callsFake(async (opts) => {
180+
if (opts.url === `https://contoso.sharepoint.com/sites/portal/_api/web?$select=WebTemplateConfiguration`) {
181+
return {
182+
WebTemplateConfiguration: 'CONTENTCTR#0'
183+
};
184+
}
185+
throw 'Invalid request';
186+
});
187+
188+
const stubDelete = sinon.stub(request, 'delete').callsFake(async (opts) => {
189+
if (opts.url === `https://contoso.sharepoint.com/sites/portal/_api/machinelearning/models/getbyuniqueid('164720c8-35ee-4157-ba26-db6726264f9d')`) {
190+
return;
191+
}
192+
193+
throw 'Invalid request';
194+
});
195+
196+
await command.action(logger, { options: { siteUrl: 'https://contoso.sharepoint.com/sites/portal', id: '164720c8-35ee-4157-ba26-db6726264f9d', force: true } });
197+
assert.strictEqual(stubDelete.lastCall.args[0].url, `https://contoso.sharepoint.com/sites/portal/_api/machinelearning/models/getbyuniqueid('164720c8-35ee-4157-ba26-db6726264f9d')`);
198+
});
199+
200+
it('deletes model when the the site URL has trailing slash', async () => {
201+
const confirmationStub = sinon.stub(cli, 'promptForConfirmation').resolves(true);
202+
sinon.stub(request, 'get').callsFake(async (opts) => {
203+
if (opts.url === `https://contoso.sharepoint.com/sites/portal/_api/web?$select=WebTemplateConfiguration`) {
204+
return {
205+
WebTemplateConfiguration: 'CONTENTCTR#0'
206+
};
207+
}
208+
throw 'Invalid request';
209+
});
210+
211+
const stubDelete = sinon.stub(request, 'delete').callsFake(async (opts) => {
212+
if (opts.url === `https://contoso.sharepoint.com/sites/portal/_api/machinelearning/models/getbyuniqueid('9b1b1e42-794b-4c71-93ac-5ed92488b67f')`) {
213+
return;
214+
}
215+
216+
throw 'Invalid request';
217+
});
218+
219+
await command.action(logger, { options: { siteUrl: 'https://contoso.sharepoint.com/sites/portal/', id: '9b1b1e42-794b-4c71-93ac-5ed92488b67f' } });
220+
assert.strictEqual(stubDelete.lastCall.args[0].url, `https://contoso.sharepoint.com/sites/portal/_api/machinelearning/models/getbyuniqueid('9b1b1e42-794b-4c71-93ac-5ed92488b67f')`);
221+
assert(confirmationStub.calledOnce);
222+
});
223+
224+
it('deletes model by title', async () => {
225+
const confirmationStub = sinon.stub(cli, 'promptForConfirmation').resolves(true);
226+
sinon.stub(request, 'get').callsFake(async (opts) => {
227+
if (opts.url === `https://contoso.sharepoint.com/sites/portal/_api/web?$select=WebTemplateConfiguration`) {
228+
return {
229+
WebTemplateConfiguration: 'CONTENTCTR#0'
230+
};
231+
}
232+
233+
throw 'Invalid request';
234+
});
235+
236+
const stubDelete = sinon.stub(request, 'delete').callsFake(async (opts) => {
237+
if (opts.url === `https://contoso.sharepoint.com/sites/portal/_api/machinelearning/models/getbytitle('ModelName')`) {
238+
return;
239+
}
240+
241+
throw 'Invalid request';
242+
});
243+
244+
await command.action(logger, { options: { siteUrl: 'https://contoso.sharepoint.com/sites/portal', title: 'ModelName' } });
245+
assert.strictEqual(stubDelete.lastCall.args[0].url, `https://contoso.sharepoint.com/sites/portal/_api/machinelearning/models/getbytitle('ModelName')`);
246+
assert(confirmationStub.calledOnce);
247+
});
248+
});

0 commit comments

Comments
 (0)