Skip to content

Commit dd0e8a1

Browse files
add review workflow (#3387)
* feat(content): add ready for review workflow * ci(workflow): Add Slack notification workflow for blog articles
1 parent 584f32f commit dd0e8a1

File tree

8 files changed

+451
-92
lines changed

8 files changed

+451
-92
lines changed

.github/workflows/blog-grammar-check.yml

Lines changed: 0 additions & 91 deletions
Original file line numberDiff line numberDiff line change
@@ -50,52 +50,6 @@ jobs:
5050
run: |
5151
node .github/scripts/grammar-check.mjs
5252
53-
- name: Extract article info
54-
if: steps.changed-files.outputs.has_files == 'true'
55-
id: article-info
56-
run: |
57-
FILE=$(echo "${{ steps.changed-files.outputs.files }}" | awk '{print $1}')
58-
if [ -f "$FILE" ]; then
59-
# Extract display_title or fall back to meta_title (handles quoted YAML values)
60-
# Use jq to properly escape for JSON
61-
RAW_TITLE=$(grep -m1 "^display_title:" "$FILE" | sed 's/display_title:[[:space:]]*"\(.*\)"/\1/' || echo "")
62-
if [ -z "$RAW_TITLE" ]; then
63-
RAW_TITLE=$(grep -m1 "^meta_title:" "$FILE" | sed 's/meta_title:[[:space:]]*"\(.*\)"/\1/' || echo "Untitled")
64-
fi
65-
RAW_AUTHOR=$(grep -m1 "^author:" "$FILE" | sed 's/author:[[:space:]]*"\(.*\)"/\1/' || echo "Unknown")
66-
RAW_DATE=$(grep -m1 "^date:" "$FILE" | sed 's/date:[[:space:]]*"\(.*\)"/\1/' || echo "")
67-
68-
# Escape for JSON (remove surrounding quotes that jq adds)
69-
TITLE=$(echo "$RAW_TITLE" | jq -Rs . | sed 's/^"//;s/"$//')
70-
AUTHOR=$(echo "$RAW_AUTHOR" | jq -Rs . | sed 's/^"//;s/"$//')
71-
DATE=$(echo "$RAW_DATE" | jq -Rs . | sed 's/^"//;s/"$//')
72-
else
73-
TITLE="Untitled"
74-
AUTHOR="Unknown"
75-
DATE=""
76-
fi
77-
echo "title=$TITLE" >> $GITHUB_OUTPUT
78-
echo "author=$AUTHOR" >> $GITHUB_OUTPUT
79-
echo "date=$DATE" >> $GITHUB_OUTPUT
80-
81-
# Determine if this is a new article or edit (edit branches have timestamp suffix)
82-
BRANCH="${{ github.head_ref }}"
83-
if [[ "$BRANCH" =~ -[0-9]+$ ]]; then
84-
echo "is_edit=true" >> $GITHUB_OUTPUT
85-
else
86-
echo "is_edit=false" >> $GITHUB_OUTPUT
87-
fi
88-
89-
# Map GitHub username to Slack user ID
90-
GH_USER="${{ github.event.pull_request.user.login }}"
91-
case "$GH_USER" in
92-
"yujonglee") SLACK_USER="<@U0628P6TAPL>" ;;
93-
"ComputelessComputer") SLACK_USER="<@U08PVBSGL31>" ;;
94-
"harshikaalagh-netizen") SLACK_USER="<@U0976J9CAKF>" ;;
95-
*) SLACK_USER="$GH_USER" ;;
96-
esac
97-
echo "slack_user=$SLACK_USER" >> $GITHUB_OUTPUT
98-
9953
- name: Post PR comment
10054
if: steps.changed-files.outputs.has_files == 'true'
10155
uses: actions/github-script@v7
@@ -132,48 +86,3 @@ jobs:
13286
body: body
13387
});
13488
}
135-
136-
- name: Notify Slack
137-
if: steps.changed-files.outputs.has_files == 'true'
138-
uses: slackapi/slack-github-action@v2.0.0
139-
with:
140-
method: chat.postMessage
141-
token: ${{ secrets.SLACK_BOT_TOKEN }}
142-
payload: |
143-
{
144-
"channel": "${{ secrets.SLACK_BLOG_CHANNEL_ID }}",
145-
"text": "${{ steps.article-info.outputs.is_edit == 'true' && format('{0} made changes to {1}', steps.article-info.outputs.slack_user, steps.article-info.outputs.title) || format('{0} is ready to publish: {1}', steps.article-info.outputs.slack_user, steps.article-info.outputs.title) }}",
146-
"blocks": [
147-
{
148-
"type": "section",
149-
"text": {
150-
"type": "mrkdwn",
151-
"text": "${{ steps.article-info.outputs.is_edit == 'true' && format('✏️ {0} made changes to *{1}*', steps.article-info.outputs.slack_user, steps.article-info.outputs.title) || format('🚀 {0} is ready to publish a new article: *{1}*', steps.article-info.outputs.slack_user, steps.article-info.outputs.title) }}"
152-
}
153-
},
154-
{
155-
"type": "context",
156-
"elements": [
157-
{
158-
"type": "mrkdwn",
159-
"text": "👤 ${{ steps.article-info.outputs.author }}${{ steps.article-info.outputs.date != '' && format(' • 📅 {0}', steps.article-info.outputs.date) || '' }}"
160-
}
161-
]
162-
},
163-
{
164-
"type": "actions",
165-
"elements": [
166-
{
167-
"type": "button",
168-
"text": {
169-
"type": "plain_text",
170-
"text": "View PR",
171-
"emoji": true
172-
},
173-
"url": "${{ github.event.pull_request.html_url }}",
174-
"style": "primary"
175-
}
176-
]
177-
}
178-
]
179-
}
Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
name: Blog Slack Notifications
2+
3+
on:
4+
pull_request:
5+
branches:
6+
- main
7+
paths:
8+
- "apps/web/content/articles/**"
9+
10+
jobs:
11+
notify:
12+
if: startsWith(github.head_ref, 'blog/')
13+
runs-on: ubuntu-latest
14+
permissions:
15+
contents: read
16+
17+
steps:
18+
- name: Checkout code
19+
uses: actions/checkout@v4
20+
with:
21+
fetch-depth: 0
22+
23+
- name: Get changed files
24+
id: changed-files
25+
run: |
26+
FILES=$(git diff --name-only origin/${{ github.base_ref }}...HEAD -- 'apps/web/content/articles/*.mdx' | tr '\n' ' ')
27+
echo "files=$FILES" >> $GITHUB_OUTPUT
28+
if [ -z "$FILES" ]; then
29+
echo "has_files=false" >> $GITHUB_OUTPUT
30+
else
31+
echo "has_files=true" >> $GITHUB_OUTPUT
32+
fi
33+
34+
- name: Extract article info
35+
if: steps.changed-files.outputs.has_files == 'true'
36+
id: article-info
37+
run: |
38+
FILE=$(echo "${{ steps.changed-files.outputs.files }}" | awk '{print $1}')
39+
if [ -f "$FILE" ]; then
40+
RAW_TITLE=$(grep -m1 "^display_title:" "$FILE" | sed 's/display_title:[[:space:]]*"\(.*\)"/\1/' || echo "")
41+
if [ -z "$RAW_TITLE" ]; then
42+
RAW_TITLE=$(grep -m1 "^meta_title:" "$FILE" | sed 's/meta_title:[[:space:]]*"\(.*\)"/\1/' || echo "Untitled")
43+
fi
44+
RAW_AUTHOR=$(grep -m1 "^author:" "$FILE" | sed 's/author:[[:space:]]*"\(.*\)"/\1/' || echo "Unknown")
45+
RAW_DATE=$(grep -m1 "^date:" "$FILE" | sed 's/date:[[:space:]]*"\(.*\)"/\1/' || echo "")
46+
47+
TITLE=$(echo "$RAW_TITLE" | jq -Rs . | sed 's/^"//;s/"$//')
48+
AUTHOR=$(echo "$RAW_AUTHOR" | jq -Rs . | sed 's/^"//;s/"$//')
49+
DATE=$(echo "$RAW_DATE" | jq -Rs . | sed 's/^"//;s/"$//')
50+
else
51+
TITLE="Untitled"
52+
AUTHOR="Unknown"
53+
DATE=""
54+
fi
55+
echo "title=$TITLE" >> $GITHUB_OUTPUT
56+
echo "author=$AUTHOR" >> $GITHUB_OUTPUT
57+
echo "date=$DATE" >> $GITHUB_OUTPUT
58+
59+
# Determine if this is a new article or edit (edit branches have timestamp suffix)
60+
BRANCH="${{ github.head_ref }}"
61+
if [[ "$BRANCH" =~ -[0-9]+$ ]]; then
62+
echo "is_edit=true" >> $GITHUB_OUTPUT
63+
else
64+
echo "is_edit=false" >> $GITHUB_OUTPUT
65+
fi
66+
67+
# Map GitHub username to Slack user ID
68+
GH_USER="${{ github.event.pull_request.user.login }}"
69+
case "$GH_USER" in
70+
"yujonglee") SLACK_USER="<@U0628P6TAPL>" ;;
71+
"ComputelessComputer") SLACK_USER="<@U08PVBSGL31>" ;;
72+
"harshikaalagh-netizen") SLACK_USER="<@U0976J9CAKF>" ;;
73+
*) SLACK_USER="$GH_USER" ;;
74+
esac
75+
echo "slack_user=$SLACK_USER" >> $GITHUB_OUTPUT
76+
77+
- name: Check ready_for_review status
78+
if: steps.changed-files.outputs.has_files == 'true'
79+
id: review-status
80+
run: |
81+
FILE=$(echo "${{ steps.changed-files.outputs.files }}" | awk '{print $1}')
82+
if [ -f "$FILE" ]; then
83+
READY_FOR_REVIEW=$(grep -m1 "^ready_for_review:" "$FILE" | sed 's/ready_for_review:[[:space:]]*//' || echo "false")
84+
echo "ready_for_review=$READY_FOR_REVIEW" >> $GITHUB_OUTPUT
85+
else
86+
echo "ready_for_review=false" >> $GITHUB_OUTPUT
87+
fi
88+
89+
- name: Notify Slack
90+
if: steps.changed-files.outputs.has_files == 'true'
91+
uses: slackapi/slack-github-action@v2.0.0
92+
with:
93+
method: chat.postMessage
94+
token: ${{ secrets.SLACK_BOT_TOKEN }}
95+
payload: |
96+
{
97+
"channel": "${{ secrets.SLACK_BLOG_CHANNEL_ID }}",
98+
"text": "${{ steps.review-status.outputs.ready_for_review == 'true' && format('Article submitted for review: {0}', steps.article-info.outputs.title) || (steps.article-info.outputs.is_edit == 'true' && format('{0} made changes to {1}', steps.article-info.outputs.slack_user, steps.article-info.outputs.title) || format('{0} is ready to publish: {1}', steps.article-info.outputs.slack_user, steps.article-info.outputs.title)) }}",
99+
"attachments": [
100+
{
101+
"color": "${{ steps.review-status.outputs.ready_for_review == 'true' && '#3b82f6' || '#2eb886' }}",
102+
"blocks": [
103+
{
104+
"type": "section",
105+
"text": {
106+
"type": "mrkdwn",
107+
"text": "${{ steps.review-status.outputs.ready_for_review == 'true' && format('👀 *Article submitted for review*\n<@U08PVBSGL31> please review\n\n>*{0}*', steps.article-info.outputs.title) || (steps.article-info.outputs.is_edit == 'true' && format('✏️ {0} made changes to *{1}*', steps.article-info.outputs.slack_user, steps.article-info.outputs.title) || format('🚀 {0} is ready to publish a new article: *{1}*', steps.article-info.outputs.slack_user, steps.article-info.outputs.title)) }}"
108+
}
109+
},
110+
{
111+
"type": "context",
112+
"elements": [
113+
{
114+
"type": "mrkdwn",
115+
"text": "👤 ${{ steps.article-info.outputs.author }}${{ steps.article-info.outputs.date != '' && format(' • 📅 {0}', steps.article-info.outputs.date) || '' }}"
116+
}
117+
]
118+
},
119+
{
120+
"type": "actions",
121+
"elements": [
122+
{
123+
"type": "button",
124+
"text": {
125+
"type": "plain_text",
126+
"text": "View PR",
127+
"emoji": true
128+
},
129+
"url": "${{ github.event.pull_request.html_url }}",
130+
"style": "primary"
131+
}
132+
]
133+
}
134+
]
135+
}
136+
]
137+
}

