diff --git a/.github/workflows/build-catalog.yml b/.github/workflows/build-catalog.yml index c9b03cc3..ef39d041 100644 --- a/.github/workflows/build-catalog.yml +++ b/.github/workflows/build-catalog.yml @@ -26,7 +26,7 @@ on: workflow_dispatch: # Allow maintainers to rebuild/verify the catalog on demand. permissions: - contents: write + contents: read discussions: read jobs: @@ -50,6 +50,8 @@ jobs: publish: if: (github.event_name == 'push' || github.event_name == 'workflow_dispatch') && github.actor != 'github-actions[bot]' runs-on: ubuntu-latest + permissions: + contents: write # Only the publish job may push the rebuilt catalog. steps: - uses: actions/checkout@v4 with: diff --git a/.github/workflows/update-message-center.yml b/.github/workflows/update-message-center.yml index c43e911d..de6b18e9 100644 --- a/.github/workflows/update-message-center.yml +++ b/.github/workflows/update-message-center.yml @@ -10,6 +10,10 @@ permissions: contents: write pull-requests: write +concurrency: + group: update-message-center + cancel-in-progress: false + jobs: update-message-center: # Only run on the canonical upstream repo, not on forks. @@ -17,12 +21,13 @@ jobs: # instead of failing when repo-specific secrets are unavailable. if: github.repository == 'microsoft/FastTrack' runs-on: ubuntu-latest + timeout-minutes: 15 steps: - name: Checkout repository - uses: actions/checkout@v5 + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 - name: Set up Node.js - uses: actions/setup-node@v5 + uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5.0.0 with: node-version: '22' @@ -53,23 +58,15 @@ jobs: git commit -m "chore: weekly message center posts refresh $DATE" git push --set-upstream origin "$BRANCH" - # Create PR if one doesn't already exist + # Create a normal PR for human review if one doesn't already exist. existing_pr=$(gh pr list --head "$BRANCH" --state open --json number --jq '.[0].number // empty') if [ -n "$existing_pr" ]; then echo "PR #$existing_pr already exists for $BRANCH" else gh pr create \ --title "chore: weekly message center posts refresh $DATE" \ - --body "Automated weekly message center data update." \ + --body "Automated weekly message center data update. This PR contains externally derived content and requires human review." \ --head "$BRANCH" \ --base master echo "✅ PR created" fi - - # Enable auto-merge - pr_number=$(gh pr list --head "$BRANCH" --state open --json number --jq '.[0].number') - if [ -n "$pr_number" ]; then - gh pr merge "$pr_number" --auto --squash \ - && echo "✅ Auto-merge enabled for PR #$pr_number" \ - || echo "::warning::Could not enable auto-merge — check repo settings" - fi diff --git a/scripts/Get-RecurringTeamsMeetings/sample-csv-file.csv b/scripts/Get-RecurringTeamsMeetings/sample-csv-file.csv index 176f8a39..f0657111 100644 --- a/scripts/Get-RecurringTeamsMeetings/sample-csv-file.csv +++ b/scripts/Get-RecurringTeamsMeetings/sample-csv-file.csv @@ -1,9 +1,9 @@ UserPrincipalName -CoraT@M365CPI23966391.OnMicrosoft.com -CoreyG@M365CPI23966391.OnMicrosoft.com -DakotaS@M365CPI23966391.OnMicrosoft.com -KaiC@M365CPI23966391.OnMicrosoft.com -HadarC@M365CPI23966391.OnMicrosoft.com -AdilE@M365CPI23966391.OnMicrosoft.com -BillieV@M365CPI23966391.OnMicrosoft.com -admin@m365cpi23966391.onmicrosoft.com \ No newline at end of file +AdeleV@contoso.onmicrosoft.com +AlexW@contoso.onmicrosoft.com +MeganB@contoso.onmicrosoft.com +DiegoS@contoso.onmicrosoft.com +NestorW@contoso.onmicrosoft.com +PradeepG@contoso.onmicrosoft.com +JohannaL@contoso.onmicrosoft.com +admin@contoso.onmicrosoft.com \ No newline at end of file diff --git a/scripts/Get-TeamsUserActivityReport/sample-csv-file.csv b/scripts/Get-TeamsUserActivityReport/sample-csv-file.csv index 176f8a39..f0657111 100644 --- a/scripts/Get-TeamsUserActivityReport/sample-csv-file.csv +++ b/scripts/Get-TeamsUserActivityReport/sample-csv-file.csv @@ -1,9 +1,9 @@ UserPrincipalName -CoraT@M365CPI23966391.OnMicrosoft.com -CoreyG@M365CPI23966391.OnMicrosoft.com -DakotaS@M365CPI23966391.OnMicrosoft.com -KaiC@M365CPI23966391.OnMicrosoft.com -HadarC@M365CPI23966391.OnMicrosoft.com -AdilE@M365CPI23966391.OnMicrosoft.com -BillieV@M365CPI23966391.OnMicrosoft.com -admin@m365cpi23966391.onmicrosoft.com \ No newline at end of file +AdeleV@contoso.onmicrosoft.com +AlexW@contoso.onmicrosoft.com +MeganB@contoso.onmicrosoft.com +DiegoS@contoso.onmicrosoft.com +NestorW@contoso.onmicrosoft.com +PradeepG@contoso.onmicrosoft.com +JohannaL@contoso.onmicrosoft.com +admin@contoso.onmicrosoft.com \ No newline at end of file diff --git a/scripts/Teams Phone User Migration/Contoso_List_Users_TeamsVoicePoliciesAndNumbers.csv b/scripts/Teams Phone User Migration/Contoso_List_Users_TeamsVoicePoliciesAndNumbers.csv index dbe7504f..2cd3eb14 100644 --- a/scripts/Teams Phone User Migration/Contoso_List_Users_TeamsVoicePoliciesAndNumbers.csv +++ b/scripts/Teams Phone User Migration/Contoso_List_Users_TeamsVoicePoliciesAndNumbers.csv @@ -1,9 +1,9 @@ UserPrincipalName,TeamsCallingPolicy,CallingLineIdentity,TeamsMOHPolicy,TeamsCallParkPolicy,TenantDialPlan,TeamsIPPhonePolicy,LocationID,City,OnlineVoiceRoutingPolicy,PhoneNumberToAssign,OperatorType -AllanD@MODERNCOMMS509747.OnMicrosoft.com,TMS_CALL_Recording_PSTN_ON,TMS_CLID_Redmond,TMS_MOH_US_Audio,TMS_CallPark_ON_US_100-300,TMS-DP-US-Redmond,,,Redmond,,+14254190479,CallingPlan -AlexW@MODERNCOMMS509747.OnMicrosoft.com,TMS_CALL_Recording_PSTN_ON,TMS_CLID_Redmond,TMS_MOH_US_Audio,TMS_CallPark_ON_US_100-300,TMS-DP-US-Red,,,,,+14254190485,CallingPlan -BiancaP@ModernComms509747.onmicrosoft.com,TMS_CALL_Recording_PSTN_ON,TMS_CLID_Redmond,TMS_MOH_US_Audio,,TMS-DP-US-Redmond,,701d2c9e-1525-4551-a958-6a9195d35f68,,,+14254190487,CallingPlan -DeliaD@ModernComms509747.onmicrosoft.com,TMS_CALL_Recording_PSTN_ON,TMS_CLID_Redmond,TMS_MOH_US_Audio,,TMS-DP-US-Redmond,,701d2c9e-1525-4551-a958-6a9195d35f68,,,+16784594494,CallingPlan -LeeG@ModernComms509747.onmicrosoft.com,TMS_CALL_Recording_PSTN_ON,,,,TMS-DP-US-Redmond,,,London,,+442045912449,CallingPlan -CAP-US-Redmond-Reception@ModernComms509747.onmicrosoft.com,TMS_CALL_Recording_PSTN_ON,TMS_CLID_Redmond,TMS_MOH_US_Audio,TMS_CallPark_ON_US_100-300,TMS-DP-US-Redmond,TMS_IPPhone_CAP_HotDesk,,Redmond,,+14254190515,CallingPlan -ChristieC@ModernComms509747.onmicrosoft.com,TMS_CALL_Recording_PSTN_ON,TMS_CLID_Redmond,TMS_MOH_US_Audio,TMS_CallPark_ON_US_100-300,TMS-DP-US-Redmond,,,Redmond,VRP-US-Unrestricted,+12345678901,DirectRouting -MegB@ModernComms509747.onmicrosoft.com,TMS_CALL_Recording_PSTN_ON,TMS_CLID_Paris,TMS_MOH_FR_Audio,,TMS-DP-FR-Paris,,,Paris,VRP-FR-Domestic,+33123456789,CallingPlan +AdeleV@contoso.onmicrosoft.com,CONTOSO_CALL_Recording_ON,CONTOSO_CLID_Seattle,CONTOSO_MOH_US_Audio,CONTOSO_CallPark_US_100-300,CONTOSO-DP-US-Seattle,,,Seattle,,+12025550100,CallingPlan +AlexW@contoso.onmicrosoft.com,CONTOSO_CALL_Recording_ON,CONTOSO_CLID_Seattle,CONTOSO_MOH_US_Audio,CONTOSO_CallPark_US_100-300,CONTOSO-DP-US-Seattle,,,,,+12025550101,CallingPlan +MeganB@contoso.onmicrosoft.com,CONTOSO_CALL_Recording_ON,CONTOSO_CLID_Seattle,CONTOSO_MOH_US_Audio,,CONTOSO-DP-US-Seattle,,f9dc9dc2-2752-4b8d-8267-ce0366056b86,,,+12025550102,CallingPlan +DiegoS@contoso.onmicrosoft.com,CONTOSO_CALL_Recording_ON,CONTOSO_CLID_Seattle,CONTOSO_MOH_US_Audio,,CONTOSO-DP-US-Seattle,,3f0198e7-f1f2-43b1-963f-04e7100c02f5,,,+12025550103,CallingPlan +NestorW@contoso.onmicrosoft.com,CONTOSO_CALL_Recording_ON,,,,CONTOSO-DP-US-NewYork,,,New York,,+12025550104,CallingPlan +CAP-US-Seattle-Reception@contoso.onmicrosoft.com,CONTOSO_CALL_Recording_ON,CONTOSO_CLID_Seattle,CONTOSO_MOH_US_Audio,CONTOSO_CallPark_US_100-300,CONTOSO-DP-US-Seattle,CONTOSO_IPPhone_CAP_HotDesk,,Seattle,,+12025550105,CallingPlan +PradeepG@contoso.onmicrosoft.com,CONTOSO_CALL_Recording_ON,CONTOSO_CLID_Seattle,CONTOSO_MOH_US_Audio,CONTOSO_CallPark_US_100-300,CONTOSO-DP-US-Seattle,,,Seattle,CONTOSO-VRP-US-Unrestricted,+12025550106,DirectRouting +JohannaL@contoso.onmicrosoft.com,CONTOSO_CALL_Recording_ON,CONTOSO_CLID_Chicago,CONTOSO_MOH_US_Audio,,CONTOSO-DP-US-Chicago,,,Chicago,CONTOSO-VRP-US-Domestic,+12025550107,CallingPlan diff --git a/scripts/update-message-center.js b/scripts/update-message-center.js index 558d9df4..73694da3 100644 --- a/scripts/update-message-center.js +++ b/scripts/update-message-center.js @@ -65,6 +65,53 @@ function fetchJson(url) { }); } +function sanitiseRawPost(raw) { + if (!raw || typeof raw !== 'object' || Array.isArray(raw)) return null; + + const string = (value, maxLength = 10000) => + typeof value === 'string' ? value.slice(0, maxLength) : ''; + const stringArray = (value, maxItems = 50, maxLength = 200) => + Array.isArray(value) + ? value + .filter((item) => typeof item === 'string') + .slice(0, maxItems) + .map((item) => item.slice(0, maxLength)) + : []; + const dateString = (value) => { + if (typeof value !== 'string' || !Number.isFinite(Date.parse(value))) return null; + return value; + }; + + const post = { + Id: string(raw.Id, 100), + Title: string(raw.Title, 500), + Category: string(raw.Category, 100), + Severity: string(raw.Severity, 50), + StartDateTime: dateString(raw.StartDateTime), + ActionRequiredByDateTime: dateString(raw.ActionRequiredByDateTime), + Services: stringArray(raw.Services), + Tags: stringArray(raw.Tags), + Details: Array.isArray(raw.Details) + ? raw.Details + .filter((detail) => detail && typeof detail === 'object') + .slice(0, 50) + .map((detail) => ({ + Name: string(detail.Name, 100), + Value: string(detail.Value), + })) + : [], + Body: { + Content: + raw.Body && typeof raw.Body === 'object' + ? string(raw.Body.Content, 50000) + : '', + }, + IsMajorChange: raw.IsMajorChange === true, + }; + + return post.Id && post.Title && post.StartDateTime ? post : null; +} + // ── category mapping ───────────────────────────────────────────────────────── // merill/mc uses camelCase categories; we map to display names. @@ -424,31 +471,28 @@ function isCopilotAgentRelevant(raw) { // ── serialise a post to inline JS ──────────────────────────────────────────── function serialisePost(p) { - const esc = (s) => s.replace(/\\/g, '\\\\').replace(/'/g, "\\'").replace(/\r?\n/g, ' ').replace(/\s{2,}/g, ' '); - const arr = (a) => '[' + a.map((v) => `'${esc(v)}'`).join(', ') + ']'; - const parts = [ - `id: '${p.id}'`, - `title: '${esc(p.title)}'`, - `category: '${p.category}'`, - `severity: '${p.severity}'`, - `datePublished: '${p.datePublished}'`, - `actionRequiredBy: ${p.actionRequiredBy ? `'${p.actionRequiredBy}'` : 'null'}`, - `status: '${p.status}'`, - `services: ${arr(p.services)}`, - `summary: '${esc(p.summary)}'`, - `tags: ${arr(p.tags)}`, - `isHighImpact: ${p.isHighImpact}`, - `agentAudience: ${arr(p.agentAudience)}`, - ]; - return ` { ${parts.join(', ')} }`; + return ( + ' ' + + JSON.stringify(p) + .replace(//g, '\\u003e') + .replace(/&/g, '\\u0026') + .replace(/\u2028/g, '\\u2028') + .replace(/\u2029/g, '\\u2029') + ); } // ── main ───────────────────────────────────────────────────────────────────── async function main() { console.log('⏳ Fetching message center posts from merill/mc...'); - const rawPosts = await fetchJson(MC_DATA_URL); - console.log(` Fetched ${rawPosts.length} total posts from archive`); + const fetchedPosts = await fetchJson(MC_DATA_URL); + if (!Array.isArray(fetchedPosts)) { + throw new Error('Message Center archive response must be an array'); + } + const rawPosts = fetchedPosts.map(sanitiseRawPost).filter(Boolean); + console.log(` Fetched ${fetchedPosts.length} total posts from archive`); + console.log(` ${rawPosts.length} posts passed schema validation`); const today = new Date(); const windowStart = addDays(today, -60); @@ -548,4 +592,3 @@ main().catch((err) => { console.error('❌ Failed to update message center posts:', err.message); process.exit(1); }); -