diff --git a/docs/docs/cmd/spp/autofillcolumn/autofillcolumn-set.mdx b/docs/docs/cmd/spp/autofillcolumn/autofillcolumn-set.mdx new file mode 100644 index 00000000000..935a1189cb0 --- /dev/null +++ b/docs/docs/cmd/spp/autofillcolumn/autofillcolumn-set.mdx @@ -0,0 +1,104 @@ +import Global from '/docs/cmd/_global.mdx'; +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + +# spp autofillcolumn set + +Applies the autofill option to the selected column + +## Usage + +```sh +m365 spp autofillcolumn set [options] +``` + +## Options + +```md definition-list +`-u, --siteUrl ` +: The URL of the target site. + +`--listTitle [listTitle]` +: The title of the library on which to apply the model. Specify either `listTitle`, `listId`, or `listUrl` but not multiple. + +`--listId [listId]` +: The ID of the library on which to apply the model. Specify either `listTitle`, `listId`, or `listUrl` but not multiple. + +`--listUrl [listUrl]` +: Server or web-relative URL of the library on which to apply the model. Specify either `listTitle`, `listId`, or `listUrl` but not multiple. + +`-i, --columnId [columnId]` +: ID of the column to which the autofill option will be assigned. Specify either `columnId`, `columnTitle` or `columnInternalName`. + +`-t, --columnTitle [columnTitle]` +: Title of the column to which the autofill option will be assigned. Specify either `columnId`, `columnTitle` or `columnInternalName`. + +`--columnInternalName [columnInternalName]` +: The internal name (case-sensitive) of the column to which the autofill option will be assigned. Specify either `columnId`, `columnTitle` or `columnInternalName`. + +`--prompt [prompt]` +: The text in natural language that will be used to extract specific information or generate information from files within a SharePoint library. + +`--isEnabled [isEnabled]` +: Enables or disables the autofill column feature. +``` + + + +## Remarks + +The `prompt` parameter is required when setting the autofill column for the first time. + +## Examples + +Applies an autofill column on a selected column to a document library based on the list id. + +```sh +m365 spp autofillcolumn set --siteUrl "https://contoso.sharepoint.com/sites/mainSite" --listId "7645e69d-21fb-4a24-a17a-9bdfa7cb63dc" --columnId "1045e69d-21fb-4214-a25a-9bdfa7cb63a2" --prompt "Write a 2-line summary of the document" +``` + +Applies an autofill column on a selected column to a document library based on the list title. + +```sh +m365 spp autofillcolumn set --siteUrl "https://contoso.sharepoint.com/sites/mainSite" --listTitle "Documents" --columnId "1045e69d-21fb-4214-a25a-9bdfa7cb63a2" --prompt "Write a 2-line summary of the document" +``` + +Applies an autofill column on a selected column to a document library based on the list url. + +```sh +m365 spp autofillcolumn set --siteUrl "https://contoso.sharepoint.com/sites/mainSite" --listUrl '/Shared Documents' --columnId "1045e69d-21fb-4214-a25a-9bdfa7cb63a2" --prompt "Write a 2-line summary of the document" +``` + +Applies an autofill column on a selected column to a document library based on the list id and column title. + +```sh +m365 spp autofillcolumn set --siteUrl "https://contoso.sharepoint.com/sites/mainSite" --listId "7645e69d-21fb-4a24-a17a-9bdfa7cb63dc" --columnTitle "ColumnTitle" --prompt "Write a 2-line summary of the document" +``` + +Applies an autofill column on a selected column to a document library based on the list id and column internal name. + +```sh +m365 spp autofillcolumn set --siteUrl "https://contoso.sharepoint.com/sites/mainSite" --listId "7645e69d-21fb-4a24-a17a-9bdfa7cb63dc" --columnInternalName "ColumnTitle" --prompt "Write a 2-line summary of the document" +``` + +Disables the autofill column for a selected column in a document library, based on the list id and column title. + +```sh +m365 spp autofillcolumn set --siteUrl "https://contoso.sharepoint.com/sites/mainSite" --listId "7645e69d-21fb-4a24-a17a-9bdfa7cb63dc" --columnTitle "ColumnTitle" --isEnabled false +``` + +Reenable the autofill column for a selected column in a document library, based on the list id and column title. + +```sh +m365 spp autofillcolumn set --siteUrl "https://contoso.sharepoint.com/sites/mainSite" --listId "7645e69d-21fb-4a24-a17a-9bdfa7cb63dc" --columnTitle "ColumnTitle" --isEnabled true +``` + +Modifies the prompt for the autofill column + +```sh +m365 spp autofillcolumn set --siteUrl "https://contoso.sharepoint.com/sites/mainSite" --listId "7645e69d-21fb-4a24-a17a-9bdfa7cb63dc" --columnTitle "ColumnTitle" --isEnabled true --prompt "Write a 1-line summary of the document" +``` + +## Response + +The command won't return a response on success. diff --git a/docs/src/config/sidebars.ts b/docs/src/config/sidebars.ts index 4496615bb50..77acd5573bb 100644 --- a/docs/src/config/sidebars.ts +++ b/docs/src/config/sidebars.ts @@ -4231,6 +4231,15 @@ const sidebars: SidebarsConfig = { }, { 'SharePoint Premium (spp)': [ + { + autofillcolumn: [ + { + type: 'doc', + label: 'autofillcolumn set', + id: 'cmd/spp/autofillcolumn/autofillcolumn-set' + } + ] + }, { contentcenter: [ { diff --git a/eslint.config.mjs b/eslint.config.mjs index 5c06fd069f3..3ba64be050a 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -23,6 +23,7 @@ const dictionary = [ 'assets', 'assignment', 'audit', + 'autofill', 'azure', 'bin', 'builder', diff --git a/src/m365/spp/commands.ts b/src/m365/spp/commands.ts index 3cce86bcb8e..056cb8f9372 100644 --- a/src/m365/spp/commands.ts +++ b/src/m365/spp/commands.ts @@ -1,6 +1,7 @@ const prefix: string = 'spp'; export default { + AUTOFILLCOLUMN_SET: `${prefix} autofillcolumn set`, CONTENTCENTER_LIST: `${prefix} contentcenter list`, MODEL_APPLY: `${prefix} model apply`, MODEL_GET: `${prefix} model get`, diff --git a/src/m365/spp/commands/autofillcolumn/autofillcolumn-set.spec.ts b/src/m365/spp/commands/autofillcolumn/autofillcolumn-set.spec.ts new file mode 100644 index 00000000000..1c9e9613329 --- /dev/null +++ b/src/m365/spp/commands/autofillcolumn/autofillcolumn-set.spec.ts @@ -0,0 +1,481 @@ +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 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 './autofillcolumn-set.js'; +import { z } from 'zod'; +import { CommandError } from '../../../../Command.js'; + +describe(commands.AUTOFILLCOLUMN_SET, () => { + let log: string[]; + let logger: Logger; + let commandInfo: CommandInfo; + let commandOptionsSchema: z.ZodTypeAny; + + before(() => { + sinon.stub(auth, 'restoreAuth').resolves(); + sinon.stub(telemetry, 'trackEvent').resolves(); + sinon.stub(pid, 'getProcessName').returns(''); + sinon.stub(session, 'getId').returns(''); + auth.connection.active = true; + commandInfo = cli.getCommandInfo(command); + commandOptionsSchema = commandInfo.command.getSchemaToParse()!; + }); + + beforeEach(() => { + log = []; + logger = { + log: async (msg: string) => { + log.push(msg); + }, + logRaw: async (msg: string) => { + log.push(msg); + }, + logToStderr: async (msg: string) => { + log.push(msg); + } + }; + }); + + afterEach(() => { + sinonUtil.restore([ + request.get, + request.post + ]); + }); + + after(() => { + sinon.restore(); + auth.connection.active = false; + }); + + it('has correct name', () => { + assert.strictEqual(command.name, commands.AUTOFILLCOLUMN_SET); + }); + + it('has a description', () => { + assert.notStrictEqual(command.description, null); + }); + + it('passes validation when required parameters are valid with column id and list id', async () => { + const actual = commandOptionsSchema.safeParse({ siteUrl: 'https://contoso.sharepoint.com/sites/sales', columnId: '9b1b1e42-794b-4c71-93ac-5ed92488b67f', listId: '421b1e42-794b-4c71-93ac-5ed92488b67d', prompt: 'test' }); + assert.strictEqual(actual.success, true); + }); + + it('passes validation when required parameters are valid with column title and list id', async () => { + const actual = commandOptionsSchema.safeParse({ siteUrl: 'https://contoso.sharepoint.com/sites/sales', columnTitle: 'ColumnName', listId: '421b1e42-794b-4c71-93ac-5ed92488b67d', prompt: 'test' }); + assert.strictEqual(actual.success, true); + }); + + it('passes validation when required parameters are valid with column id and list title', async () => { + const actual = commandOptionsSchema.safeParse({ siteUrl: 'https://contoso.sharepoint.com/sites/sales', columnId: '9b1b1e42-794b-4c71-93ac-5ed92488b67f', listTitle: 'Documents', prompt: 'test' }); + assert.strictEqual(actual.success, true); + }); + + it('passes validation when required parameters are valid with column id and list URL', async () => { + const actual = commandOptionsSchema.safeParse({ siteUrl: 'https://contoso.sharepoint.com/sites/sales', columnId: '9b1b1e42-794b-4c71-93ac-5ed92488b67f', listUrl: '/Shared Documents', prompt: 'test' }); + assert.strictEqual(actual.success, true); + }); + + it('fails validation when siteUrl is not valid', async () => { + const actual = commandOptionsSchema.safeParse({ siteUrl: 'invalidUrl', columnId: '9b1b1e42-794b-4c71-93ac-5ed92488b67f', listId: '421b1e42-794b-4c71-93ac-5ed92488b67d', prompt: 'test' }); + assert.notStrictEqual(actual.success, true); + }); + + it('fails validation when column id is not valid', async () => { + const actual = commandOptionsSchema.safeParse({ siteUrl: 'https://contoso.sharepoint.com/sites/sales', columnId: 'invalidId', listId: '421b1e42-794b-4c71-93ac-5ed92488b67d', prompt: 'test' }); + assert.notStrictEqual(actual.success, true); + }); + + it('fails validation when list id is not valid', async () => { + const actual = commandOptionsSchema.safeParse({ siteUrl: 'https://contoso.sharepoint.com/sites/sales', columnId: '9b1b1e42-794b-4c71-93ac-5ed92488b67f', listId: 'invalidId', prompt: 'test' }); + assert.notStrictEqual(actual.success, true); + }); + + it('fails validation when both columnId and columnTitle are provided', async () => { + const actual = commandOptionsSchema.safeParse({ siteUrl: 'https://contoso.sharepoint.com/sites/sales', columnId: '9b1b1e42-794b-4c71-93ac-5ed92488b67f', columnTitle: "DoubledColumn", listUrl: '/Shared Documents', prompt: 'test' }); + assert.notStrictEqual(actual.success, true); + }); + + it('fails validation when both listTitle and ListUrl are provided', async () => { + const actual = commandOptionsSchema.safeParse({ siteUrl: 'https://contoso.sharepoint.com/sites/sales', columnId: '9b1b1e42-794b-4c71-93ac-5ed92488b67f', listUrl: '/Shared Documents', listTitle: "Documents", prompt: 'test' }); + assert.notStrictEqual(actual.success, true); + }); + + it('apply autofill to column by id and list id', async () => { + sinon.stub(request, 'get').callsFake(async (opts) => { + if (opts.url === `https://contoso.sharepoint.com/sites/sales/_api/web/lists(guid'421b1e42-794b-4c71-93ac-5ed92488b67d')/fields/getbyid('9b1b1e42-794b-4c71-93ac-5ed92488b67f')?&$select=Id,Title,FieldTypeKind,AutofillInfo`) { + return { + Id: '9b1b1e42-794b-4c71-93ac-5ed92488b67f', + Title: 'ColumnName', + FieldTypeKind: 1, + AutofillInfo: null + }; + } + + if (opts.url === `https://contoso.sharepoint.com/sites/sales/_api/web/lists(guid'421b1e42-794b-4c71-93ac-5ed92488b67d')?$select=Id,BaseType`) { + return { + Id: "421b1e42-794b-4c71-93ac-5ed92488b67d", + BaseType: 1 + }; + } + + throw `${opts.url} is invalid request`; + }); + + const stubPost = sinon.stub(request, 'post').callsFake(async (opts) => { + if (opts.url === `https://contoso.sharepoint.com/sites/sales/_api/machinelearning/SetSyntexPoweredColumnPrompts`) { + return; + } + + throw `${opts.url} is invalid request`; + }); + + await command.action(logger, { options: commandOptionsSchema.parse({ siteUrl: 'https://contoso.sharepoint.com/sites/sales', columnId: '9b1b1e42-794b-4c71-93ac-5ed92488b67f', listId: '421b1e42-794b-4c71-93ac-5ed92488b67d', prompt: 'test', verbose: true }) }); + assert.deepStrictEqual(stubPost.lastCall.args[0].data, { + docLibId: `{421b1e42-794b-4c71-93ac-5ed92488b67d}`, + syntexPoweredColumnPrompts: JSON.stringify([{ columnId: '9b1b1e42-794b-4c71-93ac-5ed92488b67f', columnName: 'ColumnName', prompt: 'test', isEnabled: true }]) + }); + }); + + it('apply autofill to column by id and list title', async () => { + sinon.stub(request, 'get').callsFake(async (opts) => { + if (opts.url === `https://contoso.sharepoint.com/sites/sales/_api/web/lists(guid'421b1e42-794b-4c71-93ac-5ed92488b67d')/fields/getbyid('9b1b1e42-794b-4c71-93ac-5ed92488b67f')?&$select=Id,Title,FieldTypeKind,AutofillInfo`) { + return { + Id: '9b1b1e42-794b-4c71-93ac-5ed92488b67f', + Title: 'ColumnName', + FieldTypeKind: 1, + AutofillInfo: null + }; + } + + if (opts.url === `https://contoso.sharepoint.com/sites/sales/_api/web/lists/getByTitle('Documents')?$select=Id,BaseType`) { + return { + Id: "421b1e42-794b-4c71-93ac-5ed92488b67d", + BaseType: 1 + }; + } + + throw `${opts.url} is invalid request`; + }); + + const stubPost = sinon.stub(request, 'post').callsFake(async (opts) => { + if (opts.url === `https://contoso.sharepoint.com/sites/sales/_api/machinelearning/SetSyntexPoweredColumnPrompts`) { + return; + } + + throw `${opts.url} is invalid request`; + }); + + await command.action(logger, { options: commandOptionsSchema.parse({ siteUrl: 'https://contoso.sharepoint.com/sites/sales', columnId: '9b1b1e42-794b-4c71-93ac-5ed92488b67f', listTitle: 'Documents', prompt: 'test' }) }); + assert.deepStrictEqual(stubPost.lastCall.args[0].data, { + docLibId: `{421b1e42-794b-4c71-93ac-5ed92488b67d}`, + syntexPoweredColumnPrompts: JSON.stringify([{ columnId: '9b1b1e42-794b-4c71-93ac-5ed92488b67f', columnName: 'ColumnName', prompt: 'test', isEnabled: true }]) + }); + }); + + it('apply autofill to column by title and list id', async () => { + sinon.stub(request, 'get').callsFake(async (opts) => { + if (opts.url === + `https://contoso.sharepoint.com/sites/sales/_api/web/lists(guid'421b1e42-794b-4c71-93ac-5ed92488b67d')/fields/getbyinternalnameortitle('ColumnName')?&$select=Id,Title,FieldTypeKind,AutofillInfo`) { + return { + Id: '9b1b1e42-794b-4c71-93ac-5ed92488b67f', + Title: 'ColumnName', + FieldTypeKind: 1, + AutofillInfo: null + }; + } + + if (opts.url === `https://contoso.sharepoint.com/sites/sales/_api/web/lists(guid'421b1e42-794b-4c71-93ac-5ed92488b67d')?$select=Id,BaseType`) { + return { + Id: "421b1e42-794b-4c71-93ac-5ed92488b67d", + BaseType: 1 + }; + } + + throw `${opts.url} is invalid request`; + }); + + const stubPost = sinon.stub(request, 'post').callsFake(async (opts) => { + if (opts.url === `https://contoso.sharepoint.com/sites/sales/_api/machinelearning/SetSyntexPoweredColumnPrompts`) { + return; + } + + throw `${opts.url} is invalid request`; + }); + + await command.action(logger, { options: commandOptionsSchema.parse({ siteUrl: 'https://contoso.sharepoint.com/sites/sales', columnTitle: 'ColumnName', listId: '421b1e42-794b-4c71-93ac-5ed92488b67d', prompt: 'test', verbose: true }) }); + assert.deepStrictEqual(stubPost.lastCall.args[0].data, { + docLibId: `{421b1e42-794b-4c71-93ac-5ed92488b67d}`, + syntexPoweredColumnPrompts: JSON.stringify([{ columnId: '9b1b1e42-794b-4c71-93ac-5ed92488b67f', columnName: 'ColumnName', prompt: 'test', isEnabled: true }]) + }); + }); + + it('apply autofill to column by internalName and list id', async () => { + sinon.stub(request, 'get').callsFake(async (opts) => { + if (opts.url === + `https://contoso.sharepoint.com/sites/sales/_api/web/lists(guid'421b1e42-794b-4c71-93ac-5ed92488b67d')/fields/getbyinternalnameortitle('ColumnInternalName')?&$select=Id,Title,FieldTypeKind,AutofillInfo`) { + return { + Id: '9b1b1e42-794b-4c71-93ac-5ed92488b67f', + Title: 'ColumnInternalName', + FieldTypeKind: 1, + AutofillInfo: null + }; + } + + if (opts.url === `https://contoso.sharepoint.com/sites/sales/_api/web/lists(guid'421b1e42-794b-4c71-93ac-5ed92488b67d')?$select=Id,BaseType`) { + return { + Id: "421b1e42-794b-4c71-93ac-5ed92488b67d", + BaseType: 1 + }; + } + + throw `${opts.url} is invalid request`; + }); + + const stubPost = sinon.stub(request, 'post').callsFake(async (opts) => { + if (opts.url === `https://contoso.sharepoint.com/sites/sales/_api/machinelearning/SetSyntexPoweredColumnPrompts`) { + return; + } + + throw `${opts.url} is invalid request`; + }); + + await command.action(logger, { options: commandOptionsSchema.parse({ siteUrl: 'https://contoso.sharepoint.com/sites/sales', columnInternalName: 'ColumnInternalName', listId: '421b1e42-794b-4c71-93ac-5ed92488b67d', prompt: 'test', verbose: true }) }); + assert.deepStrictEqual(stubPost.lastCall.args[0].data, { + docLibId: `{421b1e42-794b-4c71-93ac-5ed92488b67d}`, + syntexPoweredColumnPrompts: JSON.stringify([{ columnId: '9b1b1e42-794b-4c71-93ac-5ed92488b67f', columnName: 'ColumnInternalName', prompt: 'test', isEnabled: true }]) + }); + }); + + it('apply autofill to column by id and list url', async () => { + sinon.stub(request, 'get').callsFake(async (opts) => { + if (opts.url === `https://contoso.sharepoint.com/sites/sales/_api/web/lists(guid'421b1e42-794b-4c71-93ac-5ed92488b67d')/fields/getbyinternalnameortitle('ColumnName')?&$select=Id,Title,FieldTypeKind,AutofillInfo`) { + return { + Id: '9b1b1e42-794b-4c71-93ac-5ed92488b67f', + Title: 'ColumnName', + FieldTypeKind: 1, + AutofillInfo: null + }; + } + + if (opts.url === `https://contoso.sharepoint.com/sites/sales/_api/web/GetList('%2Fsites%2Fsales%2FShared%20Documents')?$select=Id,BaseType`) { + return { + Id: "421b1e42-794b-4c71-93ac-5ed92488b67d", + BaseType: 1 + }; + } + + throw `${opts.url} is invalid request`; + }); + + const stubPost = sinon.stub(request, 'post').callsFake(async (opts) => { + if (opts.url === `https://contoso.sharepoint.com/sites/sales/_api/machinelearning/SetSyntexPoweredColumnPrompts`) { + return; + } + + throw `${opts.url} is invalid request`; + }); + + await command.action(logger, { options: commandOptionsSchema.parse({ siteUrl: 'https://contoso.sharepoint.com/sites/sales', columnTitle: 'ColumnName', prompt: 'test', listUrl: '/Shared Documents', isEnabled: false }) }); + assert.deepStrictEqual(stubPost.lastCall.args[0].data, { + docLibId: `{421b1e42-794b-4c71-93ac-5ed92488b67d}`, + syntexPoweredColumnPrompts: JSON.stringify([{ columnId: '9b1b1e42-794b-4c71-93ac-5ed92488b67f', columnName: 'ColumnName', prompt: 'test', isEnabled: false }]) + }); + }); + + it('set autofill prompt to column by id and list url', async () => { + sinon.stub(request, 'get').callsFake(async (opts) => { + if (opts.url === `https://contoso.sharepoint.com/sites/sales/_api/web/lists(guid'421b1e42-794b-4c71-93ac-5ed92488b67d')/fields/getbyinternalnameortitle('ColumnName')?&$select=Id,Title,FieldTypeKind,AutofillInfo`) { + return { + Id: '9b1b1e42-794b-4c71-93ac-5ed92488b67f', + Title: 'ColumnName', + FieldTypeKind: 1, + AutofillInfo: "{ \"LLM\": {\"IsEnabled\": true, \"Prompt\": \"test\" },\"PrebuiltModel\":null}" + }; + } + + if (opts.url === `https://contoso.sharepoint.com/sites/sales/_api/web/GetList('%2Fsites%2Fsales%2FShared%20Documents')?$select=Id,BaseType`) { + return { + Id: "421b1e42-794b-4c71-93ac-5ed92488b67d", + BaseType: 1 + }; + } + + throw `${opts.url} is invalid request`; + }); + + const stubPost = sinon.stub(request, 'post').callsFake(async (opts) => { + if (opts.url === `https://contoso.sharepoint.com/sites/sales/_api/machinelearning/SetColumnLLMInfo`) { + return; + } + + throw `${opts.url} is invalid request`; + }); + + await command.action(logger, { options: commandOptionsSchema.parse({ siteUrl: 'https://contoso.sharepoint.com/sites/sales', columnTitle: 'ColumnName', prompt: 'new prompt', listUrl: '/Shared Documents' }) }); + assert.deepStrictEqual(stubPost.lastCall.args[0].data, { + autofillPrompt: 'new prompt', + columnId: '9b1b1e42-794b-4c71-93ac-5ed92488b67f', + docLibId: `{421b1e42-794b-4c71-93ac-5ed92488b67d}`, + isEnabled: true + }); + }); + + it('set autofill isEnable to false to column by id and list url', async () => { + sinon.stub(request, 'get').callsFake(async (opts) => { + if (opts.url === `https://contoso.sharepoint.com/sites/sales/_api/web/lists(guid'421b1e42-794b-4c71-93ac-5ed92488b67d')/fields/getbyinternalnameortitle('ColumnName')?&$select=Id,Title,FieldTypeKind,AutofillInfo`) { + return { + Id: '9b1b1e42-794b-4c71-93ac-5ed92488b67f', + Title: 'ColumnName', + FieldTypeKind: 1, + AutofillInfo: "{ \"LLM\": {\"IsEnabled\": true, \"Prompt\": \"test\" },\"PrebuiltModel\":null}" + }; + } + + if (opts.url === `https://contoso.sharepoint.com/sites/sales/_api/web/GetList('%2Fsites%2Fsales%2FShared%20Documents')?$select=Id,BaseType`) { + return { + Id: "421b1e42-794b-4c71-93ac-5ed92488b67d", + BaseType: 1 + }; + } + + throw `${opts.url} is invalid request`; + }); + + const stubPost = sinon.stub(request, 'post').callsFake(async (opts) => { + if (opts.url === `https://contoso.sharepoint.com/sites/sales/_api/machinelearning/SetColumnLLMInfo`) { + return; + } + + throw `${opts.url} is invalid request`; + }); + + await command.action(logger, { options: commandOptionsSchema.parse({ siteUrl: 'https://contoso.sharepoint.com/sites/sales', columnTitle: 'ColumnName', isEnabled: false, listUrl: '/Shared Documents' }) }); + assert.deepStrictEqual(stubPost.lastCall.args[0].data, { + autofillPrompt: 'test', + columnId: '9b1b1e42-794b-4c71-93ac-5ed92488b67f', + docLibId: `{421b1e42-794b-4c71-93ac-5ed92488b67d}`, + isEnabled: false + }); + }); + + it('correctly handles error when list is not found', async () => { + sinon.stub(request, 'get').callsFake(async (opts) => { + if (opts.url === `https://contoso.sharepoint.com/sites/sales/_api/web/lists(guid'421b1e42-794b-4c71-93ac-5ed92488b67d')/fields/getbyid('9b1b1e42-794b-4c71-93ac-5ed92488b67f')?&$select=Id,Title,FieldTypeKind,AutofillInfo`) { + return { + Id: '9b1b1e42-794b-4c71-93ac-5ed92488b67f', + Title: 'ColumnName', + FieldTypeKind: 1, + AutofillInfo: null + }; + } + + if (opts.url === `https://contoso.sharepoint.com/sites/sales/_api/web/lists(guid'421b1e42-794b-4c71-93ac-5ed92488b67d')?$select=Id,BaseType`) { + throw { + error: { + "odata.error": { + code: "-1, Microsoft.SharePoint.Client.ResourceNotFoundException", + message: { + lang: "en-US", + value: "List does not exist. The page you selected contains a list that does not exist. It may have been deleted by another user." + } + } + } + }; + } + + throw `${opts.url} is invalid request`; + }); + + sinon.stub(request, 'post').callsFake(async (opts) => { + if (opts.url === `https://contoso.sharepoint.com/sites/sales/_api/machinelearning/SetSyntexPoweredColumnPrompts`) { + return; + } + + throw `${opts.url} is invalid request`; + }); + + await assert.rejects(command.action(logger, { options: commandOptionsSchema.parse({ siteUrl: 'https://contoso.sharepoint.com/sites/sales', columnId: '9b1b1e42-794b-4c71-93ac-5ed92488b67f', listId: '421b1e42-794b-4c71-93ac-5ed92488b67d', prompt: 'test', verbose: true }) }), new CommandError('List does not exist. The page you selected contains a list that does not exist. It may have been deleted by another user.')); + }); + + it('correctly handles error when trying to apply autofill column to a SharePoint list that is not a document library and returns the error message "The specified list is not a document library."', async () => { + sinon.stub(request, 'get').callsFake(async (opts) => { + if (opts.url === `https://contoso.sharepoint.com/sites/sales/_api/web/lists(guid'421b1e42-794b-4c71-93ac-5ed92488b67d')/fields/getbyid('9b1b1e42-794b-4c71-93ac-5ed92488b67f')?&$select=Id,Title,FieldTypeKind,AutofillInfo`) { + return { + Id: '9b1b1e42-794b-4c71-93ac-5ed92488b67f', + Title: 'ColumnName', + FieldTypeKind: 1, + AutofillInfo: null + }; + } + + if (opts.url === `https://contoso.sharepoint.com/sites/sales/_api/web/lists(guid'421b1e42-794b-4c71-93ac-5ed92488b67d')?$select=Id,BaseType`) { + return { + Id: "421b1e42-794b-4c71-93ac-5ed92488b67d", + BaseType: 0 + }; + } + + throw `${opts.url} is invalid request`; + }); + + await assert.rejects(command.action(logger, { options: commandOptionsSchema.parse({ siteUrl: 'https://contoso.sharepoint.com/sites/sales', columnId: '9b1b1e42-794b-4c71-93ac-5ed92488b67f', listId: '421b1e42-794b-4c71-93ac-5ed92488b67d', prompt: 'test', verbose: true }) }), new CommandError('The specified list is not a document library.')); + }); + + it('correctly handles error when trying to apply autofill to a column with an incorrect type."', async () => { + sinon.stub(request, 'get').callsFake(async (opts) => { + if (opts.url === `https://contoso.sharepoint.com/sites/sales/_api/web/lists(guid'421b1e42-794b-4c71-93ac-5ed92488b67d')/fields/getbyid('9b1b1e42-794b-4c71-93ac-5ed92488b67f')?&$select=Id,Title,FieldTypeKind,AutofillInfo`) { + return { + Id: '9b1b1e42-794b-4c71-93ac-5ed92488b67f', + Title: 'ColumnName', + FieldTypeKind: 17, + AutofillInfo: null + }; + } + + if (opts.url === `https://contoso.sharepoint.com/sites/sales/_api/web/lists(guid'421b1e42-794b-4c71-93ac-5ed92488b67d')?$select=Id,BaseType`) { + return { + Id: "421b1e42-794b-4c71-93ac-5ed92488b67d", + BaseType: 1 + }; + } + + throw `${opts.url} is invalid request`; + }); + + await assert.rejects(command.action(logger, { options: commandOptionsSchema.parse({ siteUrl: 'https://contoso.sharepoint.com/sites/sales', columnId: '9b1b1e42-794b-4c71-93ac-5ed92488b67f', listId: '421b1e42-794b-4c71-93ac-5ed92488b67d', prompt: 'test', verbose: true }) }), new CommandError('The specified column has incorrect type.')); + }); + + it('correctly handles error when applying autofill without the required prompt parameter.', async () => { + sinon.stub(request, 'get').callsFake(async (opts) => { + if (opts.url === `https://contoso.sharepoint.com/sites/sales/_api/web/lists(guid'421b1e42-794b-4c71-93ac-5ed92488b67d')/fields/getbyid('9b1b1e42-794b-4c71-93ac-5ed92488b67f')?&$select=Id,Title,FieldTypeKind,AutofillInfo`) { + return { + Id: '9b1b1e42-794b-4c71-93ac-5ed92488b67f', + Title: 'ColumnName', + FieldTypeKind: 1, + AutofillInfo: null + }; + } + + if (opts.url === `https://contoso.sharepoint.com/sites/sales/_api/web/lists(guid'421b1e42-794b-4c71-93ac-5ed92488b67d')?$select=Id,BaseType`) { + return { + Id: "421b1e42-794b-4c71-93ac-5ed92488b67d", + BaseType: 1 + }; + } + + throw `${opts.url} is invalid request`; + }); + + await assert.rejects(command.action(logger, { options: commandOptionsSchema.parse({ siteUrl: 'https://contoso.sharepoint.com/sites/sales', columnId: '9b1b1e42-794b-4c71-93ac-5ed92488b67f', listId: '421b1e42-794b-4c71-93ac-5ed92488b67d' }) }), new CommandError('The prompt parameter is required when setting the autofill column for the first time.')); + }); +}); \ No newline at end of file diff --git a/src/m365/spp/commands/autofillcolumn/autofillcolumn-set.ts b/src/m365/spp/commands/autofillcolumn/autofillcolumn-set.ts new file mode 100644 index 00000000000..62270272d51 --- /dev/null +++ b/src/m365/spp/commands/autofillcolumn/autofillcolumn-set.ts @@ -0,0 +1,217 @@ +import { Logger } from '../../../../cli/Logger.js'; +import request, { CliRequestOptions } from '../../../../request.js'; +import { formatting } from '../../../../utils/formatting.js'; +import { urlUtil } from '../../../../utils/urlUtil.js'; +import { validation } from '../../../../utils/validation.js'; +import SpoCommand from '../../../base/SpoCommand.js'; +import { ListInstance } from '../../../spo/commands/list/ListInstance.js'; +import commands from '../../commands.js'; +import { zod } from '../../../../utils/zod.js'; +import { z } from 'zod'; +import { globalOptionsZod } from '../../../../Command.js'; + +enum AllowedFieldTypeKind { + Integer = 1, + Text = 2, + Note = 3, + DateTime = 4, + Counter = 5, + Choice = 6, + Boolean = 8, + Number = 9, + Currency = 10, + URL = 11, + Computed = 12, + MultiChoice = 15, + GridChoice = 16 +} + +interface Field { + Id: string; + Title: string; + FieldTypeKind: number; + AutofillInfo?: string; +} + +interface AutofillInfo { + LLM: { + Prompt: string; + IsEnabled: boolean; + } +} + +interface CommandArgs { + options: Options; +} + +const options = globalOptionsZod + .extend({ + siteUrl: zod.alias('u', z.string() + .refine(url => validation.isValidSharePointUrl(url) === true, url => ({ + message: `'${url}' is not a valid SharePoint Online site URL.` + }))), + listTitle: z.string().optional(), + listId: z.string().uuid() + .refine(value => validation.isValidGuid(value), listId => ({ + message: `${listId} in parameter listId is not a valid GUID` + })).optional(), + listUrl: z.string().optional(), + columnId: zod.alias('i', z.string().uuid() + .refine(value => validation.isValidGuid(value), columnId => ({ + message: `${columnId} in parameter columnId is not a valid GUID` + })).optional()), + columnTitle: zod.alias('t', z.string().optional()), + columnInternalName: z.string().optional(), + prompt: z.string().optional(), + isEnabled: z.boolean().optional() + }) + .strict(); + +declare type Options = z.infer; + +class SppAutofillColumnSetCommand extends SpoCommand { + public get name(): string { + return commands.AUTOFILLCOLUMN_SET; + } + + public get description(): string { + return 'Applies the autofill option to the selected column'; + } + + public get schema(): z.ZodTypeAny | undefined { + return options; + } + + public getRefinedSchema(schema: typeof options): z.ZodEffects | undefined { + return schema + .refine(options => [options.columnId, options.columnTitle, options.columnInternalName].filter(Boolean).length === 1, { + message: `Specify exactly one of the following options: 'columnId', 'columnTitle' or 'columnInternalName'.` + }) + .refine(options => [options.listTitle, options.listId, options.listUrl].filter(Boolean).length === 1, { + message: `Specify exactly one of the following options: 'listTitle', 'listId' or 'listUrl'.` + }); + } + + public async commandAction(logger: Logger, args: CommandArgs): Promise { + try { + if (this.verbose) { + await logger.log(`Applying an autofill column to a column...`); + } + + const siteUrl = urlUtil.removeTrailingSlashes(args.options.siteUrl); + const listInstance = await this.getDocumentLibraryInfo(siteUrl, args.options); + + if (listInstance.BaseType !== 1) { + throw Error(`The specified list is not a document library.`); + } + + const column = await this.getColumn(siteUrl, args.options, listInstance.Id); + + if (!Object.values(AllowedFieldTypeKind).includes(column.FieldTypeKind)) { + throw Error(`The specified column has incorrect type.`); + } + + if (column.AutofillInfo) { + await this.updateAutoFillColumnSettings(siteUrl, args.options, column.Id, listInstance.Id, column.AutofillInfo); + return; + } + + if (!args.options.prompt) { + throw Error(`The prompt parameter is required when setting the autofill column for the first time.`); + } + + await this.applyAutoFillColumnSettings(siteUrl, args.options, column.Id, column.Title, listInstance.Id); + } + catch (err: any) { + this.handleRejectedODataJsonPromise(err); + } + } + + private getDocumentLibraryInfo(siteUrl: string, options: Options): Promise { + let requestUrl = `${siteUrl}/_api/web`; + + if (options.listId) { + requestUrl += `/lists(guid'${formatting.encodeQueryParameter(options.listId)}')`; + } + else if (options.listTitle) { + requestUrl += `/lists/getByTitle('${formatting.encodeQueryParameter(options.listTitle)}')`; + } + else if (options.listUrl) { + const listServerRelativeUrl: string = urlUtil.getServerRelativePath(siteUrl, options.listUrl); + requestUrl += `/GetList('${formatting.encodeQueryParameter(listServerRelativeUrl)}')`; + } + + const requestOptions: CliRequestOptions = { + url: `${requestUrl}?$select=Id,BaseType`, + headers: { + 'accept': 'application/json;odata=nometadata' + }, + responseType: 'json' + }; + + return request.get(requestOptions); + } + + private getColumn(siteUrl: string, options: Options, listId: string): Promise { + let fieldRestUrl: string = ''; + + if (options.columnId) { + fieldRestUrl = `/getbyid('${formatting.encodeQueryParameter(options.columnId)}')`; + } + else { + fieldRestUrl = `/getbyinternalnameortitle('${formatting.encodeQueryParameter((options.columnTitle || options.columnInternalName) as string)}')`; + } + + const requestOptions: CliRequestOptions = { + url: `${siteUrl}/_api/web/lists(guid'${formatting.encodeQueryParameter(listId)}')/fields${fieldRestUrl}?&$select=Id,Title,FieldTypeKind,AutofillInfo`, + headers: { + accept: 'application/json;odata=nometadata' + }, + responseType: 'json' + }; + + return request.get(requestOptions); + } + + private updateAutoFillColumnSettings(siteUrl: string, options: Options, columnId: string, listInstanceId: string, autofillInfo: string): Promise { + const autofillInfoObj = JSON.parse(autofillInfo) as AutofillInfo; + + const requestOptions: CliRequestOptions = { + url: `${siteUrl}/_api/machinelearning/SetColumnLLMInfo`, + headers: { + accept: 'application/json;odata=nometadata' + }, + responseType: 'json', + data: { + autofillPrompt: options.prompt ?? autofillInfoObj.LLM.Prompt, + columnId: columnId, + docLibId: `{${listInstanceId}}`, + isEnabled: options.isEnabled !== undefined ? options.isEnabled : autofillInfoObj.LLM.IsEnabled + } + }; + + return request.post(requestOptions); + } + + private applyAutoFillColumnSettings(siteUrl: string, options: Options, columnId: string, columnTitle: string, listInstanceId: string): Promise { + const requestOptions: CliRequestOptions = { + url: `${siteUrl}/_api/machinelearning/SetSyntexPoweredColumnPrompts`, + headers: { + accept: 'application/json;odata=nometadata' + }, + data: { + docLibId: `{${listInstanceId}}`, + syntexPoweredColumnPrompts: JSON.stringify([{ + columnId: columnId, + columnName: columnTitle, + prompt: options.prompt, + isEnabled: options.isEnabled !== undefined ? options.isEnabled : true + }]) + } + }; + + return request.post(requestOptions); + } +} + +export default new SppAutofillColumnSetCommand(); \ No newline at end of file