Skip to content

Commit

Permalink
Adds spp model get command closes #6105
Browse files Browse the repository at this point in the history
  • Loading branch information
mkm17 committed Oct 6, 2024
1 parent da65889 commit 4e27dc2
Show file tree
Hide file tree
Showing 7 changed files with 824 additions and 1 deletion.
308 changes: 308 additions & 0 deletions docs/docs/cmd/spp/model/model-get.mdx

Large diffs are not rendered by default.

9 changes: 9 additions & 0 deletions docs/src/config/sidebars.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3952,6 +3952,15 @@ const sidebars: SidebarsConfig = {
id: 'cmd/spp/contentcenter/contentcenter-list'
}
]
},
{
model: [
{
type: 'doc',
label: 'model get',
id: 'cmd/spp/model/model-get'
}
]
}
]
},
Expand Down
3 changes: 2 additions & 1 deletion src/m365/spp/commands.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
const prefix: string = 'spp';

export default {
CONTENTCENTER_LIST: `${prefix} contentcenter list`
CONTENTCENTER_LIST: `${prefix} contentcenter list`,
MODEL_GET: `${prefix} model get`
};
300 changes: 300 additions & 0 deletions src/m365/spp/commands/model/model-get.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,300 @@
import assert from 'assert';
import sinon from 'sinon';
import auth from '../../../../Auth.js';
import { cli } from '../../../../cli/cli.js';
import { CommandInfo } from '../../../../cli/CommandInfo.js';
import { Logger } from '../../../../cli/Logger.js';
import { CommandError } from '../../../../Command.js';
import request from '../../../../request.js';
import { telemetry } from '../../../../telemetry.js';
import { pid } from '../../../../utils/pid.js';
import { session } from '../../../../utils/session.js';
import { sinonUtil } from '../../../../utils/sinonUtil.js';
import commands from '../../commands.js';
import command from './model-get.js';

