From d5730758f82970aad5c706e1a0f467365025dc3a Mon Sep 17 00:00:00 2001 From: Mathijs Verbeeck Date: Mon, 13 Oct 2025 16:01:27 +0200 Subject: [PATCH] Adds `spo site alert get` command. Closes #6862 --- .devproxy/api-specs/sharepoint.yaml | 29 +++ docs/docs/cmd/spo/site/site-alert-get.mdx | 189 +++++++++++++++ docs/src/config/sidebars.ts | 5 + src/m365/spo/commands.ts | 1 + .../spo/commands/site/site-alert-get.spec.ts | 228 ++++++++++++++++++ src/m365/spo/commands/site/site-alert-get.ts | 59 +++++ 6 files changed, 511 insertions(+) create mode 100644 docs/docs/cmd/spo/site/site-alert-get.mdx create mode 100644 src/m365/spo/commands/site/site-alert-get.spec.ts create mode 100644 src/m365/spo/commands/site/site-alert-get.ts diff --git a/.devproxy/api-specs/sharepoint.yaml b/.devproxy/api-specs/sharepoint.yaml index 6393d2cd60a..fcebee03bd2 100644 --- a/.devproxy/api-specs/sharepoint.yaml +++ b/.devproxy/api-specs/sharepoint.yaml @@ -90,6 +90,35 @@ paths: responses: 200: description: OK + /_api/web/alerts/GetById({alertId}): + get: + parameters: + - name: alertId + in: path + required: true + schema: + type: string + example: "'7cbb4c8d-8e4d-4d2e-9c6f-3f1d8b2e6a0e'" + - name: $expand + in: query + required: false + schema: + type: string + example: "List,User,List/Rootfolder,Item" + - name: $select + in: query + required: false + schema: + type: string + example: "*,List/Id,List/Title,List/Rootfolder/ServerRelativeUrl,Item/ID,Item/FileRef,Item/Guid" + security: + - delegated: + - AllSites.FullControl + - application: + - Sites.FullControl.All + responses: + 200: + description: OK /_api/web/alerts: get: parameters: diff --git a/docs/docs/cmd/spo/site/site-alert-get.mdx b/docs/docs/cmd/spo/site/site-alert-get.mdx new file mode 100644 index 00000000000..f6cbe5cd80b --- /dev/null +++ b/docs/docs/cmd/spo/site/site-alert-get.mdx @@ -0,0 +1,189 @@ +import Global from '/docs/cmd/_global.mdx'; +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + +# spo site alert get + +Retrieves details of a specific alert from a SharePoint list + +## Usage + +```sh +m365 spo site alert get [options] +``` + +## Options + +```md definition-list +`-u, --webUrl ` +: The URL of the SharePoint site + +`--id ` +: The ID of the alert +``` + + + +## Permissions + + + + + | Resource | Permissions | + |------------|----------------------| + | SharePoint | AllSites.FullControl | + + + + + | Resource | Permissions | + |------------|-----------------------| + | SharePoint | Sites.FullControl.All | + + + + +## Examples + +Get details of an alert by ID + +```sh +m365 spo site alert get --webUrl https://contoso.sharepoint.com/sites/Marketing --id 7cbb4c8d-8e4d-4d2e-9c6f-3f1d8b2e6a0e +``` + +## Response + + + + + ```json + { + "AlertFrequency": 0, + "AlertTemplateName": "SPAlertTemplateType.DocumentLibrary", + "AlertType": 0, + "AlwaysNotify": false, + "DeliveryChannels": 1, + "EventType": -1, + "Filter": "", + "ID": "a188ee89-72e2-4327-9802-8d0c408ec129", + "List": { + "Id": "1ec04825-b082-46f8-9a1c-b6b54d83bc46", + "RootFolder": { + "ServerRelativeUrl": "/sites/Marketing/Documents" + }, + "Title": "Documents" + }, + "Properties": [ + { + "Key": "dispformurl", + "Value": "Documents/Forms/DispForm.aspx", + "ValueType": "Edm.String" + }, + { + "Key": "filterindex", + "Value": "0", + "ValueType": "Edm.String" + }, + { + "Key": "defaultitemopen", + "Value": "Browser", + "ValueType": "Edm.String" + }, + { + "Key": "sendurlinsms", + "Value": "False", + "ValueType": "Edm.String" + }, + { + "Key": "mobileurl", + "Value": "https://contoso.sharepoint.com/_layouts/15/mobile/", + "ValueType": "Edm.String" + }, + { + "Key": "eventtypeindex", + "Value": "0", + "ValueType": "Edm.String" + }, + { + "Key": "siteurl", + "Value": "https://contoso.sharepoint.com", + "ValueType": "Edm.String" + } + ], + "Status": 0, + "Title": "Documents", + "User": { + "Email": "admin@contoso.onmicrosoft.com", + "Expiration": "", + "Id": 10, + "IsEmailAuthenticationGuestUser": false, + "IsHiddenInUI": false, + "IsShareByEmailGuestUser": false, + "IsSiteAdmin": true, + "LoginName": "i:0#.f|membership|admin@contoso.onmicrosoft.com", + "PrincipalType": 1, + "Title": "Admin User", + "UserId": { + "NameId": "100320009d8267fc", + "NameIdIssuer": "urn:federation:microsoftonline" + }, + "UserPrincipalName": "admin@contoso.onmicrosoft.com" + }, + "UserId": 10 + } + ``` + + + + + ```text + AlertFrequency : 0 + AlertTemplateName: SPAlertTemplateType.DocumentLibrary + AlertType : 0 + AlwaysNotify : false + DeliveryChannels : 1 + EventType : -1 + Filter : + ID : a188ee89-72e2-4327-9802-8d0c408ec129 + List : {"RootFolder":{"ServerRelativeUrl":"/FlowTest"},"Id":"1ec04825-b082-46f8-9a1c-b6b54d83bc46","Title":"FlowTest"} + Properties : [{"Key":"dispformurl","Value":"FlowTest/Forms/DispForm.aspx","ValueType":"Edm.String"},{"Key":"filterindex","Value":"0","ValueType":"Edm.String"},{"Key":"defaultitemopen","Value":"Browser","ValueType":"Edm.String"},{"Key":"sendurlinsms","Value":"False","ValueType":"Edm.String"},{"Key":"mobileurl","Value":"https://contoso.sharepoint.com/_layouts/15/mobile/","ValueType":"Edm.String"},{"Key":"eventtypeindex","Value":"0","ValueType":"Edm.String"},{"Key":"siteurl","Value":"https://contoso.sharepoint.com","ValueType":"Edm.String"}] + Status : 0 + Title : FlowTest + User : {"Id":10,"IsHiddenInUI":false,"LoginName":"i:0#.f|membership|admin@contoso.onmicrosoft.com","Title":"Admin","PrincipalType":1,"Email":"admin@contoso.onmicrosoft.com","Expiration":"","IsEmailAuthenticationGuestUser":false,"IsShareByEmailGuestUser":false,"IsSiteAdmin":true,"UserId":{"NameId":"100320009d8267fc","NameIdIssuer":"urn:federation:microsoftonline"},"UserPrincipalName":"admin@contoso.onmicrosoft.com"} + UserId : 10 + ``` + + + + + ```csv + AlertFrequency,AlertTemplateName,AlertType,AlwaysNotify,DeliveryChannels,EventType,Filter,ID,Status,Title,UserId + 0,SPAlertTemplateType.DocumentLibrary,0,false,1,-1,,a188ee89-72e2-4327-9802-8d0c408ec129,0,Documents,10 + ``` + + + + + ```md + # spo site alert get --webUrl "https://contoso.sharepoint.com/sites/Marketing" --id "7cbb4c8d-8e4d-4d2e-9c6f-3f1d8b2e6a0e" + + Date: 10/1/2025 + + Property | Value + ---------|------- + AlertFrequency | 0 + AlertTemplateName | SPAlertTemplateType.DocumentLibrary + AlertType | 0 + AlwaysNotify | false + DeliveryChannels | 1 + EventType | -1 + Filter | + ID | a188ee89-72e2-4327-9802-8d0c408ec129 + Status | 0 + Title | Documents + UserId | 10 + ``` + + + + diff --git a/docs/src/config/sidebars.ts b/docs/src/config/sidebars.ts index b6c437fbe6a..c1aff0f405c 100644 --- a/docs/src/config/sidebars.ts +++ b/docs/src/config/sidebars.ts @@ -3704,6 +3704,11 @@ const sidebars: SidebarsConfig = { label: 'site admin remove', id: 'cmd/spo/site/site-admin-remove' }, + { + type: 'doc', + label: 'site alert get', + id: 'cmd/spo/site/site-alert-get' + }, { type: 'doc', label: 'site alert list', diff --git a/src/m365/spo/commands.ts b/src/m365/spo/commands.ts index c7497aa11ec..08e78c752b2 100644 --- a/src/m365/spo/commands.ts +++ b/src/m365/spo/commands.ts @@ -258,6 +258,7 @@ export default { SITE_ADMIN_ADD: `${prefix} site admin add`, SITE_ADMIN_LIST: `${prefix} site admin list`, SITE_ADMIN_REMOVE: `${prefix} site admin remove`, + SITE_ALERT_GET: `${prefix} site alert get`, SITE_ALERT_LIST: `${prefix} site alert list`, SITE_APPCATALOG_ADD: `${prefix} site appcatalog add`, SITE_APPCATALOG_LIST: `${prefix} site appcatalog list`, diff --git a/src/m365/spo/commands/site/site-alert-get.spec.ts b/src/m365/spo/commands/site/site-alert-get.spec.ts new file mode 100644 index 00000000000..67497e46619 --- /dev/null +++ b/src/m365/spo/commands/site/site-alert-get.spec.ts @@ -0,0 +1,228 @@ +import assert from 'assert'; +import sinon from 'sinon'; +import { z } from 'zod'; +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 './site-alert-get.js'; + +describe(commands.SITE_ALERT_GET, () => { + const webUrl = 'https://contoso.sharepoint.com/sites/Marketing'; + const alertId = '7cbb4c8d-8e4d-4d2e-9c6f-3f1d8b2e6a0e'; + const alertResponse = { + AlertFrequency: 0, + AlertTemplateName: 'SPAlertTemplateType.DocumentLibrary', + AlertType: 0, + AlwaysNotify: false, + DeliveryChannels: 1, + EventType: -1, + Filter: '', + ID: 'a188ee89-72e2-4327-9802-8d0c408ec129', + List: { + Id: '1ec04825-b082-46f8-9a1c-b6b54d83bc46', + RootFolder: { + ServerRelativeUrl: '/sites/Marketing/Documents' + }, + Title: 'Documents' + }, + Properties: [ + { + Key: 'dispformurl', + Value: 'Documents/Forms/DispForm.aspx', + ValueType: 'Edm.String' + }, + { + Key: 'filterindex', + Value: '0', + ValueType: 'Edm.String' + }, + { + Key: 'defaultitemopen', + Value: 'Browser', + ValueType: 'Edm.String' + }, + { + Key: 'sendurlinsms', + Value: 'False', + ValueType: 'Edm.String' + }, + { + Key: 'mobileurl', + Value: 'https://contoso.sharepoint.com/_layouts/15/mobile/', + ValueType: 'Edm.String' + }, + { + Key: 'eventtypeindex', + Value: '0', + ValueType: 'Edm.String' + }, + { + Key: 'siteurl', + Value: 'https://contoso.sharepoint.com', + ValueType: 'Edm.String' + } + ], + Status: 0, + Title: 'Documents', + User: { + Email: 'admin@contoso.onmicrosoft.com', + Expiration: '', + Id: 10, + IsEmailAuthenticationGuestUser: false, + IsHiddenInUI: false, + IsShareByEmailGuestUser: false, + IsSiteAdmin: true, + LoginName: 'i:0#.f|membership|admin@contoso.onmicrosoft.com', + PrincipalType: 1, + Title: 'Admin User', + UserId: { + NameId: '100320009d8267fc', + NameIdIssuer: 'urn:federation:microsoftonline' + }, + UserPrincipalName: 'admin@contoso.onmicrosoft.com' + }, + UserId: 10 + }; + + let log: any[]; + 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(''); + 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); + } + }; + 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.startsWith(commands.SITE_ALERT_GET), true); + }); + + it('has a description', () => { + assert.notStrictEqual(command.description, null); + }); + + it('retrieves alert details by ID', async () => { + sinon.stub(request, 'get').callsFake(async (opts) => { + if (opts.url === `${webUrl}/_api/Web/Alerts/GetById('${alertId}')?$expand=List,User,List/Rootfolder&$select=*,List/Id,List/Title,List/Rootfolder/ServerRelativeUrl`) { + return alertResponse; + } + + throw 'Invalid request'; + }); + + await command.action(logger, { + options: { + webUrl: webUrl, + id: alertId + } + }); + assert(loggerLogSpy.calledWith(alertResponse)); + }); + + it('correctly handles error when alert does not exist', async () => { + const error = { + error: { + 'odata.error': { + code: '-2146232832, Microsoft.SharePoint.SPException', + message: { + lang: 'en-US', + value: 'The alert you are trying to access does not exist or has just been deleted. ' + } + } + } + }; + + sinon.stub(request, 'get').callsFake(async (opts) => { + if (opts.url === `${webUrl}/_api/Web/Alerts/GetById('${alertId}')?$expand=List,User,List/Rootfolder&$select=*,List/Id,List/Title,List/Rootfolder/ServerRelativeUrl`) { + throw error; + } + + throw 'Invalid request'; + }); + + await assert.rejects( + command.action(logger, { + options: { + webUrl: webUrl, + id: alertId + } + } as any), + new CommandError(error.error['odata.error'].message.value) + ); + }); + + it('fails validation if the webUrl option is not a valid SharePoint site URL', async () => { + const actual = commandOptionsSchema.safeParse({ + webUrl: 'foo', + id: alertId + }); + assert.strictEqual(actual.success, false); + }); + + it('passes validation if the webUrl option is a valid SharePoint site URL', async () => { + const actual = commandOptionsSchema.safeParse({ + webUrl: webUrl, + id: alertId + }); + assert.strictEqual(actual.success, true); + }); + + it('fails validation if the id option is not a valid GUID', async () => { + const actual = commandOptionsSchema.safeParse({ + webUrl: webUrl, + id: 'invalid-guid' + }); + assert.strictEqual(actual.success, false); + }); + + it('passes validation if the id option is a valid GUID', async () => { + const actual = commandOptionsSchema.safeParse({ + webUrl: webUrl, + id: alertId + }); + assert.strictEqual(actual.success, true); + }); +}); + diff --git a/src/m365/spo/commands/site/site-alert-get.ts b/src/m365/spo/commands/site/site-alert-get.ts new file mode 100644 index 00000000000..14e8a41ef85 --- /dev/null +++ b/src/m365/spo/commands/site/site-alert-get.ts @@ -0,0 +1,59 @@ +import { z } from 'zod'; +import { Logger } from '../../../../cli/Logger.js'; +import { globalOptionsZod } from '../../../../Command.js'; +import request, { CliRequestOptions } from '../../../../request.js'; +import { validation } from '../../../../utils/validation.js'; +import { zod } from '../../../../utils/zod.js'; +import SpoCommand from '../../../base/SpoCommand.js'; +import commands from '../../commands.js'; + +const options = globalOptionsZod + .extend({ + webUrl: zod.alias('u', z.string().refine(url => validation.isValidSharePointUrl(url) === true, { + message: 'Specify a valid SharePoint site URL' + })), + id: z.string().refine(id => validation.isValidGuid(id) === true, { + message: 'Specify a valid GUID' + }) + }) + .strict(); +declare type Options = z.infer; + +interface CommandArgs { + options: Options; +} + +class SpoSiteAlertGetCommand extends SpoCommand { + public get name(): string { + return commands.SITE_ALERT_GET; + } + + public get description(): string { + return 'Retrieves details of a specific alert from a SharePoint site list'; + } + + public get schema(): z.ZodTypeAny | undefined { + return options; + } + + public async commandAction(logger: Logger, args: CommandArgs): Promise { + const requestOptions: CliRequestOptions = { + url: `${args.options.webUrl}/_api/Web/Alerts/GetById('${args.options.id}')?$expand=List,User,List/Rootfolder&$select=*,List/Id,List/Title,List/Rootfolder/ServerRelativeUrl`, + headers: { + accept: 'application/json;odata=nometadata' + }, + responseType: 'json' + }; + + try { + const res = await request.get(requestOptions); + await logger.log(res); + } + catch (err: any) { + this.handleRejectedODataJsonPromise(err); + } + } +} + +export default new SpoSiteAlertGetCommand(); +