apps/web/content-collections.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,7 @@ const articles = defineCollection({
9191
coverImage: z.string().optional(),
9292
featured: z.boolean().optional(),
9393
published: z.boolean().default(false),
94+
ready_for_review: z.boolean().default(false),
9495
category: z
9596
.enum([
9697
"Case Study",

apps/web/src/routeTree.gen.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,7 @@ import { Route as ApiAdminMediaDeleteRouteImport } from './routes/api/admin/medi
115115
import { Route as ApiAdminMediaCreateFolderRouteImport } from './routes/api/admin/media/create-folder'
116116
import { Route as ApiAdminImportSaveRouteImport } from './routes/api/admin/import/save'
117117
import { Route as ApiAdminImportGoogleDocsRouteImport } from './routes/api/admin/import/google-docs'
118+
import { Route as ApiAdminContentSubmitForReviewRouteImport } from './routes/api/admin/content/submit-for-review'
118119
import { Route as ApiAdminContentSaveRouteImport } from './routes/api/admin/content/save'
119120
import { Route as ApiAdminContentRenameRouteImport } from './routes/api/admin/content/rename'
120121
import { Route as ApiAdminContentPublishRouteImport } from './routes/api/admin/content/publish'
@@ -670,6 +671,12 @@ const ApiAdminImportGoogleDocsRoute =
670671
path: '/api/admin/import/google-docs',
671672
getParentRoute: () => rootRouteImport,
672673
} as any)
674+
const ApiAdminContentSubmitForReviewRoute =
675+
ApiAdminContentSubmitForReviewRouteImport.update({
676+
id: '/api/admin/content/submit-for-review',
677+
path: '/api/admin/content/submit-for-review',
678+
getParentRoute: () => rootRouteImport,
679+
} as any)
673680
const ApiAdminContentSaveRoute = ApiAdminContentSaveRouteImport.update({
674681
id: '/api/admin/content/save',
675682
path: '/api/admin/content/save',
@@ -859,6 +866,7 @@ export interface FileRoutesByFullPath {
859866
'/api/admin/content/publish': typeof ApiAdminContentPublishRoute
860867
'/api/admin/content/rename': typeof ApiAdminContentRenameRoute
861868
'/api/admin/content/save': typeof ApiAdminContentSaveRoute
869+
'/api/admin/content/submit-for-review': typeof ApiAdminContentSubmitForReviewRoute
862870
'/api/admin/import/google-docs': typeof ApiAdminImportGoogleDocsRoute
863871
'/api/admin/import/save': typeof ApiAdminImportSaveRoute
864872
'/api/admin/media/create-folder': typeof ApiAdminMediaCreateFolderRoute
@@ -976,6 +984,7 @@ export interface FileRoutesByTo {
976984
'/api/admin/content/publish': typeof ApiAdminContentPublishRoute
977985
'/api/admin/content/rename': typeof ApiAdminContentRenameRoute
978986
'/api/admin/content/save': typeof ApiAdminContentSaveRoute
987+
'/api/admin/content/submit-for-review': typeof ApiAdminContentSubmitForReviewRoute
979988
'/api/admin/import/google-docs': typeof ApiAdminImportGoogleDocsRoute
980989
'/api/admin/import/save': typeof ApiAdminImportSaveRoute
981990
'/api/admin/media/create-folder': typeof ApiAdminMediaCreateFolderRoute
@@ -1099,6 +1108,7 @@ export interface FileRoutesById {
10991108
'/api/admin/content/publish': typeof ApiAdminContentPublishRoute
11001109
'/api/admin/content/rename': typeof ApiAdminContentRenameRoute
11011110
'/api/admin/content/save': typeof ApiAdminContentSaveRoute
1111+
'/api/admin/content/submit-for-review': typeof ApiAdminContentSubmitForReviewRoute
11021112
'/api/admin/import/google-docs': typeof ApiAdminImportGoogleDocsRoute
11031113
'/api/admin/import/save': typeof ApiAdminImportSaveRoute
11041114
'/api/admin/media/create-folder': typeof ApiAdminMediaCreateFolderRoute
@@ -1222,6 +1232,7 @@ export interface FileRouteTypes {
12221232
| '/api/admin/content/publish'
12231233
| '/api/admin/content/rename'
12241234
| '/api/admin/content/save'
1235+
| '/api/admin/content/submit-for-review'
12251236
| '/api/admin/import/google-docs'
12261237
| '/api/admin/import/save'
12271238
| '/api/admin/media/create-folder'
@@ -1339,6 +1350,7 @@ export interface FileRouteTypes {
13391350
| '/api/admin/content/publish'
13401351
| '/api/admin/content/rename'
13411352
| '/api/admin/content/save'
1353+
| '/api/admin/content/submit-for-review'
13421354
| '/api/admin/import/google-docs'
13431355
| '/api/admin/import/save'
13441356
| '/api/admin/media/create-folder'
@@ -1461,6 +1473,7 @@ export interface FileRouteTypes {
14611473
| '/api/admin/content/publish'
14621474
| '/api/admin/content/rename'
14631475
| '/api/admin/content/save'
1476+
| '/api/admin/content/submit-for-review'
14641477
| '/api/admin/import/google-docs'
14651478
| '/api/admin/import/save'
14661479
| '/api/admin/media/create-folder'
@@ -1503,6 +1516,7 @@ export interface RootRouteChildren {
15031516
ApiAdminContentPublishRoute: typeof ApiAdminContentPublishRoute
15041517
ApiAdminContentRenameRoute: typeof ApiAdminContentRenameRoute
15051518
ApiAdminContentSaveRoute: typeof ApiAdminContentSaveRoute
1519+
ApiAdminContentSubmitForReviewRoute: typeof ApiAdminContentSubmitForReviewRoute
15061520
ApiAdminImportGoogleDocsRoute: typeof ApiAdminImportGoogleDocsRoute
15071521
ApiAdminImportSaveRoute: typeof ApiAdminImportSaveRoute
15081522
ApiAdminMediaCreateFolderRoute: typeof ApiAdminMediaCreateFolderRoute
@@ -2256,6 +2270,13 @@ declare module '@tanstack/react-router' {
22562270
preLoaderRoute: typeof ApiAdminImportGoogleDocsRouteImport
22572271
parentRoute: typeof rootRouteImport
22582272
}
2273+
'/api/admin/content/submit-for-review': {
2274+
id: '/api/admin/content/submit-for-review'
2275+
path: '/api/admin/content/submit-for-review'
2276+
fullPath: '/api/admin/content/submit-for-review'
2277+
preLoaderRoute: typeof ApiAdminContentSubmitForReviewRouteImport
2278+
parentRoute: typeof rootRouteImport
2279+
}
22592280
'/api/admin/content/save': {
22602281
id: '/api/admin/content/save'
22612282
path: '/api/admin/content/save'
@@ -2604,6 +2625,7 @@ const rootRouteChildren: RootRouteChildren = {
26042625
ApiAdminContentPublishRoute: ApiAdminContentPublishRoute,
26052626
ApiAdminContentRenameRoute: ApiAdminContentRenameRoute,
26062627
ApiAdminContentSaveRoute: ApiAdminContentSaveRoute,
2628+
ApiAdminContentSubmitForReviewRoute: ApiAdminContentSubmitForReviewRoute,
26072629
ApiAdminImportGoogleDocsRoute: ApiAdminImportGoogleDocsRoute,
26082630
ApiAdminImportSaveRoute: ApiAdminImportSaveRoute,
26092631
ApiAdminMediaCreateFolderRoute: ApiAdminMediaCreateFolderRoute,

0 commit comments

Comments
 (0)