Skip to content

Commit 4e27dc2

Browse files
committed
Adds spp model get command closes #6105
1 parent da65889 commit 4e27dc2

File tree

7 files changed

+824
-1
lines changed

7 files changed

+824
-1
lines changed

docs/docs/cmd/spp/model/model-get.mdx

Lines changed: 308 additions & 0 deletions
Large diffs are not rendered by default.

docs/src/config/sidebars.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3952,6 +3952,15 @@ const sidebars: SidebarsConfig = {
39523952
id: 'cmd/spp/contentcenter/contentcenter-list'
39533953
}
39543954
]
3955+
},
3956+
{
3957+
model: [
3958+
{
3959+
type: 'doc',
3960+
label: 'model get',
3961+
id: 'cmd/spp/model/model-get'
3962+
}
3963+
]
39553964
}
39563965
]
39573966
},

src/m365/spp/commands.ts

Lines changed: 2 additions & 1 deletion
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_GET: `${prefix} model get`
56
};
Lines changed: 300 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,300 @@
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-get.js';
15+
16+
describe(commands.MODEL_GET, () => {
17+
let log: string[];
18+
let logger: Logger;
19+
let loggerLogSpy: sinon.SinonSpy;
20+
let commandInfo: CommandInfo;
21+
const model = {
22+
"AIBuilderHybridModelType": null,
23+
"AzureCognitivePrebuiltModelName": null,
24+
"BaseContentTypeName": null,
25+
"ConfidenceScore": "{\"trainingStatus\":{\"kind\":\"original\",\"ClassifierStatus\":{\"TrainingStatus\":\"success\",\"TimeStamp\":1716547860981},\"ExtractorsStatus\":[{\"TimeStamp\":1716547860173,\"ExtractorName\":\"Name\",\"TrainingStatus\":\"success\"}]},\"modelAccuracy\":{\"Classifier\":1,\"Extractors\":{\"Name\":0.333333343}},\"perSampleAccuracy\":{\"1\":{\"Classifier\":1,\"Extractors\":{\"Name\":1}},\"2\":{\"Classifier\":1,\"Extractors\":{\"Name\":0}},\"3\":{\"Classifier\":1,\"Extractors\":{\"Name\":0}},\"4\":{\"Classifier\":1,\"Extractors\":{\"Name\":0}},\"5\":{\"Classifier\":1,\"Extractors\":{\"Name\":0}},\"6\":{\"Classifier\":1,\"Extractors\":{\"Name\":1}}},\"perSamplePrediction\":{\"1\":{\"Extractors\":{\"Name\":[\"Michał\"]}},\"2\":{\"Extractors\":{\"Name\":[]}},\"3\":{\"Extractors\":{\"Name\":[]}},\"4\":{\"Extractors\":{\"Name\":[]}},\"5\":{\"Extractors\":{\"Name\":[]}},\"6\":{\"Extractors\":{\"Name\":[]}}},\"trainingFailures\":{}}",
26+
"ContentTypeGroup": "Intelligent Document Content Types",
27+
"ContentTypeId": "0x010100A5C3671D1FB1A64D9F280C628D041692",
28+
"ContentTypeName": "TeachingModel",
29+
"Created": "2024-05-23T16:51:29Z",
30+
"CreatedBy": "i:0#.f|membership|user@contoso.onmicrosoft.com",
31+
"DriveId": "b!qTVLltt1P02LUgejOy6O_1amoFeu1EJBlawH83UtYbQs_H3KVKAcQpuQOpNLl646",
32+
"Explanations": "{\"Classifier\":[{\"id\":\"950f39cd-5e72-4442-ae8a-ea4bae32962c\",\"kind\":\"regexFeature\",\"name\":\"Email address\",\"active\":true,\"pattern\":\"[A-Za-z0-9._%-]+@[A-Za-z0-9.-]+.[A-Za-z]{2,6}\"},{\"id\":\"d3f2940d-1df1-4ba8-975a-db3a4d626d5c\",\"kind\":\"dictionaryFeature\",\"name\":\"FirstName\",\"active\":true,\"nGrams\":[\"Michał\"],\"caseSensitive\":false,\"ignoreDigitIdentity\":false,\"ignoreLetterIdentity\":false},{\"id\":\"077966e1-73be-44c9-855f-a4eade6a280b\",\"kind\":\"modelFeature\",\"name\":\"Name\",\"active\":true,\"modelReference\":\"Name\",\"conceptId\":\"309b64e5-acd5-4538-a5b6-c6bfcdc1ffbf\"}],\"Extractors\":{\"Name\":[{\"id\":\"69e412bc-e5bc-4657-b378-34a01966bb92\",\"kind\":\"dictionaryFeature\",\"name\":\"Before label\",\"active\":true,\"nGrams\":[\"Test\",\"'Surname\",\"Test (\"],\"caseSensitive\":false,\"ignoreDigitIdentity\":false,\"ignoreLetterIdentity\":false}]}}",
33+
"ID": 1,
34+
"LastTrained": "2024-05-24T17:51:16Z",
35+
"ListID": "ca7dfc2c-a054-421c-9b90-3a934b97ae3a",
36+
"ModelSettings": "{\"ModelOrigin\":{\"LibraryId\":\"8a6027ab-c584-4394-ba9c-3dc4dd152b65\",\"Published\":false,\"SelecteFileUniqueIds\":[]}}",
37+
"ModelType": 2,
38+
"Modified": "2024-05-24T17:51:02Z",
39+
"ModifiedBy": "i:0#.f|membership|user@contoso.onmicrosoft.com",
40+
"ObjectId": "01HQDCWVGIEBDRN3RVK5A3UJW3M4TCMT45",
41+
"PublicationType": 0,
42+
"Schemas": "{\"Version\":2,\"Extractors\":{\"Name\":{\"concepts\":{\"309b64e5-acd5-4538-a5b6-c6bfcdc1ffbf\":{\"name\":\"Name\"}},\"relationships\":[],\"id\":\"Name\"}}}",
43+
"SourceSiteUrl": "https://contoso.sharepoint.com/sites/SyntexTest",
44+
"SourceUrl": null,
45+
"SourceWebServerRelativeUrl": "/sites/SyntexTest",
46+
"UniqueId": "164720c8-35ee-4157-ba26-db6726264f9d"
47+
};
48+
49+
const publications = [
50+
{
51+
"Created": "2020-11-03T12:52:12Z",
52+
"CreatedBy": "i:0#.f|membership|user@contoso.onmicrosoft.com",
53+
"DriveId": "b!7w273MPU8kiqSKc6SWaUkQ3wyRgeHbxDh-e6ShP9X-e-6B-bO36LRI87VfSrxwy9",
54+
"HasTargetSitePermission": true,
55+
"ID": 1,
56+
"ModelId": 3,
57+
"ModelName": "BenefitsChangeNotice.classifier",
58+
"ModelType": 0,
59+
"ModelUniqueId": "b10e0de5-c069-46f9-90f7-4fb8ac001372",
60+
"ModelVersion": "2.0",
61+
"Modified": "2020-11-03T18:45:18Z",
62+
"ModifiedBy": "i:0#.f|membership|user@contoso.onmicrosoft.com",
63+
"ObjectId": "01XHOXSCVXVZCQAAQWCNGK6NGADKKZUMW7",
64+
"PublicationType": 1,
65+
"TargetLibraryId": "7eb6c306-1680-4ba9-9bbe-6b5b7efb27be",
66+
"TargetLibraryName": "Documents",
67+
"TargetLibraryRemoved": false,
68+
"TargetLibraryServerRelativeUrl": "/sites/SyntexTest/Shared%20Documents",
69+
"TargetLibraryUrl": "https://contoso.sharepoint.com/sites/SyntexTest/Shared%20Documents",
70+
"TargetSiteId": "7e44de0a-30e3-45ab-a601-057298c9068f",
71+
"TargetSiteUrl": "https://contoso.sharepoint.com/sites/SyntexTest",
72+
"TargetTableListId": "00000000-0000-0000-0000-000000000000",
73+
"TargetTableListName": null,
74+
"TargetTableListRemoved": false,
75+
"TargetTableListServerRelativeUrl": null,
76+
"TargetTableListUrl": null,
77+
"TargetWebId": "9166df43-8e45-43b8-94ef-3a22826346de",
78+
"TargetWebName": "SyntexTest",
79+
"TargetWebServerRelativeUrl": "/sites/SyntexTest",
80+
"UniqueId": "0045aeb7-1602-4c13-af34-c01a959a32df",
81+
"ViewOption": "NewViewAsDefault"
82+
}
83+
];
84+
85+
before(() => {
86+
sinon.stub(auth, 'restoreAuth').resolves();
87+
sinon.stub(telemetry, 'trackEvent').returns();
88+
sinon.stub(pid, 'getProcessName').returns('');
89+
sinon.stub(session, 'getId').returns('');
90+
auth.connection.active = true;
91+
commandInfo = cli.getCommandInfo(command);
92+
});
93+
94+
beforeEach(() => {
95+
log = [];
96+
logger = {
97+
log: async (msg: string) => {
98+
log.push(msg);
99+
},
100+
logRaw: async (msg: string) => {
101+
log.push(msg);
102+
},
103+
logToStderr: async (msg: string) => {
104+
log.push(msg);
105+
}
106+
};
107+
loggerLogSpy = sinon.spy(logger, 'log');
108+
});
109+
110+
afterEach(() => {
111+
sinonUtil.restore([
112+
request.get
113+
]);
114+
});
115+
116+
after(() => {
117+
sinon.restore();
118+
auth.connection.active = false;
119+
});
120+
121+
it('has correct name', () => {
122+
assert.strictEqual(command.name, commands.MODEL_GET);
123+
});
124+
125+
it('has a description', () => {
126+
assert.notStrictEqual(command.description, null);
127+
});
128+
129+
it('passes validation when required parameters are valid with id', async () => {
130+
const actual = await command.validate({ options: { siteUrl: 'https://contoso.sharepoint.com/sites/sales', id: '9b1b1e42-794b-4c71-93ac-5ed92488b67f' } }, commandInfo);
131+
assert.strictEqual(actual, true);
132+
});
133+
134+
it('passes validation when required parameters are valid with title', async () => {
135+
const actual = await command.validate({ options: { siteUrl: 'https://contoso.sharepoint.com/sites/sales', title: 'ModelName' } }, commandInfo);
136+
assert.strictEqual(actual, true);
137+
});
138+
139+
it('passes validation when required parameters are valid with id and listPublications', async () => {
140+
const actual = await command.validate({ options: { siteUrl: 'https://contoso.sharepoint.com/sites/sales', id: '9b1b1e42-794b-4c71-93ac-5ed92488b67f', listPublications: true } }, commandInfo);
141+
assert.strictEqual(actual, true);
142+
});
143+
144+
it('passes validation when required parameters are valid with title and listPublications', async () => {
145+
const actual = await command.validate({ options: { siteUrl: 'https://contoso.sharepoint.com/sites/sales', title: 'ModelName', listPublications: true } }, commandInfo);
146+
assert.strictEqual(actual, true);
147+
});
148+
149+
it('fails validation when siteUrl is not valid', async () => {
150+
const actual = await command.validate({ options: { siteUrl: 'invalidUrl', id: '9b1b1e42-794b-4c71-93ac-5ed92488b67f' } }, commandInfo);
151+
assert.notStrictEqual(actual, true);
152+
});
153+
154+
it('fails validation when id is not valid', async () => {
155+
const actual = await command.validate({ options: { siteUrl: 'https://contoso.sharepoint.com/sites/sales', id: 'foo' } }, commandInfo);
156+
assert.notStrictEqual(actual, true);
157+
});
158+
159+
it('correctly handles site is not Content Site', async () => {
160+
sinon.stub(request, 'get').callsFake(async (opts) => {
161+
if (opts.url === `https://contoso.sharepoint.com/sites/portal/_api/web?$select=WebTemplateConfiguration`) {
162+
return {
163+
WebTemplateConfiguration: 'SITEPAGEPUBLISHING#0'
164+
};
165+
}
166+
167+
throw 'Invalid request';
168+
});
169+
170+
await assert.rejects(command.action(logger, { options: { verbose: true, siteUrl: 'https://contoso.sharepoint.com/sites/portal', id: '9b1b1e42-794b-4c71-93ac-5ed92488b67f' } }),
171+
new CommandError('https://contoso.sharepoint.com/sites/portal is not a content site.'));
172+
});
173+
174+
175+
it('correctly handles an access denied error', async () => {
176+
sinon.stub(request, 'get').callsFake(async (opts) => {
177+
if (opts.url === `https://contoso.sharepoint.com/sites/portal/_api/web?$select=WebTemplateConfiguration`) {
178+
throw {
179+
error: {
180+
"odata.error": {
181+
message: {
182+
lang: "en-US",
183+
value: "Attempted to perform an unauthorized operation."
184+
}
185+
}
186+
}
187+
};
188+
}
189+
190+
throw 'Invalid request';
191+
});
192+
193+
await assert.rejects(command.action(logger, { options: { verbose: true, siteUrl: 'https://contoso.sharepoint.com/sites/portal', id: '9b1b1e42-794b-4c71-93ac-5ed92488b67f' } }),
194+
new CommandError('Attempted to perform an unauthorized operation.'));
195+
});
196+
197+
198+
it('retrieves model by id', async () => {
199+
sinon.stub(request, 'get').callsFake(async (opts) => {
200+
if (opts.url === `https://contoso.sharepoint.com/sites/portal/_api/web?$select=WebTemplateConfiguration`) {
201+
return {
202+
WebTemplateConfiguration: 'CONTENTCTR#0'
203+
};
204+
}
205+
206+
if (opts.url === `https://contoso.sharepoint.com/sites/portal/_api/machinelearning/models/getbyuniqueid('9b1b1e42-794b-4c71-93ac-5ed92488b67f')`) {
207+
return model;
208+
}
209+
210+
throw 'Invalid request';
211+
});
212+
213+
await command.action(logger, { options: { siteUrl: 'https://contoso.sharepoint.com/sites/portal', id: '9b1b1e42-794b-4c71-93ac-5ed92488b67f' } });
214+
assert(loggerLogSpy.calledOnceWithExactly(model));
215+
});
216+
217+
it('retrieves model by id with listPublications', async () => {
218+
sinon.stub(request, 'get').callsFake(async (opts) => {
219+
if (opts.url === `https://contoso.sharepoint.com/sites/portal/_api/web?$select=WebTemplateConfiguration`) {
220+
return {
221+
WebTemplateConfiguration: 'CONTENTCTR#0'
222+
};
223+
}
224+
225+
if (opts.url === `https://contoso.sharepoint.com/sites/portal/_api/machinelearning/models/getbyuniqueid('164720c8-35ee-4157-ba26-db6726264f9d')`) {
226+
return model;
227+
}
228+
229+
if (opts.url === `https://contoso.sharepoint.com/sites/portal/_api/machinelearning/publications/getbymodeluniqueid('164720c8-35ee-4157-ba26-db6726264f9d')`) {
230+
return { value: publications };
231+
}
232+
233+
throw 'Invalid request';
234+
});
235+
236+
await command.action(logger, { options: { siteUrl: 'https://contoso.sharepoint.com/sites/portal', id: '164720c8-35ee-4157-ba26-db6726264f9d', listPublications: true } });
237+
assert(loggerLogSpy.calledOnceWithExactly({ ...model, Publications: publications }));
238+
});
239+
240+
it('gets correct model when the site URL has a trailing slash', async () => {
241+
sinon.stub(request, 'get').callsFake(async (opts) => {
242+
if (opts.url === `https://contoso.sharepoint.com/sites/portal/_api/web?$select=WebTemplateConfiguration`) {
243+
return {
244+
WebTemplateConfiguration: 'CONTENTCTR#0'
245+
};
246+
}
247+
248+
if (opts.url === `https://contoso.sharepoint.com/sites/portal/_api/machinelearning/models/getbyuniqueid('9b1b1e42-794b-4c71-93ac-5ed92488b67f')`) {
249+
return model;
250+
}
251+
252+
throw 'Invalid request';
253+
});
254+
255+
await command.action(logger, { options: { siteUrl: 'https://contoso.sharepoint.com/sites/portal/', id: '9b1b1e42-794b-4c71-93ac-5ed92488b67f' } });
256+
assert(loggerLogSpy.calledOnceWithExactly(model));
257+
});
258+
259+
it('retrieves model by title', async () => {
260+
sinon.stub(request, 'get').callsFake(async (opts) => {
261+
if (opts.url === `https://contoso.sharepoint.com/sites/portal/_api/web?$select=WebTemplateConfiguration`) {
262+
return {
263+
WebTemplateConfiguration: 'CONTENTCTR#0'
264+
};
265+
}
266+
267+
if (opts.url === `https://contoso.sharepoint.com/sites/portal/_api/machinelearning/models/getbytitle('ModelName')`) {
268+
return model;
269+
}
270+
271+
throw 'Invalid request';
272+
});
273+
274+
await command.action(logger, { options: { siteUrl: 'https://contoso.sharepoint.com/sites/portal', title: 'ModelName' } });
275+
assert(loggerLogSpy.calledOnceWithExactly(model));
276+
});
277+
278+
it('retrieves model by title with listPublications', async () => {
279+
sinon.stub(request, 'get').callsFake(async (opts) => {
280+
if (opts.url === `https://contoso.sharepoint.com/sites/portal/_api/web?$select=WebTemplateConfiguration`) {
281+
return {
282+
WebTemplateConfiguration: 'CONTENTCTR#0'
283+
};
284+
}
285+
286+
if (opts.url === `https://contoso.sharepoint.com/sites/portal/_api/machinelearning/models/getbytitle('ModelName')`) {
287+
return model;
288+
}
289+
290+
if (opts.url === `https://contoso.sharepoint.com/sites/portal/_api/machinelearning/publications/getbymodeluniqueid('164720c8-35ee-4157-ba26-db6726264f9d')`) {
291+
return { value: publications };
292+
}
293+
294+
throw 'Invalid request';
295+
});
296+
297+
await command.action(logger, { options: { siteUrl: 'https://contoso.sharepoint.com/sites/portal', title: 'ModelName', listPublications: true } });
298+
assert(loggerLogSpy.calledOnceWithExactly({ ...model, Publications: publications }));
299+
});
300+
});

0 commit comments

Comments
 (0)