mirror of https://github.com/buster-so/buster.git
fixed types on slack handlers
This commit is contained in:
parent
b179a7dd9d
commit
b062694a2c
|
@ -1,9 +1,14 @@
|
|||
import { getUserOrganizationId } from '@buster/database';
|
||||
import {
|
||||
type GetChannelsResponse,
|
||||
type GetIntegrationResponse,
|
||||
type InitiateOAuthResponse,
|
||||
InitiateOAuthSchema,
|
||||
OAuthCallbackSchema,
|
||||
type RemoveIntegrationResponse,
|
||||
SlackError,
|
||||
SlackErrorCodes,
|
||||
type SlackErrorResponse,
|
||||
type UpdateIntegrationResponse,
|
||||
UpdateIntegrationSchema,
|
||||
} from '@buster/server-shared/slack';
|
||||
import { SlackChannelService } from '@buster/slack';
|
||||
|
@ -34,14 +39,14 @@ export class SlackHandler {
|
|||
* POST /api/v2/slack/auth/init
|
||||
* Initiate OAuth flow
|
||||
*/
|
||||
async initiateOAuth(c: Context) {
|
||||
async initiateOAuth(c: Context): Promise<Response> {
|
||||
try {
|
||||
// Get service instance (lazy initialization)
|
||||
const slackOAuthService = this.getSlackOAuthService();
|
||||
|
||||
// Check if service is available
|
||||
if (!slackOAuthService) {
|
||||
return c.json(
|
||||
return c.json<SlackErrorResponse>(
|
||||
{
|
||||
error: 'Slack integration is not configured',
|
||||
code: 'INTEGRATION_NOT_CONFIGURED',
|
||||
|
@ -52,7 +57,7 @@ export class SlackHandler {
|
|||
|
||||
// Check if integration is enabled
|
||||
if (!slackOAuthService.isEnabled()) {
|
||||
return c.json(
|
||||
return c.json<SlackErrorResponse>(
|
||||
{
|
||||
error: 'Slack integration is not enabled',
|
||||
code: 'INTEGRATION_DISABLED',
|
||||
|
@ -75,10 +80,10 @@ export class SlackHandler {
|
|||
}
|
||||
|
||||
// Parse request body
|
||||
const body = await c.req.json().catch(() => ({}));
|
||||
const body: unknown = await c.req.json().catch(() => ({}));
|
||||
const parsed = InitiateOAuthSchema.safeParse(body);
|
||||
|
||||
const metadata = parsed.success ? parsed.data.metadata : undefined;
|
||||
const metadata = parsed.success ? parsed.data?.metadata : undefined;
|
||||
|
||||
// Add IP address to metadata
|
||||
const enrichedMetadata = {
|
||||
|
@ -93,7 +98,10 @@ export class SlackHandler {
|
|||
metadata: enrichedMetadata,
|
||||
});
|
||||
|
||||
return c.json(result);
|
||||
return c.json<InitiateOAuthResponse>({
|
||||
auth_url: result.authUrl,
|
||||
state: result.state,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Failed to initiate OAuth:', error);
|
||||
|
||||
|
@ -121,7 +129,7 @@ export class SlackHandler {
|
|||
* GET /api/v2/slack/auth/callback
|
||||
* Handle OAuth callback from Slack
|
||||
*/
|
||||
async handleOAuthCallback(c: Context) {
|
||||
async handleOAuthCallback(c: Context): Promise<Response> {
|
||||
try {
|
||||
// Get service instance (lazy initialization)
|
||||
const slackOAuthService = this.getSlackOAuthService();
|
||||
|
@ -132,7 +140,7 @@ export class SlackHandler {
|
|||
}
|
||||
|
||||
// Parse query parameters
|
||||
const query = c.req.query();
|
||||
const query = c.req.query() as Record<string, string>;
|
||||
console.info('OAuth callback received', {
|
||||
hasCode: !!query.code,
|
||||
hasState: !!query.state,
|
||||
|
@ -149,7 +157,7 @@ export class SlackHandler {
|
|||
}
|
||||
|
||||
console.error('Invalid OAuth callback parameters:', {
|
||||
errors: parsed.error.errors,
|
||||
errors: parsed.error.issues,
|
||||
providedKeys: Object.keys(query),
|
||||
expectedKeys: ['code', 'state'],
|
||||
});
|
||||
|
@ -198,14 +206,14 @@ export class SlackHandler {
|
|||
* GET /api/v2/slack/integration
|
||||
* Get current integration status
|
||||
*/
|
||||
async getIntegration(c: Context) {
|
||||
async getIntegration(c: Context): Promise<Response> {
|
||||
try {
|
||||
// Get service instance (lazy initialization)
|
||||
const slackOAuthService = this.getSlackOAuthService();
|
||||
|
||||
// Check if service is available
|
||||
if (!slackOAuthService) {
|
||||
return c.json(
|
||||
return c.json<SlackErrorResponse>(
|
||||
{
|
||||
error: 'Slack integration is not configured',
|
||||
code: 'INTEGRATION_NOT_CONFIGURED',
|
||||
|
@ -228,7 +236,19 @@ export class SlackHandler {
|
|||
|
||||
const status = await slackOAuthService.getIntegrationStatus(organizationGrant.organizationId);
|
||||
|
||||
return c.json(status);
|
||||
return c.json<GetIntegrationResponse>({
|
||||
connected: status.connected,
|
||||
integration: status.integration
|
||||
? {
|
||||
id: status.integration.id,
|
||||
team_name: status.integration.teamName,
|
||||
installed_at: status.integration.installedAt,
|
||||
team_domain: status.integration.teamDomain,
|
||||
last_used_at: status.integration.lastUsedAt,
|
||||
default_channel: status.integration.defaultChannel,
|
||||
}
|
||||
: undefined,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Failed to get integration status:', error);
|
||||
|
||||
|
@ -248,14 +268,14 @@ export class SlackHandler {
|
|||
* DELETE /api/v2/slack/integration
|
||||
* Remove Slack integration
|
||||
*/
|
||||
async removeIntegration(c: Context) {
|
||||
async removeIntegration(c: Context): Promise<Response> {
|
||||
try {
|
||||
// Get service instance (lazy initialization)
|
||||
const slackOAuthService = this.getSlackOAuthService();
|
||||
|
||||
// Check if service is available
|
||||
if (!slackOAuthService) {
|
||||
return c.json(
|
||||
return c.json<SlackErrorResponse>(
|
||||
{
|
||||
error: 'Slack integration is not configured',
|
||||
code: 'INTEGRATION_NOT_CONFIGURED',
|
||||
|
@ -289,7 +309,7 @@ export class SlackHandler {
|
|||
);
|
||||
}
|
||||
|
||||
return c.json({
|
||||
return c.json<RemoveIntegrationResponse>({
|
||||
message: 'Slack integration removed successfully',
|
||||
});
|
||||
} catch (error) {
|
||||
|
@ -311,14 +331,14 @@ export class SlackHandler {
|
|||
* PUT /api/v2/slack/integration
|
||||
* Update Slack integration settings
|
||||
*/
|
||||
async updateIntegration(c: Context) {
|
||||
async updateIntegration(c: Context): Promise<Response> {
|
||||
try {
|
||||
// Get service instance (lazy initialization)
|
||||
const slackOAuthService = this.getSlackOAuthService();
|
||||
|
||||
// Check if service is available
|
||||
if (!slackOAuthService) {
|
||||
return c.json(
|
||||
return c.json<SlackErrorResponse>(
|
||||
{
|
||||
error: 'Slack integration is not configured',
|
||||
code: 'INTEGRATION_NOT_CONFIGURED',
|
||||
|
@ -340,12 +360,12 @@ export class SlackHandler {
|
|||
}
|
||||
|
||||
// Parse request body
|
||||
const body = await c.req.json().catch(() => ({}));
|
||||
const body: unknown = await c.req.json().catch(() => ({}));
|
||||
const parsed = UpdateIntegrationSchema.safeParse(body);
|
||||
|
||||
if (!parsed.success) {
|
||||
throw new SlackError(
|
||||
`Invalid request body: ${parsed.error.errors.map((e) => e.message).join(', ')}`,
|
||||
`Invalid request body: ${parsed.error.issues.map((e) => e.message).join(', ')}`,
|
||||
400,
|
||||
'INVALID_REQUEST_BODY'
|
||||
);
|
||||
|
@ -363,7 +383,7 @@ export class SlackHandler {
|
|||
await updateDefaultChannel(integration.id, parsed.data.default_channel);
|
||||
}
|
||||
|
||||
return c.json({
|
||||
return c.json<UpdateIntegrationResponse>({
|
||||
message: 'Integration updated successfully',
|
||||
...parsed.data,
|
||||
});
|
||||
|
@ -386,14 +406,14 @@ export class SlackHandler {
|
|||
* GET /api/v2/slack/channels
|
||||
* Get public channels for the current integration
|
||||
*/
|
||||
async getChannels(c: Context) {
|
||||
async getChannels(c: Context): Promise<Response> {
|
||||
try {
|
||||
// Get service instance (lazy initialization)
|
||||
const slackOAuthService = this.getSlackOAuthService();
|
||||
|
||||
// Check if service is available
|
||||
if (!slackOAuthService) {
|
||||
return c.json(
|
||||
return c.json<SlackErrorResponse>(
|
||||
{
|
||||
error: 'Slack integration is not configured',
|
||||
code: 'INTEGRATION_NOT_CONFIGURED',
|
||||
|
@ -418,7 +438,7 @@ export class SlackHandler {
|
|||
const integration = await slackHelpers.getActiveIntegration(organizationGrant.organizationId);
|
||||
|
||||
if (!integration) {
|
||||
return c.json(
|
||||
return c.json<SlackErrorResponse>(
|
||||
{
|
||||
error: 'No active Slack integration found',
|
||||
code: 'INTEGRATION_NOT_FOUND',
|
||||
|
@ -446,7 +466,7 @@ export class SlackHandler {
|
|||
await slackHelpers.updateLastUsedAt(integration.id);
|
||||
|
||||
// Return only id and name as requested
|
||||
return c.json({
|
||||
return c.json<GetChannelsResponse>({
|
||||
channels: channels.map((channel) => ({
|
||||
id: channel.id,
|
||||
name: channel.name,
|
||||
|
|
|
@ -40,7 +40,13 @@ export class SlackOAuthService {
|
|||
clientId: this.env.SLACK_CLIENT_ID,
|
||||
clientSecret: this.env.SLACK_CLIENT_SECRET,
|
||||
redirectUri: this.env.SLACK_REDIRECT_URI,
|
||||
scopes: ['channels:history', 'chat:write', 'chat:write.public', 'commands'],
|
||||
scopes: [
|
||||
'channels:history',
|
||||
'channels:read',
|
||||
'chat:write',
|
||||
'chat:write.public',
|
||||
'commands',
|
||||
],
|
||||
},
|
||||
tokenStorage,
|
||||
oauthStateStorage
|
||||
|
|
|
@ -320,7 +320,13 @@ describe('data-transformers', () => {
|
|||
});
|
||||
|
||||
it('should handle empty conversation history', () => {
|
||||
const result = buildWorkflowInput(baseMessageContext, [], basePreviousResults, baseDatasets, false);
|
||||
const result = buildWorkflowInput(
|
||||
baseMessageContext,
|
||||
[],
|
||||
basePreviousResults,
|
||||
baseDatasets,
|
||||
false
|
||||
);
|
||||
expect(result.conversationHistory).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
|
|
@ -127,7 +127,7 @@ export async function getExistingSlackMessageForChat(chatId: string): Promise<{
|
|||
* Send a Slack notification based on post-processing results
|
||||
*/
|
||||
export async function sendSlackNotification(
|
||||
params: SlackNotificationParams,
|
||||
params: SlackNotificationParams
|
||||
): Promise<SlackNotificationResult> {
|
||||
try {
|
||||
// Step 1: Check if organization has active Slack integration
|
||||
|
@ -139,8 +139,8 @@ export async function sendSlackNotification(
|
|||
and(
|
||||
eq(slackIntegrations.organizationId, params.organizationId),
|
||||
eq(slackIntegrations.status, 'active'),
|
||||
isNull(slackIntegrations.deletedAt),
|
||||
),
|
||||
isNull(slackIntegrations.deletedAt)
|
||||
)
|
||||
)
|
||||
.limit(1);
|
||||
|
||||
|
@ -189,7 +189,7 @@ export async function sendSlackNotification(
|
|||
const result = await sendSlackMessage(
|
||||
tokenSecret.secret,
|
||||
integration.defaultChannel.id,
|
||||
slackMessage,
|
||||
slackMessage
|
||||
);
|
||||
|
||||
if (result.success) {
|
||||
|
@ -229,7 +229,7 @@ export async function sendSlackNotification(
|
|||
* Send a Slack reply notification to an existing thread
|
||||
*/
|
||||
export async function sendSlackReplyNotification(
|
||||
params: SlackReplyNotificationParams,
|
||||
params: SlackReplyNotificationParams
|
||||
): Promise<SlackNotificationResult> {
|
||||
try {
|
||||
// Step 1: Check if we should send a notification
|
||||
|
@ -257,7 +257,7 @@ export async function sendSlackReplyNotification(
|
|||
tokenSecret.secret,
|
||||
params.channelId,
|
||||
slackMessage,
|
||||
params.threadTs,
|
||||
params.threadTs
|
||||
);
|
||||
|
||||
if (result.success) {
|
||||
|
@ -372,7 +372,7 @@ function formatSlackReplyMessage(params: SlackReplyNotificationParams): SlackMes
|
|||
|
||||
throw new Error(
|
||||
'Invalid reply notification parameters: Missing required fields. ' +
|
||||
'Requires either formattedMessage, summaryTitle with summaryMessage, or toolCalled="flagChat" with message',
|
||||
'Requires either formattedMessage, summaryTitle with summaryMessage, or toolCalled="flagChat" with message'
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -456,7 +456,7 @@ function formatSlackMessage(params: SlackNotificationParams): SlackMessage {
|
|||
throw new Error(
|
||||
`Invalid notification parameters: Missing required fields. Requires either formattedMessage, summaryTitle with summaryMessage, or toolCalled="flagChat" with message. Received: formattedMessage=${!!params.formattedMessage}, summaryTitle=${!!params.summaryTitle}, summaryMessage=${!!params.summaryMessage}, toolCalled="${
|
||||
params.toolCalled
|
||||
}", message=${!!params.message}`,
|
||||
}", message=${!!params.message}`
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -467,7 +467,7 @@ async function sendSlackMessage(
|
|||
accessToken: string,
|
||||
channelId: string,
|
||||
message: SlackMessage,
|
||||
threadTs?: string,
|
||||
threadTs?: string
|
||||
): Promise<{ success: boolean; messageTs?: string; error?: string }> {
|
||||
try {
|
||||
const response = await fetch('https://slack.com/api/chat.postMessage', {
|
||||
|
@ -540,8 +540,8 @@ export async function trackSlackNotification(params: {
|
|||
content: params.slackBlocks
|
||||
? JSON.stringify({ blocks: params.slackBlocks })
|
||||
: params.summaryTitle && params.summaryMessage
|
||||
? `${params.summaryTitle}\n\n${params.summaryMessage}`
|
||||
: 'Notification sent',
|
||||
? `${params.summaryTitle}\n\n${params.summaryMessage}`
|
||||
: 'Notification sent',
|
||||
senderInfo: {
|
||||
sentBy: 'buster-post-processing',
|
||||
userName: params.userName,
|
||||
|
|
|
@ -24,14 +24,16 @@ vi.mock('@buster/database', () => ({
|
|||
getDb: vi.fn(),
|
||||
eq: vi.fn((a, b) => ({ type: 'eq', a, b })),
|
||||
messages: { id: 'messages.id' },
|
||||
getBraintrustMetadata: vi.fn(() => Promise.resolve({
|
||||
userName: 'John Doe',
|
||||
userId: 'user-123',
|
||||
organizationName: 'Test Org',
|
||||
organizationId: 'org-123',
|
||||
messageId: 'msg-12345',
|
||||
chatId: 'chat-123',
|
||||
})),
|
||||
getBraintrustMetadata: vi.fn(() =>
|
||||
Promise.resolve({
|
||||
userName: 'John Doe',
|
||||
userId: 'user-123',
|
||||
organizationName: 'Test Org',
|
||||
organizationId: 'org-123',
|
||||
messageId: 'msg-12345',
|
||||
chatId: 'chat-123',
|
||||
})
|
||||
),
|
||||
}));
|
||||
|
||||
vi.mock('@buster/ai/workflows/post-processing-workflow', () => ({
|
||||
|
|
|
@ -19,7 +19,9 @@ export const combineParallelResultsOutputSchema = z.object({
|
|||
userId: z.string().describe('User ID for the current operation'),
|
||||
chatId: z.string().describe('Chat ID for the current operation'),
|
||||
isFollowUp: z.boolean().describe('Whether this is a follow-up message'),
|
||||
isSlackFollowUp: z.boolean().describe('Whether this is a follow-up message for an existing Slack thread'),
|
||||
isSlackFollowUp: z
|
||||
.boolean()
|
||||
.describe('Whether this is a follow-up message for an existing Slack thread'),
|
||||
previousMessages: z.array(z.string()).describe('Array of previous messages for context'),
|
||||
datasets: z.string().describe('Assembled YAML content of all available datasets for context'),
|
||||
|
||||
|
|
|
@ -15,7 +15,9 @@ const inputSchema = z.object({
|
|||
userId: z.string().describe('User ID for the current operation'),
|
||||
chatId: z.string().describe('Chat ID for the current operation'),
|
||||
isFollowUp: z.boolean().describe('Whether this is a follow-up message'),
|
||||
isSlackFollowUp: z.boolean().describe('Whether this is a follow-up message for an existing Slack thread'),
|
||||
isSlackFollowUp: z
|
||||
.boolean()
|
||||
.describe('Whether this is a follow-up message for an existing Slack thread'),
|
||||
previousMessages: z.array(z.string()).describe('Array of previous messages for context'),
|
||||
datasets: z.string().describe('Assembled YAML content of all available datasets for context'),
|
||||
});
|
||||
|
@ -28,7 +30,9 @@ export const flagChatOutputSchema = z.object({
|
|||
userId: z.string().describe('User ID for the current operation'),
|
||||
chatId: z.string().describe('Chat ID for the current operation'),
|
||||
isFollowUp: z.boolean().describe('Whether this is a follow-up message'),
|
||||
isSlackFollowUp: z.boolean().describe('Whether this is a follow-up message for an existing Slack thread'),
|
||||
isSlackFollowUp: z
|
||||
.boolean()
|
||||
.describe('Whether this is a follow-up message for an existing Slack thread'),
|
||||
previousMessages: z.array(z.string()).describe('Array of previous messages for context'),
|
||||
datasets: z.string().describe('Assembled YAML content of all available datasets for context'),
|
||||
|
||||
|
|
|
@ -18,7 +18,9 @@ const inputSchema = z.object({
|
|||
userId: z.string().describe('User ID for the current operation'),
|
||||
chatId: z.string().describe('Chat ID for the current operation'),
|
||||
isFollowUp: z.boolean().describe('Whether this is a follow-up message'),
|
||||
isSlackFollowUp: z.boolean().describe('Whether this is a follow-up message for an existing Slack thread'),
|
||||
isSlackFollowUp: z
|
||||
.boolean()
|
||||
.describe('Whether this is a follow-up message for an existing Slack thread'),
|
||||
previousMessages: z.array(z.string()).describe('Array of previous messages for context'),
|
||||
datasets: z.string().describe('Assembled YAML content of all available datasets for context'),
|
||||
});
|
||||
|
@ -31,7 +33,9 @@ export const identifyAssumptionsOutputSchema = z.object({
|
|||
userId: z.string().describe('User ID for the current operation'),
|
||||
chatId: z.string().describe('Chat ID for the current operation'),
|
||||
isFollowUp: z.boolean().describe('Whether this is a follow-up message'),
|
||||
isSlackFollowUp: z.boolean().describe('Whether this is a follow-up message for an existing Slack thread'),
|
||||
isSlackFollowUp: z
|
||||
.boolean()
|
||||
.describe('Whether this is a follow-up message for an existing Slack thread'),
|
||||
previousMessages: z.array(z.string()).describe('Array of previous messages for context'),
|
||||
datasets: z.string().describe('Assembled YAML content of all available datasets for context'),
|
||||
|
||||
|
|
|
@ -9,7 +9,9 @@ export const postProcessingWorkflowInputSchema = z.object({
|
|||
userId: z.string().describe('User ID for the current operation'),
|
||||
chatId: z.string().describe('Chat ID for the current operation'),
|
||||
isFollowUp: z.boolean().describe('Whether this is a follow-up message'),
|
||||
isSlackFollowUp: z.boolean().describe('Whether this is a follow-up message for an existing Slack thread'),
|
||||
isSlackFollowUp: z
|
||||
.boolean()
|
||||
.describe('Whether this is a follow-up message for an existing Slack thread'),
|
||||
previousMessages: z.array(z.string()).describe('Array of the previous post-processing messages'),
|
||||
datasets: z.string().describe('Assembled YAML content of all available datasets for context'),
|
||||
});
|
||||
|
@ -23,7 +25,9 @@ export const postProcessingWorkflowOutputSchema = z.object({
|
|||
userId: z.string().describe('User ID for the current operation'),
|
||||
chatId: z.string().describe('Chat ID for the current operation'),
|
||||
isFollowUp: z.boolean().describe('Whether this is a follow-up message'),
|
||||
isSlackFollowUp: z.boolean().describe('Whether this is a follow-up message for an existing Slack thread'),
|
||||
isSlackFollowUp: z
|
||||
.boolean()
|
||||
.describe('Whether this is a follow-up message for an existing Slack thread'),
|
||||
previousMessages: z.array(z.string()).describe('Array of the previous post-processing messages'),
|
||||
datasets: z.string().describe('Assembled YAML content of all available datasets for context'),
|
||||
|
||||
|
|
|
@ -30,7 +30,8 @@ export function validateAndAdjustBarLineAxes(metricYml: MetricYml): AxisValidati
|
|||
return {
|
||||
isValid: false,
|
||||
shouldSwapAxes: false,
|
||||
error: 'Bar and line charts require at least one column for each axis. Please specify both X and Y axis columns.',
|
||||
error:
|
||||
'Bar and line charts require at least one column for each axis. Please specify both X and Y axis columns.',
|
||||
};
|
||||
}
|
||||
const xColumns = barAndLineAxis.x;
|
||||
|
|
Loading…
Reference in New Issue