diff --git a/.devproxy/api-specs/sharepoint.yaml b/.devproxy/api-specs/sharepoint.yaml index 4887a0bd60f..189c6a3be46 100644 --- a/.devproxy/api-specs/sharepoint.yaml +++ b/.devproxy/api-specs/sharepoint.yaml @@ -158,6 +158,25 @@ paths: responses: 200: description: OK + /_api/web/GetFileByServerRelativePath(DecodedUrl={filePath})/$value: + get: + parameters: + - name: filePath + in: path + required: true + description: URL-encoded server-relative path to the file + schema: + type: string + example: "'%2FShared%20Documents%2FForms%2Fclient_LocationBasedDefaults.html'" + security: + - delegated: + - AllSites.Read + - AllSites.Write + - AllSites.Manage + - AllSites.FullControl + responses: + 200: + description: OK /_api/web/GetFolderByServerRelativePath(DecodedUrl={folderPath}): get: parameters: @@ -218,6 +237,30 @@ paths: responses: 200: description: OK + /_api/web/lists({listId}): + get: + parameters: + - name: listId + in: path + required: true + description: list GUID + schema: + type: string + example: "'56bbfec4-4425-4660-95cb-da1887baa7b9'" + security: + - delegated: + - AllSites.Read + - AllSites.Write + - AllSites.Manage + - AllSites.FullControl + - application: + - Sites.Read.All + - Sites.Manage.All + - Sites.ReadWrite.All + - Sites.FullControl.All + responses: + 200: + description: OK /_api/web/webs: get: security: diff --git a/docs/docs/cmd/spo/list/list-defaultvalue-get.mdx b/docs/docs/cmd/spo/list/list-defaultvalue-get.mdx new file mode 100644 index 00000000000..6274dd26b05 --- /dev/null +++ b/docs/docs/cmd/spo/list/list-defaultvalue-get.mdx @@ -0,0 +1,118 @@ +import Global from '/docs/cmd/_global.mdx'; +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + +# spo list defaultvalue get + +Gets a specific default column value from a specific document library + +## Usage + +```sh +m365 spo list defaultvalue get [options] +``` + +## Options + +```md definition-list +`-u, --webUrl ` +: URL of the site where the list is located. + +`-i, --listId [listId]` +: ID of the list. Specify either `listTitle`, `listId`, or `listUrl`. + +`-t, --listTitle [listTitle]` +: Title of the list. Specify either `listTitle`, `listId`, or `listUrl`. + +`--listUrl [listUrl]` +: Server- or site-relative URL of the list. Specify either `listTitle`, `listId`, or `listUrl`. + +`--fieldName ` +: Internal name of the field. + +`--folderUrl [folderUrl]` +: Get a specific field of a specific folder by specifying a server- or site-relative URL. +``` + + + +## Permissions + + + + + | Resource | Permissions | + |------------|---------------| + | SharePoint | AllSites.Read | + + + + + | Resource | Permissions | + |------------|----------------| + | SharePoint | Sites.Read.All | + + + + +## Examples + +Get default column value from the root folder of the list + +```sh +m365 spo list defaultvalue get --webUrl https://contoso.sharepoint.com/sites/Marketing --listTitle Logos --fieldName Company +``` + +Get default column value from a specific folder of the list + +```sh +m365 spo list defaultvalue get --webUrl https://contoso.sharepoint.com/sites/Marketing --listTitle Logos --fieldName Company --folderUrl "/sites/Marketing/Logos/Contoso" +``` + +## Response + + + + + ```json + { + "fieldName": "Company", + "fieldValue": "Contoso", + "folderUrl": "sites/Marketing/Logos" + } + ``` + + + + + ```text + fieldName : Company + fieldValue: Contoso + folderUrl : sites/Marketing/Logos + ``` + + + + + ```csv + fieldName,fieldValue,folderUrl + Company,Contoso,sites/Marketing/Logos + ``` + + + + + ```md + # spo list defaultvalue get --webUrl "https://contoso.sharepoint.com/sites/Marketing" --listTitle "Logos" --fieldName "Company" + + Date: 2/20/2023 + + Property | Value + ---------|------- + fieldName | Company + fieldValue | Contoso + folderUrl | sites/Marketing/Logos + ``` + + + diff --git a/docs/src/config/sidebars.ts b/docs/src/config/sidebars.ts index f7e795494dc..c9fbf5073e5 100644 --- a/docs/src/config/sidebars.ts +++ b/docs/src/config/sidebars.ts @@ -3069,6 +3069,11 @@ const sidebars: SidebarsConfig = { label: 'list defaultvalue clear', id: 'cmd/spo/list/list-defaultvalue-clear' }, + { + type: 'doc', + label: 'list defaultvalue get', + id: 'cmd/spo/list/list-defaultvalue-get' + }, { type: 'doc', label: 'list defaultvalue list', diff --git a/src/m365/spo/commands.ts b/src/m365/spo/commands.ts index d526308c48c..d8a86657e64 100644 --- a/src/m365/spo/commands.ts +++ b/src/m365/spo/commands.ts @@ -142,6 +142,7 @@ export default { LIST_CONTENTTYPE_REMOVE: `${prefix} list contenttype remove`, LIST_CONTENTTYPE_DEFAULT_SET: `${prefix} list contenttype default set`, LIST_DEFAULTVALUE_CLEAR: `${prefix} list defaultvalue clear`, + LIST_DEFAULTVALUE_GET: `${prefix} list defaultvalue get`, LIST_DEFAULTVALUE_LIST: `${prefix} list defaultvalue list`, LIST_DEFAULTVALUE_REMOVE: `${prefix} list defaultvalue remove`, LIST_DEFAULTVALUE_SET: `${prefix} list defaultvalue set`, diff --git a/src/m365/spo/commands/list/list-defaultvalue-get.spec.ts b/src/m365/spo/commands/list/list-defaultvalue-get.spec.ts new file mode 100644 index 00000000000..628a5353743 --- /dev/null +++ b/src/m365/spo/commands/list/list-defaultvalue-get.spec.ts @@ -0,0 +1,358 @@ +import assert from 'assert'; +import sinon from 'sinon'; +import auth from '../../../../Auth.js'; +import { Logger } from '../../../../cli/Logger.js'; +import request from '../../../../request.js'; +import { telemetry } from '../../../../telemetry.js'; +import { pid } from '../../../../utils/pid.js'; +import { CommandInfo } from '../../../../cli/CommandInfo.js'; +import { session } from '../../../../utils/session.js'; +import { sinonUtil } from '../../../../utils/sinonUtil.js'; +import commands from '../../commands.js'; +import command from './list-defaultvalue-get.js'; +import { z } from 'zod'; +import { cli } from '../../../../cli/cli.js'; +import { formatting } from '../../../../utils/formatting.js'; +import { CommandError } from '../../../../Command.js'; + +describe(commands.LIST_DEFAULTVALUE_GET, () => { + const siteUrl = 'https://contoso.sharepoint.com/sites/marketing'; + const listId = 'c090e594-3b8e-4f4d-9b9f-3e8e1f0b9f1a'; + const listTitle = 'Documents'; + const listUrl = '/sites/marketing/Shared Documents'; + const siteRelListUrl = '/Shared Documents'; + const folderUrl = '/sites/marketing/Shared Documents/Logos'; + const fieldName = 'DocumentType'; + + const defaultColumnXml = ` + + + General + + + Logo + + `; + + const defaultColumnValueRootLibrary = { + fieldName: 'DocumentType', + fieldValue: 'General', + folderUrl: '/sites/Marketing/Shared Documents' + }; + + const defaultColumnValueFolder = { + fieldName: 'DocumentType', + fieldValue: 'Logo', + folderUrl: '/sites/Marketing/Shared Documents/Logos' + }; + + let log: string[]; + let logger: Logger; + let loggerLogSpy: sinon.SinonSpy; + 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(''); + + commandInfo = cli.getCommandInfo(command); + commandOptionsSchema = commandInfo.command.getSchemaToParse()!; + auth.connection.active = true; + }); + + 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.LIST_DEFAULTVALUE_GET); + }); + + it('has a description', () => { + assert.notStrictEqual(command.description, null); + }); + + it('fails validation if webUrl is not a valid URL', async () => { + const actual = commandOptionsSchema.safeParse({ webUrl: 'invalid', listId: listId, fieldName: fieldName }); + assert.strictEqual(actual.success, false); + }); + + it('fails validation if listId is not a valid GUID', async () => { + const actual = commandOptionsSchema.safeParse({ webUrl: siteUrl, listId: 'invalid', fieldName: fieldName }); + assert.strictEqual(actual.success, false); + }); + + it('fails validation if listId, listTitle and listUrl are not specified', async () => { + const actual = commandOptionsSchema.safeParse({ webUrl: siteUrl }); + assert.strictEqual(actual.success, false); + }); + + it('fails validation if listId and listTitle are specified', async () => { + const actual = commandOptionsSchema.safeParse({ webUrl: siteUrl, listId: listId, listTitle: listTitle, fieldName: fieldName }); + assert.strictEqual(actual.success, false); + }); + + it('fails validation if listId and listUrl are specified', async () => { + const actual = commandOptionsSchema.safeParse({ webUrl: siteUrl, listId: listId, listUrl: listUrl, fieldName: fieldName }); + assert.strictEqual(actual.success, false); + }); + + it('fails validation if fieldName is not specified', async () => { + const actual = commandOptionsSchema.safeParse({ webUrl: siteUrl, listId: listId }); + assert.strictEqual(actual.success, false); + }); + + it('succeeds validation if folderUrl is specified', async () => { + const actual = commandOptionsSchema.safeParse({ webUrl: siteUrl, listId: listId, fieldName: fieldName, folderUrl: folderUrl }); + assert.strictEqual(actual.success, true); + }); + + it('succeeds validation if folderUrl is not specified', async () => { + const actual = commandOptionsSchema.safeParse({ webUrl: siteUrl, listId: listId, fieldName: fieldName }); + assert.strictEqual(actual.success, true); + }); + + it('only outputs one single result', async () => { + sinon.stub(request, 'get').callsFake(async (opts) => { + if (opts.url === `${siteUrl}/_api/Web/Lists('${listId}')?$expand=RootFolder&$select=RootFolder/ServerRelativeUrl,BaseTemplate`) { + return { RootFolder: { ServerRelativeUrl: listUrl }, BaseTemplate: 101 }; + } + + if (opts.url === `${siteUrl}/_api/Web/GetFileByServerRelativePath(decodedUrl='${formatting.encodeQueryParameter(listUrl + '/Forms/client_LocationBasedDefaults.html')}')/$value`) { + return defaultColumnXml; + } + + throw 'Invalid request: ' + opts.url; + }); + + await command.action(logger, { options: { webUrl: siteUrl, listId: listId, fieldName: fieldName } }); + assert(loggerLogSpy.calledOnce); + }); + + it('correctly retrieves column default value for the specified field and list by listId', async () => { + sinon.stub(request, 'get').callsFake(async (opts) => { + if (opts.url === `${siteUrl}/_api/Web/Lists('${listId}')?$expand=RootFolder&$select=RootFolder/ServerRelativeUrl,BaseTemplate`) { + return { RootFolder: { ServerRelativeUrl: listUrl }, BaseTemplate: 101 }; + } + + if (opts.url === `${siteUrl}/_api/Web/GetFileByServerRelativePath(decodedUrl='${formatting.encodeQueryParameter(listUrl + '/Forms/client_LocationBasedDefaults.html')}')/$value`) { + return defaultColumnXml; + } + + throw 'Invalid request: ' + opts.url; + }); + + await command.action(logger, { options: { webUrl: siteUrl, listId: listId, fieldName: fieldName, verbose: true } }); + assert.deepStrictEqual(loggerLogSpy.firstCall.args[0], defaultColumnValueRootLibrary); + }); + + it('correctly retrieves column default value for the specified field and list by listTitle', async () => { + sinon.stub(request, 'get').callsFake(async (opts) => { + if (opts.url === `${siteUrl}/_api/Web/Lists/GetByTitle('${listTitle}')?$expand=RootFolder&$select=RootFolder/ServerRelativeUrl,BaseTemplate`) { + return { RootFolder: { ServerRelativeUrl: listUrl }, BaseTemplate: 101 }; + } + + if (opts.url === `${siteUrl}/_api/Web/GetFileByServerRelativePath(decodedUrl='${formatting.encodeQueryParameter(listUrl + '/Forms/client_LocationBasedDefaults.html')}')/$value`) { + return defaultColumnXml; + } + + throw 'Invalid request: ' + opts.url; + }); + + await command.action(logger, { options: { webUrl: siteUrl, listTitle: listTitle, fieldName: fieldName, verbose: true } }); + assert.deepStrictEqual(loggerLogSpy.firstCall.args[0], defaultColumnValueRootLibrary); + }); + + it('correctly retrieves column default value for the specified field and list by listUrl', async () => { + sinon.stub(request, 'get').callsFake(async (opts) => { + if (opts.url === `${siteUrl}/_api/Web/GetList('${listUrl}')?$expand=RootFolder&$select=RootFolder/ServerRelativeUrl,BaseTemplate`) { + return { RootFolder: { ServerRelativeUrl: listUrl }, BaseTemplate: 101 }; + } + + if (opts.url === `${siteUrl}/_api/Web/GetFileByServerRelativePath(decodedUrl='${formatting.encodeQueryParameter(listUrl + '/Forms/client_LocationBasedDefaults.html')}')/$value`) { + return defaultColumnXml; + } + + throw 'Invalid request: ' + opts.url; + }); + + await command.action(logger, { options: { webUrl: siteUrl, listUrl: listUrl, fieldName: fieldName, verbose: true } }); + assert.deepStrictEqual(loggerLogSpy.firstCall.args[0], defaultColumnValueRootLibrary); + }); + + it('correctly retrieves column default value for the specified field and list by listUrl when using a site relative URL', async () => { + sinon.stub(request, 'get').callsFake(async (opts) => { + if (opts.url === `${siteUrl}/_api/Web/GetList('${listUrl}')?$expand=RootFolder&$select=RootFolder/ServerRelativeUrl,BaseTemplate`) { + return { RootFolder: { ServerRelativeUrl: listUrl }, BaseTemplate: 101 }; + } + + if (opts.url === `${siteUrl}/_api/Web/GetFileByServerRelativePath(decodedUrl='${formatting.encodeQueryParameter(listUrl + '/Forms/client_LocationBasedDefaults.html')}')/$value`) { + return defaultColumnXml; + } + + throw 'Invalid request: ' + opts.url; + }); + + await command.action(logger, { options: { webUrl: siteUrl, listUrl: siteRelListUrl, fieldName: fieldName, verbose: true } }); + assert.deepStrictEqual(loggerLogSpy.firstCall.args[0], defaultColumnValueRootLibrary); + }); + + it('correctly filters column default value for the specified folder', async () => { + sinon.stub(request, 'get').callsFake(async (opts) => { + if (opts.url === `${siteUrl}/_api/Web/GetList('${listUrl}')?$expand=RootFolder&$select=RootFolder/ServerRelativeUrl,BaseTemplate`) { + return { RootFolder: { ServerRelativeUrl: listUrl }, BaseTemplate: 101 }; + } + + if (opts.url === `${siteUrl}/_api/Web/GetFileByServerRelativePath(decodedUrl='${formatting.encodeQueryParameter(listUrl + '/Forms/client_LocationBasedDefaults.html')}')/$value`) { + return defaultColumnXml; + } + + throw 'Invalid request: ' + opts.url; + }); + + await command.action(logger, { options: { webUrl: siteUrl, listUrl: siteRelListUrl, fieldName: fieldName, folderUrl: folderUrl } }); + assert.deepStrictEqual(loggerLogSpy.firstCall.args[0], defaultColumnValueFolder); + }); + + it('correctly filters column default value for the specified folder with a site relative URL', async () => { + sinon.stub(request, 'get').callsFake(async (opts) => { + if (opts.url === `${siteUrl}/_api/Web/GetList('${listUrl}')?$expand=RootFolder&$select=RootFolder/ServerRelativeUrl,BaseTemplate`) { + return { RootFolder: { ServerRelativeUrl: listUrl }, BaseTemplate: 101 }; + } + + if (opts.url === `${siteUrl}/_api/Web/GetFileByServerRelativePath(decodedUrl='${formatting.encodeQueryParameter(listUrl + '/Forms/client_LocationBasedDefaults.html')}')/$value`) { + return defaultColumnXml; + } + + throw 'Invalid request: ' + opts.url; + }); + + await command.action(logger, { options: { webUrl: siteUrl, listUrl: siteRelListUrl, fieldName: fieldName, folderUrl: '/Shared Documents/LoGoS' } }); + assert.deepStrictEqual(loggerLogSpy.firstCall.args[0], defaultColumnValueFolder); + }); + + it('correctly logs error when field was not found', async () => { + sinon.stub(request, 'get').callsFake(async (opts) => { + if (opts.url === `${siteUrl}/_api/Web/GetList('${listUrl}')?$expand=RootFolder&$select=RootFolder/ServerRelativeUrl,BaseTemplate`) { + return { + BaseTemplate: 101, + RootFolder: { + ServerRelativeUrl: listUrl + } + }; + } + if (opts.url === `${siteUrl}/_api/Web/GetFileByServerRelativePath(decodedUrl='${formatting.encodeQueryParameter(listUrl + '/Forms/client_LocationBasedDefaults.html')}')/$value`) { + return defaultColumnXml; + } + + throw `Invalid GET request: ${opts.url}`; + }); + + await assert.rejects(command.action(logger, { options: { webUrl: siteUrl, listUrl: listUrl, fieldName: 'NonExistentField' } }), + new CommandError("No default column value found for field 'NonExistentField'.")); + }); + + it('correctly logs error when field default value not set for specified folder', async () => { + sinon.stub(request, 'get').callsFake(async (opts) => { + if (opts.url === `${siteUrl}/_api/Web/GetList('${listUrl}')?$expand=RootFolder&$select=RootFolder/ServerRelativeUrl,BaseTemplate`) { + return { + BaseTemplate: 101, + RootFolder: { + ServerRelativeUrl: listUrl + } + }; + } + if (opts.url === `${siteUrl}/_api/Web/GetFileByServerRelativePath(decodedUrl='${formatting.encodeQueryParameter(listUrl + '/Forms/client_LocationBasedDefaults.html')}')/$value`) { + return defaultColumnXml; + } + + throw `Invalid GET request: ${opts.url}`; + }); + + await assert.rejects(command.action(logger, { options: { webUrl: siteUrl, listUrl: listUrl, fieldName: fieldName, folderUrl: '/sites/marketing/Shared Documents/NonExistentFolder' } }), + new CommandError("No default column value found for field 'DocumentType' in folder '/sites/marketing/Shared Documents/NonExistentFolder'.")); + }); + + it('correctly handles when list has no default values set', async () => { + sinon.stub(request, 'get').callsFake(async (opts) => { + if (opts.url === `${siteUrl}/_api/Web/GetList('${listUrl}')?$expand=RootFolder&$select=RootFolder/ServerRelativeUrl,BaseTemplate`) { + return { RootFolder: { ServerRelativeUrl: listUrl }, BaseTemplate: 101 }; + } + + if (opts.url === `${siteUrl}/_api/Web/GetFileByServerRelativePath(decodedUrl='${formatting.encodeQueryParameter(listUrl + '/Forms/client_LocationBasedDefaults.html')}')/$value`) { + throw { status: 404, error: { 'odata.error': { message: { value: 'The file does not exist.' } } } }; + } + + throw 'Invalid request: ' + opts.url; + }); + + await assert.rejects(command.action(logger, { options: { webUrl: siteUrl, listUrl: listUrl, fieldName: fieldName } }), + new CommandError(`No default column value found for field '${fieldName}'.`)); + }); + + it('correctly handles error when retrieving column list', async () => { + sinon.stub(request, 'get').rejects({ error: { 'odata.error': { message: { value: 'An error has occurred' } } } }); + + await assert.rejects(command.action(logger, { options: { webUrl: siteUrl, listId: listId } }), + new CommandError('An error has occurred')); + }); + + it('correctly handles error when retrieving column default values', async () => { + sinon.stub(request, 'get').callsFake(async (opts) => { + if (opts.url === `${siteUrl}/_api/Web/GetList('${listUrl}')?$expand=RootFolder&$select=RootFolder/ServerRelativeUrl,BaseTemplate`) { + return { RootFolder: { ServerRelativeUrl: listUrl }, BaseTemplate: 101 }; + } + + throw { error: { 'odata.error': { message: { value: 'An error has occurred' } } } }; + }); + + await assert.rejects(command.action(logger, { options: { webUrl: siteUrl, listUrl: listUrl } }), + new CommandError('An error has occurred')); + }); + + it('correctly handles error when list is not found', async () => { + sinon.stub(request, 'get').rejects({ status: 404 }); + + await assert.rejects(command.action(logger, { options: { webUrl: siteUrl, listUrl: listUrl, fieldName: fieldName } }), + new CommandError(`List '${listUrl}' was not found.`)); + }); + + it('fails command when list is not a document library', async () => { + sinon.stub(request, 'get').callsFake(async (opts) => { + if (opts.url === `${siteUrl}/_api/Web/GetList('${listUrl}')?$expand=RootFolder&$select=RootFolder/ServerRelativeUrl,BaseTemplate`) { + return { RootFolder: { ServerRelativeUrl: listUrl }, BaseTemplate: 100 }; + } + + throw 'Invalid request: ' + opts.url; + }); + + await assert.rejects(command.action(logger, { options: { webUrl: siteUrl, listUrl: listUrl, fieldName: fieldName } }), + new CommandError(`List '${listUrl}' is not a document library.`)); + }); +}); \ No newline at end of file diff --git a/src/m365/spo/commands/list/list-defaultvalue-get.ts b/src/m365/spo/commands/list/list-defaultvalue-get.ts new file mode 100644 index 00000000000..22bdc0c6ec3 --- /dev/null +++ b/src/m365/spo/commands/list/list-defaultvalue-get.ts @@ -0,0 +1,185 @@ +import commands from '../../commands.js'; +import { Logger } from '../../../../cli/Logger.js'; +import SpoCommand from '../../../base/SpoCommand.js'; +import { globalOptionsZod } from '../../../../Command.js'; +import { z } from 'zod'; +import { zod } from '../../../../utils/zod.js'; +import { validation } from '../../../../utils/validation.js'; +import { urlUtil } from '../../../../utils/urlUtil.js'; +import { DOMParser } from '@xmldom/xmldom'; +import request, { CliRequestOptions } from '../../../../request.js'; +import { formatting } from '../../../../utils/formatting.js'; + +interface DefaultColumnValue { + fieldName: string; + fieldValue: string; + folderUrl: string +} + +const options = globalOptionsZod + .extend({ + webUrl: zod.alias('u', z.string() + .refine(url => validation.isValidSharePointUrl(url) === true, url => ({ + message: `'${url}' is not a valid SharePoint Online site URL.` + })) + ), + listId: zod.alias('i', z.string().optional() + .refine(id => id === undefined || validation.isValidGuid(id), id => ({ + message: `'${id}' is not a valid GUID.` + })) + ), + listTitle: zod.alias('t', z.string().optional()), + listUrl: z.string().optional(), + fieldName: z.string(), + folderUrl: z.string().optional() + }) + .strict(); +declare type Options = z.infer; + +interface CommandArgs { + options: Options; +} + +class SpoListDefaultValueGetCommand extends SpoCommand { + public get name(): string { + return commands.LIST_DEFAULTVALUE_GET; + } + + public get description(): string { + return 'Gets a specific default column value from a list'; + } + + public get schema(): z.ZodTypeAny { + return options; + } + + public getRefinedSchema(schema: z.ZodTypeAny): z.ZodEffects | undefined { + return schema + .refine(options => [options.listId, options.listTitle, options.listUrl].filter(o => o !== undefined).length === 1, { + message: 'Use one of the following options: listId, listTitle, listUrl.' + }); + } + + public async commandAction(logger: Logger, args: CommandArgs): Promise { + try { + if (this.verbose) { + await logger.logToStderr(`Retrieving default column value for field '${args.options.fieldName}' in list '${args.options.listId || args.options.listTitle || args.options.listUrl}'...`); + await logger.logToStderr('Retrieving list information...'); + } + + const listServerRelUrl = await this.getServerRelativeListUrl(args.options); + if (this.verbose) { + await logger.logToStderr('Retrieving default column values...'); + } + + let defaultValues: DefaultColumnValue[]; + try { + const defaultValuesXml = await this.getDefaultColumnValuesXml(args.options.webUrl, listServerRelUrl); + defaultValues = this.convertXmlToJson(defaultValuesXml); + } + catch (err: any) { + if (err.status !== 404) { + throw err; + } + // For lists that have never had default column values set, the client_LocationBasedDefaults.html file does not exist. + defaultValues = []; + } + defaultValues = defaultValues.filter(d => d.fieldName.toLowerCase() === args.options.fieldName.toLowerCase()); + + if (args.options.folderUrl) { + const serverRelFolderUrl = urlUtil.removeTrailingSlashes(urlUtil.getServerRelativePath(args.options.webUrl, args.options.folderUrl)); + defaultValues = defaultValues.filter(d => d.folderUrl.toLowerCase() === serverRelFolderUrl.toLowerCase()); + } + else { + defaultValues = defaultValues.filter(d => d.folderUrl.toLowerCase() === listServerRelUrl.toLowerCase()); + } + + if (defaultValues.length === 0) { + throw `No default column value found for field '${args.options.fieldName}'${args.options.folderUrl ? ` in folder '${args.options.folderUrl}'` : ''}.`; + } + + await logger.log(defaultValues[0]); + } + catch (err: any) { + this.handleRejectedODataJsonPromise(err); + } + } + + private async getServerRelativeListUrl(options: Options): Promise { + const requestOptions: CliRequestOptions = { + url: `${options.webUrl}/_api/Web`, + headers: { + accept: 'application/json;odata=nometadata' + }, + responseType: 'json' + }; + + if (options.listUrl) { + const serverRelativeUrl = urlUtil.getServerRelativePath(options.webUrl, options.listUrl); + requestOptions.url += `/GetList('${serverRelativeUrl}')`; + } + else if (options.listId) { + requestOptions.url += `/Lists('${options.listId}')`; + } + else if (options.listTitle) { + requestOptions.url += `/Lists/GetByTitle('${formatting.encodeQueryParameter(options.listTitle)}')`; + } + + requestOptions.url += '?$expand=RootFolder&$select=RootFolder/ServerRelativeUrl,BaseTemplate'; + + try { + const response = await request.get<{ RootFolder: { ServerRelativeUrl: string }, BaseTemplate: number }>(requestOptions); + if (response.BaseTemplate !== 101) { + throw `List '${options.listId || options.listTitle || options.listUrl}' is not a document library.`; + } + return response.RootFolder.ServerRelativeUrl; + } + catch (error: any) { + if (error.status === 404) { + throw `List '${options.listId || options.listTitle || options.listUrl}' was not found.`; + } + + throw error; + } + } + + private async getDefaultColumnValuesXml(webUrl: string, listServerRelUrl: string): Promise { + const requestOptions: CliRequestOptions = { + url: `${webUrl}/_api/Web/GetFileByServerRelativePath(decodedUrl='${formatting.encodeQueryParameter(listServerRelUrl + '/Forms/client_LocationBasedDefaults.html')}')/$value`, + headers: { + accept: 'application/json;odata=nometadata' + }, + responseType: 'json' + }; + const defaultValuesXml = await request.get(requestOptions); + return defaultValuesXml; + } + + private convertXmlToJson(xml: string): DefaultColumnValue[] { + const results: DefaultColumnValue[] = []; + + const parser = new DOMParser(); + const doc = parser.parseFromString(xml, 'application/xml'); + + const folderLinks = doc.getElementsByTagName('a'); + for (let i = 0; i < folderLinks.length; i++) { + const folderUrl = folderLinks[i].getAttribute('href')!; + const defaultValues = folderLinks[i].getElementsByTagName('DefaultValue'); + + for (let j = 0; j < defaultValues.length; j++) { + const fieldName = defaultValues[j].getAttribute('FieldName')!; + const fieldValue = defaultValues[j].textContent!; + + results.push({ + fieldName: fieldName, + fieldValue: fieldValue, + folderUrl: decodeURIComponent(folderUrl) + }); + } + } + + return results; + } +} + +export default new SpoListDefaultValueGetCommand(); \ No newline at end of file