Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

39 changes: 18 additions & 21 deletions apps/api/src/handlers/discord/automation-suggestions.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
import type { CommunicationMessageButton } from '@roomote/communication';
import { and, db, eq, sql, trackedMessages } from '@roomote/db/server';
import {
and,
db,
eq,
registerTrackedSuggestionCards,
sql,
trackedMessages,
} from '@roomote/db/server';
import {
findDiscordAutomationDestination,
findDiscordDefaultDestination,
Expand Down Expand Up @@ -54,30 +61,20 @@ export async function postCurrentThreadSuggestionsToDiscord(params: {
return false;
}

const trackedRow = {
surface: 'discord' as const,
kind: 'suggestion_card' as const,
dedupeKey: `${posted.threadId ?? posted.channelId}:${posted.messageId}`,
channelId: posted.threadId ?? posted.channelId,
...(posted.threadId ? { threadTs: posted.threadId } : {}),
messageTs: posted.messageId,
workItemId: suggestion.id,
createdByUserId: params.createdByUserId,
metadata: {
await registerTrackedSuggestionCards([
{
surface: 'discord',
channelId: posted.threadId ?? posted.channelId,
messageTs: posted.messageId,
threadTs: posted.threadId,
workItemId: suggestion.id,
createdByUserId: params.createdByUserId,
suggestionType: 'suggested_tasks',
suggestionKey: `${params.sourceTaskId}:${suggestion.id}`,
suggestionGroupKey: params.suggestionGroupKey,
...(params.launchRouting
? { launchRouting: params.launchRouting }
: {}),
launchRouting: params.launchRouting,
},
};
await db
.insert(trackedMessages)
.values(trackedRow)
.onConflictDoNothing({
target: [trackedMessages.kind, trackedMessages.dedupeKey],
});
]);
}

return true;
Expand Down
3 changes: 2 additions & 1 deletion apps/api/src/handlers/slack/constants.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { Env } from '@roomote/env';
import { TASK_SUGGESTION_MESSAGE_METADATA_EVENT_TYPE } from '@roomote/types';

const UNFURL_ALLOWED_DOMAIN_SUFFIXES = new Set(
(Env.SLACK_UNFURL_ALLOWED_DOMAINS ?? new URL(Env.R_APP_URL).hostname)
Expand Down Expand Up @@ -32,7 +33,7 @@ export const TASK_SUGGESTION_TYPES = [
SUGGESTED_TASKS_SUGGESTION_TYPE,
] as const;
export const SETUP_ONBOARDING_SUGGESTION_METADATA_EVENT_TYPE =
'roomote.setup_onboarding_suggestion';
TASK_SUGGESTION_MESSAGE_METADATA_EVENT_TYPE;
export const THUMBS_UP_REACTIONS = new Set(['+1', 'thumbsup']);

export const isAllowedUnfurlDomain = (domain: string): boolean => {
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

103 changes: 28 additions & 75 deletions apps/api/src/handlers/tasks/submitTaskSuggestions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,10 @@ import {
workspaceReadinessSchema,
type WorkspaceReadiness,
} from '@roomote/types';
import { SlackNotifier } from '@roomote/slack';
import {
buildTaskSuggestionMessageMetadata,
SlackNotifier,
} from '@roomote/slack';
import { SETUP_SUGGESTIONS_THREAD_INTRO_TEXT } from '@roomote/communication/chat-messages';
import { findEnvironmentForRepo } from '@roomote/cloud-agents/server';
import {
Expand All @@ -36,7 +39,9 @@ import {
db,
environments,
eq,
findTrackedSuggestionWorkItemIds,
inArray,
registerTrackedSuggestionCards,
repositories,
resolveRepositorySelectionByIds,
slackInstallationChannels,
Expand Down Expand Up @@ -102,9 +107,6 @@ const submitTaskSuggestionsBodySchema = z.object({
submissionKey: z.string().trim().min(1).max(200).optional(),
});

const SETUP_ONBOARDING_SUGGESTION_METADATA_EVENT_TYPE =
'roomote.setup_onboarding_suggestion';

type SuggestedTasksPayload = TaskPayload<typeof TaskPayloadKind.Scan>;

type PersistedTaskSuggestion = {
Expand Down Expand Up @@ -177,29 +179,23 @@ type SuggestionCardMessageRow = {
createdByUserId: string | null;
};

/**
* Map Slack suggestion-card rows to `tracked_messages` insert values. The
* launch state lives on the referenced `work_items` row; the tracked message
* carries only registry metadata (suggestion type + key) and dedups on
* `(kind, dedupeKey)` where dedupeKey is `${channelId}:${messageTs}`.
*/
function buildSlackSuggestionCardValues(
function registerSlackSuggestionMessageRows(
rows: SuggestionCardMessageRow[],
): (typeof trackedMessages.$inferInsert)[] {
return rows.map((row) => ({
surface: 'slack' as const,
kind: 'suggestion_card' as const,
dedupeKey: `${row.channelId}:${row.messageTs}`,
channelId: row.channelId,
messageTs: row.messageTs,
workItemId: row.workItemId,
createdByUserId: row.createdByUserId,
metadata: {
executor?: Parameters<typeof registerTrackedSuggestionCards>[1],
): Promise<void> {
return registerTrackedSuggestionCards(
rows.map((row) => ({
surface: 'slack',
channelId: row.channelId,
messageTs: row.messageTs,
workItemId: row.workItemId,
createdByUserId: row.createdByUserId,
suggestionType: row.suggestionType,
suggestionKey: row.suggestionKey,
...(row.launchRouting ? { launchRouting: row.launchRouting } : {}),
},
}));
launchRouting: row.launchRouting,
})),
executor,
);
}

function buildSuggestionMessageKey(params: {
Expand All @@ -209,20 +205,6 @@ function buildSuggestionMessageKey(params: {
return `${params.sourceTaskId}:${params.suggestionId}`;
}

function buildSuggestionMessageMetadata(params: {
sourceTaskId: string;
suggestionId: string;
}) {
return {
event_type: SETUP_ONBOARDING_SUGGESTION_METADATA_EVENT_TYPE,
event_payload: {
sourceTaskId: params.sourceTaskId,
suggestionId: params.suggestionId,
schemaVersion: 1,
},
};
}

function buildSuggestedTasksSummaryLockKey(params: {
sourceTaskId: string;
}): string {
Expand Down Expand Up @@ -787,7 +769,7 @@ async function postTaskSuggestionsThreadToSlack(params: {
thread_ts: rootMessageTs,
text,
blocks,
metadata: buildSuggestionMessageMetadata({
metadata: buildTaskSuggestionMessageMetadata({
sourceTaskId: params.sourceTaskId,
suggestionId: suggestion.id,
}),
Expand Down Expand Up @@ -864,12 +846,7 @@ async function postCurrentThreadSuggestionsToSlack(params: {
existingRootMessageTs: params.slackThreadTs,
suggestions: missingSuggestions,
insertSuggestionMessages: async (suggestionMessageRows) => {
await db
.insert(trackedMessages)
.values(buildSlackSuggestionCardValues(suggestionMessageRows))
.onConflictDoNothing({
target: [trackedMessages.kind, trackedMessages.dedupeKey],
});
await registerSlackSuggestionMessageRows(suggestionMessageRows);
},
});

Expand All @@ -887,24 +864,10 @@ async function getMissingTrackedSuggestions(
return [];
}

const existingSuggestionCards = await db
.select({ workItemId: trackedMessages.workItemId })
.from(trackedMessages)
.where(
and(
eq(trackedMessages.surface, surface),
eq(trackedMessages.kind, 'suggestion_card'),
inArray(
trackedMessages.workItemId,
suggestions.map((suggestion) => suggestion.id),
),
),
);
const deliveredWorkItemIds = new Set(
existingSuggestionCards
.map((card) => card.workItemId)
.filter((workItemId): workItemId is string => Boolean(workItemId)),
);
const deliveredWorkItemIds = await findTrackedSuggestionWorkItemIds({
surface,
workItemIds: suggestions.map((suggestion) => suggestion.id),
});
const missingSuggestions = suggestions.filter(
(suggestion) => !deliveredWorkItemIds.has(suggestion.id),
);
Expand Down Expand Up @@ -970,12 +933,7 @@ async function postSetupTaskSuggestionsToSlack(params: {
rootText: introText,
suggestions: missingSuggestions,
insertSuggestionMessages: async (suggestionMessageRows) => {
await db
.insert(trackedMessages)
.values(buildSlackSuggestionCardValues(suggestionMessageRows))
.onConflictDoNothing({
target: [trackedMessages.kind, trackedMessages.dedupeKey],
});
await registerSlackSuggestionMessageRows(suggestionMessageRows);
},
});

Expand Down Expand Up @@ -1141,12 +1099,7 @@ async function postSuggestedTasksSummaryToSlack(params: {
: null,
suggestions: missingSuggestions,
insertSuggestionMessages: async (suggestionMessageRows) => {
await tx
.insert(trackedMessages)
.values(buildSlackSuggestionCardValues(suggestionMessageRows))
.onConflictDoNothing({
target: [trackedMessages.kind, trackedMessages.dedupeKey],
});
await registerSlackSuggestionMessageRows(suggestionMessageRows, tx);
},
});

Expand Down
Loading
Loading