Skip to content

Commit cedd9e2

Browse files
authored
Merge branch 'master' into survey-tools
2 parents 9a12bb3 + 5863b58 commit cedd9e2

File tree

11 files changed

+478
-9
lines changed

11 files changed

+478
-9
lines changed
Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
name: Art board project reminder (reusable)
2+
3+
# Reusable workflow: paginates a Projects V2 board, finds issues stuck in a
4+
# given column for too long, and posts a one-time reminder comment.
5+
6+
permissions:
7+
contents: read
8+
9+
on:
10+
workflow_call:
11+
inputs:
12+
column:
13+
required: true
14+
type: string
15+
stale_days:
16+
required: true
17+
type: number
18+
match_missing_status:
19+
description: Also match items with no Status field set (for "No Status" columns)
20+
required: false
21+
type: boolean
22+
default: false
23+
reminder_tag:
24+
description: HTML comment used to deduplicate reminders
25+
required: true
26+
type: string
27+
message:
28+
description: 'Comment body — use {days} as a placeholder'
29+
required: true
30+
type: string
31+
secrets:
32+
GH_APP_POSTHOG_ART_BOARD_BOT_APP_ID:
33+
required: true
34+
GH_APP_POSTHOG_ART_BOARD_BOT_PRIVATE_KEY:
35+
required: true
36+
37+
jobs:
38+
remind:
39+
runs-on: ubuntu-latest
40+
steps:
41+
- name: Get app token
42+
id: app-token
43+
uses: actions/create-github-app-token@29824e69f54612133e76f7eaac726eef6c875baf # v2.2.1
44+
with:
45+
app-id: ${{ secrets.GH_APP_POSTHOG_ART_BOARD_BOT_APP_ID }}
46+
private-key: ${{ secrets.GH_APP_POSTHOG_ART_BOARD_BOT_PRIVATE_KEY }}
47+
- name: Find stale issues and post reminders
48+
uses: actions/github-script@v7
49+
env:
50+
INPUT_COLUMN: ${{ inputs.column }}
51+
INPUT_STALE_DAYS: ${{ inputs.stale_days }}
52+
INPUT_MATCH_MISSING: ${{ inputs.match_missing_status }}
53+
INPUT_TAG: ${{ inputs.reminder_tag }}
54+
INPUT_MESSAGE: ${{ inputs.message }}
55+
with:
56+
github-token: ${{ steps.app-token.outputs.token }}
57+
script: |
58+
const column = process.env.INPUT_COLUMN;
59+
const staleDays = parseInt(process.env.INPUT_STALE_DAYS);
60+
const matchMissing = process.env.INPUT_MATCH_MISSING === 'true';
61+
const reminderTag = process.env.INPUT_TAG;
62+
const msgTemplate = process.env.INPUT_MESSAGE;
63+
const snoozed = 'All projects (snoozed projects)';
64+
const staleMs = staleDays * 86_400_000;
65+
const now = Date.now();
66+
67+
// ── Paginate all project items ────────────────────────────────
68+
const QUERY = `
69+
query($org: String!, $num: Int!, $cursor: String) {
70+
organization(login: $org) {
71+
projectV2(number: $num) {
72+
items(first: 100, after: $cursor) {
73+
pageInfo { hasNextPage endCursor }
74+
nodes {
75+
updatedAt
76+
fieldValues(first: 20) {
77+
nodes {
78+
... on ProjectV2ItemFieldSingleSelectValue {
79+
name
80+
field { ... on ProjectV2FieldCommon { name } }
81+
}
82+
}
83+
}
84+
content {
85+
... on Issue { number state repository { nameWithOwner } }
86+
}
87+
}
88+
}
89+
}
90+
}
91+
}`;
92+
93+
let items = [], cursor = null;
94+
do {
95+
const res = await github.graphql(QUERY, { org: 'PostHog', num: 65, cursor });
96+
const page = res.organization.projectV2.items;
97+
items = items.concat(page.nodes);
98+
cursor = page.pageInfo.hasNextPage ? page.pageInfo.endCursor : null;
99+
} while (cursor);
100+
101+
// ── Filter stale items ────────────────────────────────────────
102+
const stale = items.filter(item => {
103+
const issue = item.content;
104+
if (!issue?.number || issue.state === 'CLOSED') return false;
105+
106+
const status = item.fieldValues.nodes.find(f => f.field?.name === 'Status');
107+
if (status?.name === snoozed) return false;
108+
109+
const matches = matchMissing
110+
? (!status || status.name === column)
111+
: (status?.name === column);
112+
if (!matches) return false;
113+
114+
return (now - new Date(item.updatedAt).getTime()) >= staleMs;
115+
});
116+
117+
console.log(`${stale.length} stale item(s) in "${column}".`);
118+
119+
// ── Post one-time reminders ───────────────────────────────────
120+
for (const item of stale) {
121+
const issue = item.content;
122+
const [owner, repo] = issue.repository.nameWithOwner.split('/');
123+
124+
const { data: comments } = await github.rest.issues.listComments({
125+
owner, repo, issue_number: issue.number, per_page: 100,
126+
});
127+
if (comments.some(c => c.body.includes(reminderTag))) continue;
128+
129+
const days = Math.floor((now - new Date(item.updatedAt).getTime()) / 86_400_000);
130+
await github.rest.issues.createComment({
131+
owner, repo, issue_number: issue.number,
132+
body: `${reminderTag}\n${msgTemplate.replace('{days}', days)}`,
133+
});
134+
console.log(`Reminded #${issue.number} (${days} days).`);
135+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
name: Art board reminders
2+
3+
permissions:
4+
contents: read
5+
6+
on:
7+
schedule:
8+
- cron: '0 9 * * *' # daily 9 AM UTC
9+
workflow_dispatch:
10+
11+
jobs:
12+
feedback-review:
13+
uses: ./.github/workflows/art-board-reminder.yml
14+
with:
15+
column: 'Feedback/Review'
16+
stale_days: 10
17+
reminder_tag: '<!-- art-board-feedback-reminder -->'
18+
message: 'This issue has been in **Feedback/Review** for {days} days. Any feedback needed to move it forward?'
19+
secrets:
20+
GH_APP_POSTHOG_ART_BOARD_BOT_APP_ID: ${{ secrets.GH_APP_POSTHOG_ART_BOARD_BOT_APP_ID }}
21+
GH_APP_POSTHOG_ART_BOARD_BOT_PRIVATE_KEY: ${{ secrets.GH_APP_POSTHOG_ART_BOARD_BOT_PRIVATE_KEY }}
22+
23+
no-status:
24+
uses: ./.github/workflows/art-board-reminder.yml
25+
with:
26+
column: 'No Status'
27+
stale_days: 7
28+
match_missing_status: true
29+
reminder_tag: '<!-- art-board-no-status-reminder -->'
30+
message: 'This issue has been sitting without an owner for {days} days. Can someone pick this up or assign it to a column on the board?'
31+
secrets:
32+
GH_APP_POSTHOG_ART_BOARD_BOT_APP_ID: ${{ secrets.GH_APP_POSTHOG_ART_BOARD_BOT_APP_ID }}
33+
GH_APP_POSTHOG_ART_BOARD_BOT_PRIVATE_KEY: ${{ secrets.GH_APP_POSTHOG_ART_BOARD_BOT_PRIVATE_KEY }}
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
name: Art board status change
2+
3+
# Handles two actions when an issue is moved on the art request board:
4+
# 1. Moved to "Done" → close the issue
5+
# 2. Moved to "Assigned: X" → remove the other default assignees
6+
# (exception: internal requests from team members keep everyone assigned)
7+
8+
permissions:
9+
contents: read
10+
11+
on:
12+
projects_v2_item:
13+
types: [edited]
14+
15+
jobs:
16+
handle:
17+
runs-on: ubuntu-latest
18+
if: github.event.projects_v2_item.field_name == 'Status'
19+
steps:
20+
- name: Get app token
21+
id: app-token
22+
uses: actions/create-github-app-token@29824e69f54612133e76f7eaac726eef6c875baf # v2.2.1
23+
with:
24+
app-id: ${{ secrets.GH_APP_POSTHOG_ART_BOARD_BOT_APP_ID }}
25+
private-key: ${{ secrets.GH_APP_POSTHOG_ART_BOARD_BOT_PRIVATE_KEY }}
26+
- name: Close issue or sync assignees
27+
uses: actions/github-script@v7
28+
with:
29+
github-token: ${{ steps.app-token.outputs.token }}
30+
script: |
31+
const ASSIGNEE_MAP = {
32+
'Assigned: Daniel': { keep: 'dphawkins1617', remove: ['lottiecoxon', 'heidiberton'] },
33+
'Assigned: Lottie': { keep: 'lottiecoxon', remove: ['dphawkins1617', 'heidiberton'] },
34+
'Assigned: Heidi': { keep: 'heidiberton', remove: ['lottiecoxon', 'dphawkins1617'] },
35+
};
36+
const TEAM = ['lottiecoxon', 'dphawkins1617', 'heidiberton'];
37+
38+
const { node: item } = await github.graphql(`
39+
query($id: ID!) {
40+
node(id: $id) {
41+
... on ProjectV2Item {
42+
fieldValues(first: 20) {
43+
nodes {
44+
... on ProjectV2ItemFieldSingleSelectValue {
45+
name
46+
field { ... on ProjectV2FieldCommon { name } }
47+
}
48+
}
49+
}
50+
content {
51+
... on Issue {
52+
number state author { login }
53+
repository { nameWithOwner }
54+
}
55+
}
56+
}
57+
}
58+
}`, { id: context.payload.projects_v2_item.node_id });
59+
60+
const status = item.fieldValues.nodes.find(f => f.field?.name === 'Status');
61+
if (!status) return;
62+
63+
const issue = item.content;
64+
if (!issue?.number || issue.state === 'CLOSED') return;
65+
66+
const [owner, repo] = issue.repository.nameWithOwner.split('/');
67+
68+
// ── Done → close ──────────────────────────────────────────────
69+
if (status.name === 'Done') {
70+
await github.rest.issues.update({
71+
owner, repo, issue_number: issue.number,
72+
state: 'closed', state_reason: 'completed',
73+
});
74+
console.log(`Closed #${issue.number}`);
75+
return;
76+
}
77+
78+
// ── Assigned column → sync assignees ──────────────────────────
79+
const mapping = ASSIGNEE_MAP[status.name];
80+
if (!mapping) return;
81+
82+
if (TEAM.includes(issue.author?.login?.toLowerCase())) {
83+
console.log(`#${issue.number} is an internal request — keeping all assignees.`);
84+
return;
85+
}
86+
87+
await github.rest.issues.removeAssignees({
88+
owner, repo, issue_number: issue.number, assignees: mapping.remove,
89+
});
90+
console.log(`Synced assignees on #${issue.number}: kept @${mapping.keep}`);

contents/handbook/brand/art-requests.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,19 @@ They get a lot of work requests, so they use [a request template](https://github
2222

2323
You can see what they are working on via the [Art & Brand Planning project board](https://github.com/orgs/PostHog/projects/65/views/2).
2424

25+
### Art board automations
26+
27+
The Art & Brand Planning board uses GitHub Actions to keep work moving:
28+
29+
- **Reminders** — A daily job (9 AM UTC) posts one-time comments on issues that have been stuck in...
30+
- **Feedback/Review** for 10+ days: asks if any feedback is needed to move the task forward.
31+
- **No Status** for 7+ days: asks someone to pick it up or assign it to a column.
32+
- **Status changes** — When an issue’s Status is changed on the board:
33+
- **Moved to "Done"** → the issue is automatically closed (as completed).
34+
- **Moved to "Assigned: Daniel", "Assigned: Lottie", or "Assigned: Heidi"** → other default assignees are removed so only the assigned person is on the issue. Internal requests (from the design team) keep all assignees.
35+
- These changes do not impact the "Assigned: Cleo" column, as Cleo has a different workload.
36+
- Workflows run under the **Art Board Bot** GitHub App and live in `.github/workflows/` (`art-board-reminder.yml`, `art-board-reminders.yml`, `art-board-status-change.yml`).
37+
2538
To establish a clear connection between the task and the working file, designers will create a frame containing a link to the task. They should then add a link to that frame within the task for easy reference.
2639

2740
Lottie and Daniel usually ask for two weeks minimum notice, but can often work faster on things if needed. If your request is genuinely urgent, please share your request issue in [#team-marketing channel](https://posthog.slack.com/archives/C08CG24E3SR) and mention Lottie, Daniel, and/or [Cory](https://posthog.com/community/profiles/30200).

contents/handbook/brand/partners.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,11 @@ sidebar: Handbook
44
showTitle: true
55
---
66

7-
We're frequently contacted about revenue-sharing partnerships, or individuals and agencies that want to be listed as official partners. We do not currently operate any form of partnership program to help users implement, extend, or adapt PostHog to their needs.
7+
We're frequently contacted about revenue-sharing partnerships, or individuals and agencies that want to be listed as official partners. Also, technology integrations!
88

9-
**If someone contacts you about partnering with PostHog**, refer them to [Joe](https://posthog.com/community/profiles/29070). He'll give them the bad news and explore any opportunities.
9+
**If someone contacts you about partnering with PostHog**, refer them to [our partnerships page](/partnerships) and ask them to complete the survey there. This will directly alert relevant teams internally.
1010

11-
Instead, we recommend users who need this sort of help explore our existing resources, or [contact us](http://app.posthog.com/home#supportModal). If we can help, we will!
11+
We recommend users who need implementation help explore our existing resources, [purchase time with the onboarding team](/merch) or [contact us](http://app.posthog.com/home#supportModal). If we can help, we will!
1212

1313
## Helpful resources for users
1414
Users who contact us about wanting support from a partner often want particular types of help. We've curated some resources below which we can give them so they can self-serve where possible.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@
6060
"@popperjs/core": "^2.11.2",
6161
"@posthog/hedgehog-mode": "^0.0.48",
6262
"@posthog/icons": "0.36.6",
63-
"@posthog/types": "1.353.1",
63+
"@posthog/types": "1.354.2",
6464
"@radix-ui/react-accordion": "^1.2.3",
6565
"@radix-ui/react-collapsible": "^1.1.12",
6666
"@radix-ui/react-icons": "^1.3.2",

pnpm-lock.yaml

Lines changed: 5 additions & 5 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)