describe(commands.MODEL_GET, () => {
let log: string[];
let logger: Logger;
let loggerLogSpy: sinon.SinonSpy;
let commandInfo: CommandInfo;
const model = {
"AIBuilderHybridModelType": null,
"AzureCognitivePrebuiltModelName": null,
"BaseContentTypeName": null,
"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\":{}}",
"ContentTypeGroup": "Intelligent Document Content Types",
"ContentTypeId": "0x010100A5C3671D1FB1A64D9F280C628D041692",
"ContentTypeName": "TeachingModel",
"Created": "2024-05-23T16:51:29Z",
"CreatedBy": "i:0#.f|membership|[email protected]",
"DriveId": "b!qTVLltt1P02LUgejOy6O_1amoFeu1EJBlawH83UtYbQs_H3KVKAcQpuQOpNLl646",
"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}]}}",
"ID": 1,
"LastTrained": "2024-05-24T17:51:16Z",
"ListID": "ca7dfc2c-a054-421c-9b90-3a934b97ae3a",
"ModelSettings": "{\"ModelOrigin\":{\"LibraryId\":\"8a6027ab-c584-4394-ba9c-3dc4dd152b65\",\"Published\":false,\"SelecteFileUniqueIds\":[]}}",
"ModelType": 2,
"Modified": "2024-05-24T17:51:02Z",
"ModifiedBy": "i:0#.f|membership|[email protected]",
"ObjectId": "01HQDCWVGIEBDRN3RVK5A3UJW3M4TCMT45",
"PublicationType": 0,
"Schemas": "{\"Version\":2,\"Extractors\":{\"Name\":{\"concepts\":{\"309b64e5-acd5-4538-a5b6-c6bfcdc1ffbf\":{\"name\":\"Name\"}},\"relationships\":[],\"id\":\"Name\"}}}",
"SourceSiteUrl": "https://contoso.sharepoint.com/sites/SyntexTest",
"SourceUrl": null,
"SourceWebServerRelativeUrl": "/sites/SyntexTest",
"UniqueId": "164720c8-35ee-4157-ba26-db6726264f9d"
};

const publications = [
{
"Created": "2020-11-03T12:52:12Z",
"CreatedBy": "i:0#.f|membership|[email protected]",
"DriveId": "b!7w273MPU8kiqSKc6SWaUkQ3wyRgeHbxDh-e6ShP9X-e-6B-bO36LRI87VfSrxwy9",
"HasTargetSitePermission": true,
"ID": 1,
"ModelId": 3,
"ModelName": "BenefitsChangeNotice.classifier",
"ModelType": 0,
"ModelUniqueId": "b10e0de5-c069-46f9-90f7-4fb8ac001372",
"ModelVersion": "2.0",
"Modified": "2020-11-03T18:45:18Z",
"ModifiedBy": "i:0#.f|membership|[email protected]",
"ObjectId": "01XHOXSCVXVZCQAAQWCNGK6NGADKKZUMW7",
"PublicationType": 1,
"TargetLibraryId": "7eb6c306-1680-4ba9-9bbe-6b5b7efb27be",
"TargetLibraryName": "Documents",
"TargetLibraryRemoved": false,
"TargetLibraryServerRelativeUrl": "/sites/SyntexTest/Shared%20Documents",
"TargetLibraryUrl": "https://contoso.sharepoint.com/sites/SyntexTest/Shared%20Documents",
"TargetSiteId": "7e44de0a-30e3-45ab-a601-057298c9068f",
"TargetSiteUrl": "https://contoso.sharepoint.com/sites/SyntexTest",
"TargetTableListId": "00000000-0000-0000-0000-000000000000",
"TargetTableListName": null,
"TargetTableListRemoved": false,
"TargetTableListServerRelativeUrl": null,
"TargetTableListUrl": null,
"TargetWebId": "9166df43-8e45-43b8-94ef-3a22826346de",
"TargetWebName": "SyntexTest",
"TargetWebServerRelativeUrl": "/sites/SyntexTest",
"UniqueId": "0045aeb7-1602-4c13-af34-c01a959a32df",
"ViewOption": "NewViewAsDefault"
}
];

before(() => {
sinon.stub(auth, 'restoreAuth').resolves();
sinon.stub(telemetry, 'trackEvent').returns();
sinon.stub(pid, 'getProcessName').returns('');
sinon.stub(session, 'getId').returns('');
auth.connection.active = true;
commandInfo = cli.getCommandInfo(command);
});

beforeEach(() => {
log = [];
logger = {
log: async (msg: string) => {
log.push(msg);
},
logRaw: async (msg: string) => {
log.push(msg);
},
logToStderr: async (msg: string) => {
log.push(msg);
}
};
loggerLogSpy = sinon.spy(logger, 'log');
});

afterEach(() => {
sinonUtil.restore([
request.get
]);
});

after(() => {
sinon.restore();
auth.connection.active = false;
});

it('has correct name', () => {
assert.strictEqual(command.name, commands.MODEL_GET);
});

it('has a description', () => {
assert.notStrictEqual(command.description, null);
});

it('passes validation when required parameters are valid with id', async () => {
const actual = await command.validate({ options: { siteUrl: 'https://contoso.sharepoint.com/sites/sales', id: '9b1b1e42-794b-4c71-93ac-5ed92488b67f' } }, commandInfo);
assert.strictEqual(actual, true);
});

it('passes validation when required parameters are valid with title', async () => {
const actual = await command.validate({ options: { siteUrl: 'https://contoso.sharepoint.com/sites/sales', title: 'ModelName' } }, commandInfo);
assert.strictEqual(actual, true);
});

it('passes validation when required parameters are valid with id and listPublications', async () => {
const actual = await command.validate({ options: { siteUrl: 'https://contoso.sharepoint.com/sites/sales', id: '9b1b1e42-794b-4c71-93ac-5ed92488b67f', listPublications: true } }, commandInfo);
assert.strictEqual(actual, true);
});

it('passes validation when required parameters are valid with title and listPublications', async () => {
const actual = await command.validate({ options: { siteUrl: 'https://contoso.sharepoint.com/sites/sales', title: 'ModelName', listPublications: true } }, commandInfo);
assert.strictEqual(actual, true);
});

it('fails validation when siteUrl is not valid', async () => {
const actual = await command.validate({ options: { siteUrl: 'invalidUrl', id: '9b1b1e42-794b-4c71-93ac-5ed92488b67f' } }, commandInfo);
assert.notStrictEqual(actual, true);
});

it('fails validation when id is not valid', async () => {
const actual = await command.validate({ options: { siteUrl: 'https://contoso.sharepoint.com/sites/sales', id: 'foo' } }, commandInfo);
assert.notStrictEqual(actual, true);
});

it('correctly handles site is not Content Site', async () => {
sinon.stub(request, 'get').callsFake(async (opts) => {
if (opts.url === `https://contoso.sharepoint.com/sites/portal/_api/web?$select=WebTemplateConfiguration`) {
return {
WebTemplateConfiguration: 'SITEPAGEPUBLISHING#0'
};
}

throw 'Invalid request';
});

await assert.rejects(command.action(logger, { options: { verbose: true, siteUrl: 'https://contoso.sharepoint.com/sites/portal', id: '9b1b1e42-794b-4c71-93ac-5ed92488b67f' } }),
new CommandError('https://contoso.sharepoint.com/sites/portal is not a content site.'));
});


it('correctly handles an access denied error', async () => {
sinon.stub(request, 'get').callsFake(async (opts) => {
if (opts.url === `https://contoso.sharepoint.com/sites/portal/_api/web?$select=WebTemplateConfiguration`) {
throw {
error: {
"odata.error": {
message: {
lang: "en-US",
value: "Attempted to perform an unauthorized operation."
}
}
}
};
}

throw 'Invalid request';
});

await assert.rejects(command.action(logger, { options: { verbose: true, siteUrl: 'https://contoso.sharepoint.com/sites/portal', id: '9b1b1e42-794b-4c71-93ac-5ed92488b67f' } }),
new CommandError('Attempted to perform an unauthorized operation.'));
});


it('retrieves model by id', async () => {
sinon.stub(request, 'get').callsFake(async (opts) => {
if (opts.url === `https://contoso.sharepoint.com/sites/portal/_api/web?$select=WebTemplateConfiguration`) {
return {
WebTemplateConfiguration: 'CONTENTCTR#0'
};
}

if (opts.url === `https://contoso.sharepoint.com/sites/portal/_api/machinelearning/models/getbyuniqueid('9b1b1e42-794b-4c71-93ac-5ed92488b67f')`) {
return model;
}

throw 'Invalid request';
});

await command.action(logger, { options: { siteUrl: 'https://contoso.sharepoint.com/sites/portal', id: '9b1b1e42-794b-4c71-93ac-5ed92488b67f' } });
assert(loggerLogSpy.calledOnceWithExactly(model));
});

it('retrieves model by id with listPublications', async () => {
sinon.stub(request, 'get').callsFake(async (opts) => {
if (opts.url === `https://contoso.sharepoint.com/sites/portal/_api/web?$select=WebTemplateConfiguration`) {
return {
WebTemplateConfiguration: 'CONTENTCTR#0'
};
}

if (opts.url === `https://contoso.sharepoint.com/sites/portal/_api/machinelearning/models/getbyuniqueid('164720c8-35ee-4157-ba26-db6726264f9d')`) {
return model;
}

if (opts.url === `https://contoso.sharepoint.com/sites/portal/_api/machinelearning/publications/getbymodeluniqueid('164720c8-35ee-4157-ba26-db6726264f9d')`) {
return { value: publications };
}

throw 'Invalid request';
});

await command.action(logger, { options: { siteUrl: 'https://contoso.sharepoint.com/sites/portal', id: '164720c8-35ee-4157-ba26-db6726264f9d', listPublications: true } });
assert(loggerLogSpy.calledOnceWithExactly({ ...model, Publications: publications }));
});

it('gets correct model when the site URL has a trailing slash', async () => {
sinon.stub(request, 'get').callsFake(async (opts) => {
if (opts.url === `https://contoso.sharepoint.com/sites/portal/_api/web?$select=WebTemplateConfiguration`) {
return {
WebTemplateConfiguration: 'CONTENTCTR#0'
};
}

if (opts.url === `https://contoso.sharepoint.com/sites/portal/_api/machinelearning/models/getbyuniqueid('9b1b1e42-794b-4c71-93ac-5ed92488b67f')`) {
return model;
}

throw 'Invalid request';
});

await command.action(logger, { options: { siteUrl: 'https://contoso.sharepoint.com/sites/portal/', id: '9b1b1e42-794b-4c71-93ac-5ed92488b67f' } });
assert(loggerLogSpy.calledOnceWithExactly(model));
});

it('retrieves model by title', async () => {
sinon.stub(request, 'get').callsFake(async (opts) => {
if (opts.url === `https://contoso.sharepoint.com/sites/portal/_api/web?$select=WebTemplateConfiguration`) {
return {
WebTemplateConfiguration: 'CONTENTCTR#0'
};
}

if (opts.url === `https://contoso.sharepoint.com/sites/portal/_api/machinelearning/models/getbytitle('ModelName')`) {
return model;
}

throw 'Invalid request';
});

await command.action(logger, { options: { siteUrl: 'https://contoso.sharepoint.com/sites/portal', title: 'ModelName' } });
assert(loggerLogSpy.calledOnceWithExactly(model));
});

it('retrieves model by title with listPublications', async () => {
sinon.stub(request, 'get').callsFake(async (opts) => {
if (opts.url === `https://contoso.sharepoint.com/sites/portal/_api/web?$select=WebTemplateConfiguration`) {
return {
WebTemplateConfiguration: 'CONTENTCTR#0'
};
}

if (opts.url === `https://contoso.sharepoint.com/sites/portal/_api/machinelearning/models/getbytitle('ModelName')`) {
return model;
}

if (opts.url === `https://contoso.sharepoint.com/sites/portal/_api/machinelearning/publications/getbymodeluniqueid('164720c8-35ee-4157-ba26-db6726264f9d')`) {
return { value: publications };
}

throw 'Invalid request';
});

await command.action(logger, { options: { siteUrl: 'https://contoso.sharepoint.com/sites/portal', title: 'ModelName', listPublications: true } });
assert(loggerLogSpy.calledOnceWithExactly({ ...model, Publications: publications }));
});
});
Loading

0 comments on commit 4e27dc2

Please sign in to comment.