mirror of
https://github.com/Infisical/infisical.git
synced 2025-03-31 22:09:57 +00:00
misc: added root workflow integration structure
This commit is contained in:
backend/src
@types
db
migrations
schemas
server/routes
services
frontend/src
hooks/api/workflowIntegrations
views/Settings/OrgSettingsPage/components/OrgWorkflowIntegrationTab
2
backend/src/@types/fastify.d.ts
vendored
2
backend/src/@types/fastify.d.ts
vendored
@ -77,6 +77,7 @@ import { TUserDALFactory } from "@app/services/user/user-dal";
|
||||
import { TUserServiceFactory } from "@app/services/user/user-service";
|
||||
import { TUserEngagementServiceFactory } from "@app/services/user-engagement/user-engagement-service";
|
||||
import { TWebhookServiceFactory } from "@app/services/webhook/webhook-service";
|
||||
import { TWorkflowIntegrationServiceFactory } from "@app/services/workflow-integration/workflow-integration-service";
|
||||
|
||||
declare module "fastify" {
|
||||
interface FastifyRequest {
|
||||
@ -179,6 +180,7 @@ declare module "fastify" {
|
||||
externalKms: TExternalKmsServiceFactory;
|
||||
orgAdmin: TOrgAdminServiceFactory;
|
||||
slack: TSlackServiceFactory;
|
||||
workflowIntegration: TWorkflowIntegrationServiceFactory;
|
||||
};
|
||||
// this is exclusive use for middlewares in which we need to inject data
|
||||
// everywhere else access using service layer
|
||||
|
10
backend/src/@types/knex.d.ts
vendored
10
backend/src/@types/knex.d.ts
vendored
@ -331,7 +331,10 @@ import {
|
||||
TUsersUpdate,
|
||||
TWebhooks,
|
||||
TWebhooksInsert,
|
||||
TWebhooksUpdate
|
||||
TWebhooksUpdate,
|
||||
TWorkflowIntegrations,
|
||||
TWorkflowIntegrationsInsert,
|
||||
TWorkflowIntegrationsUpdate
|
||||
} from "@app/db/schemas";
|
||||
import {
|
||||
TSecretV2TagJunction,
|
||||
@ -800,5 +803,10 @@ declare module "knex/types/tables" {
|
||||
TAdminSlackConfigsInsert,
|
||||
TAdminSlackConfigsUpdate
|
||||
>;
|
||||
[TableName.WorkflowIntegrations]: KnexOriginal.CompositeTableType<
|
||||
TWorkflowIntegrations,
|
||||
TWorkflowIntegrationsInsert,
|
||||
TWorkflowIntegrationsUpdate
|
||||
>;
|
||||
}
|
||||
}
|
||||
|
@ -4,13 +4,25 @@ import { TableName } from "../schemas";
|
||||
import { createOnUpdateTrigger, dropOnUpdateTrigger } from "../utils";
|
||||
|
||||
export async function up(knex: Knex): Promise<void> {
|
||||
if (!(await knex.schema.hasTable(TableName.SlackIntegrations))) {
|
||||
await knex.schema.createTable(TableName.SlackIntegrations, (tb) => {
|
||||
if (!(await knex.schema.hasTable(TableName.WorkflowIntegrations))) {
|
||||
await knex.schema.createTable(TableName.WorkflowIntegrations, (tb) => {
|
||||
tb.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
|
||||
tb.string("integration").notNullable();
|
||||
tb.string("slug").notNullable();
|
||||
tb.uuid("orgId").notNullable();
|
||||
tb.foreign("orgId").references("id").inTable(TableName.Organization).onDelete("CASCADE");
|
||||
tb.string("description");
|
||||
tb.unique(["orgId", "slug"]);
|
||||
tb.timestamps(true, true, true);
|
||||
});
|
||||
|
||||
await createOnUpdateTrigger(knex, TableName.WorkflowIntegrations);
|
||||
}
|
||||
|
||||
if (!(await knex.schema.hasTable(TableName.SlackIntegrations))) {
|
||||
await knex.schema.createTable(TableName.SlackIntegrations, (tb) => {
|
||||
tb.uuid("id", { primaryKey: true }).notNullable();
|
||||
tb.foreign("id").references("id").inTable(TableName.WorkflowIntegrations).onDelete("CASCADE");
|
||||
tb.string("teamId").notNullable();
|
||||
tb.string("teamName").notNullable();
|
||||
tb.string("slackUserId").notNullable();
|
||||
@ -18,7 +30,6 @@ export async function up(knex: Knex): Promise<void> {
|
||||
tb.binary("encryptedBotAccessToken").notNullable();
|
||||
tb.string("slackBotId").notNullable();
|
||||
tb.string("slackBotUserId").notNullable();
|
||||
tb.unique(["orgId", "slug"]);
|
||||
tb.timestamps(true, true, true);
|
||||
});
|
||||
|
||||
@ -58,9 +69,12 @@ export async function down(knex: Knex): Promise<void> {
|
||||
await knex.schema.dropTableIfExists(TableName.ProjectSlackConfigs);
|
||||
await dropOnUpdateTrigger(knex, TableName.ProjectSlackConfigs);
|
||||
|
||||
await knex.schema.dropTableIfExists(TableName.SlackIntegrations);
|
||||
await dropOnUpdateTrigger(knex, TableName.SlackIntegrations);
|
||||
|
||||
await knex.schema.dropTableIfExists(TableName.AdminSlackConfig);
|
||||
await dropOnUpdateTrigger(knex, TableName.AdminSlackConfig);
|
||||
|
||||
await knex.schema.dropTableIfExists(TableName.SlackIntegrations);
|
||||
await dropOnUpdateTrigger(knex, TableName.SlackIntegrations);
|
||||
await knex.schema.dropTableIfExists(TableName.WorkflowIntegrations);
|
||||
await dropOnUpdateTrigger(knex, TableName.WorkflowIntegrations);
|
||||
}
|
||||
|
@ -112,3 +112,4 @@ export * from "./user-encryption-keys";
|
||||
export * from "./user-group-membership";
|
||||
export * from "./users";
|
||||
export * from "./webhooks";
|
||||
export * from "./workflow-integrations";
|
||||
|
@ -115,6 +115,7 @@ export enum TableName {
|
||||
InternalKmsKeyVersion = "internal_kms_key_version",
|
||||
// @depreciated
|
||||
KmsKeyVersion = "kms_key_versions",
|
||||
WorkflowIntegrations = "workflow_integrations",
|
||||
SlackIntegrations = "slack_integrations",
|
||||
ProjectSlackConfigs = "project_slack_configs",
|
||||
AdminSlackConfig = "admin_slack_configs"
|
||||
|
@ -11,9 +11,6 @@ import { TImmutableDBKeys } from "./models";
|
||||
|
||||
export const SlackIntegrationsSchema = z.object({
|
||||
id: z.string().uuid(),
|
||||
slug: z.string(),
|
||||
orgId: z.string().uuid(),
|
||||
description: z.string().nullable().optional(),
|
||||
teamId: z.string(),
|
||||
teamName: z.string(),
|
||||
slackUserId: z.string(),
|
||||
|
22
backend/src/db/schemas/workflow-integrations.ts
Normal file
22
backend/src/db/schemas/workflow-integrations.ts
Normal file
@ -0,0 +1,22 @@
|
||||
// Code generated by automation script, DO NOT EDIT.
|
||||
// Automated by pulling database and generating zod schema
|
||||
// To update. Just run npm run generate:schema
|
||||
// Written by akhilmhdh.
|
||||
|
||||
import { z } from "zod";
|
||||
|
||||
import { TImmutableDBKeys } from "./models";
|
||||
|
||||
export const WorkflowIntegrationsSchema = z.object({
|
||||
id: z.string().uuid(),
|
||||
integration: z.string(),
|
||||
slug: z.string(),
|
||||
orgId: z.string().uuid(),
|
||||
description: z.string().nullable().optional(),
|
||||
createdAt: z.date(),
|
||||
updatedAt: z.date()
|
||||
});
|
||||
|
||||
export type TWorkflowIntegrations = z.infer<typeof WorkflowIntegrationsSchema>;
|
||||
export type TWorkflowIntegrationsInsert = Omit<z.input<typeof WorkflowIntegrationsSchema>, TImmutableDBKeys>;
|
||||
export type TWorkflowIntegrationsUpdate = Partial<Omit<z.input<typeof WorkflowIntegrationsSchema>, TImmutableDBKeys>>;
|
@ -198,6 +198,8 @@ import { userAliasDALFactory } from "@app/services/user-alias/user-alias-dal";
|
||||
import { userEngagementServiceFactory } from "@app/services/user-engagement/user-engagement-service";
|
||||
import { webhookDALFactory } from "@app/services/webhook/webhook-dal";
|
||||
import { webhookServiceFactory } from "@app/services/webhook/webhook-service";
|
||||
import { workflowIntegrationDALFactory } from "@app/services/workflow-integration/workflow-integration-dal";
|
||||
import { workflowIntegrationServiceFactory } from "@app/services/workflow-integration/workflow-integration-service";
|
||||
|
||||
import { injectAuditLogInfo } from "../plugins/audit-log";
|
||||
import { injectIdentity } from "../plugins/auth/inject-identity";
|
||||
@ -329,6 +331,7 @@ export const registerRoutes = async (
|
||||
const slackIntegrationDAL = slackIntegrationDALFactory(db);
|
||||
const projectSlackConfigDAL = projectSlackConfigDALFactory(db);
|
||||
const adminSlackConfigDAL = adminSlackConfigDALFactory(db);
|
||||
const workflowIntegrationDAL = workflowIntegrationDALFactory(db);
|
||||
|
||||
const permissionService = permissionServiceFactory({
|
||||
permissionDAL,
|
||||
@ -1170,7 +1173,13 @@ export const registerRoutes = async (
|
||||
permissionService,
|
||||
kmsService,
|
||||
slackIntegrationDAL,
|
||||
adminSlackConfigDAL
|
||||
adminSlackConfigDAL,
|
||||
workflowIntegrationDAL
|
||||
});
|
||||
|
||||
const workflowIntegrationService = workflowIntegrationServiceFactory({
|
||||
permissionService,
|
||||
workflowIntegrationDAL
|
||||
});
|
||||
|
||||
await superAdminService.initServerCfg();
|
||||
@ -1255,7 +1264,8 @@ export const registerRoutes = async (
|
||||
userEngagement: userEngagementService,
|
||||
externalKms: externalKmsService,
|
||||
orgAdmin: orgAdminService,
|
||||
slack: slackService
|
||||
slack: slackService,
|
||||
workflowIntegration: workflowIntegrationService
|
||||
});
|
||||
|
||||
const cronJobs: CronJob[] = [];
|
||||
|
@ -35,6 +35,7 @@ import { registerUserActionRouter } from "./user-action-router";
|
||||
import { registerUserEngagementRouter } from "./user-engagement-router";
|
||||
import { registerUserRouter } from "./user-router";
|
||||
import { registerWebhookRouter } from "./webhook-router";
|
||||
import { registerWorkflowIntegrationRouter } from "./workflow-integration-router";
|
||||
|
||||
export const registerV1Routes = async (server: FastifyZodProvider) => {
|
||||
await server.register(registerSsoRouter, { prefix: "/sso" });
|
||||
@ -64,6 +65,7 @@ export const registerV1Routes = async (server: FastifyZodProvider) => {
|
||||
|
||||
await server.register(
|
||||
async (workflowIntegrationRouter) => {
|
||||
await workflowIntegrationRouter.register(registerWorkflowIntegrationRouter);
|
||||
await workflowIntegrationRouter.register(registerSlackRouter, { prefix: "/slack" });
|
||||
},
|
||||
{ prefix: "/workflow-integrations" }
|
||||
|
@ -1,12 +1,23 @@
|
||||
import { z } from "zod";
|
||||
|
||||
import { SlackIntegrationsSchema } from "@app/db/schemas";
|
||||
import { SlackIntegrationsSchema, WorkflowIntegrationsSchema } from "@app/db/schemas";
|
||||
import { EventType } from "@app/ee/services/audit-log/audit-log-types";
|
||||
import { getConfig } from "@app/lib/config/env";
|
||||
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
|
||||
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
||||
import { AuthMode } from "@app/services/auth/auth-type";
|
||||
|
||||
const sanitizedSlackIntegrationSchema = WorkflowIntegrationsSchema.pick({
|
||||
id: true,
|
||||
description: true,
|
||||
slug: true,
|
||||
integration: true
|
||||
}).merge(
|
||||
SlackIntegrationsSchema.pick({
|
||||
teamName: true
|
||||
})
|
||||
);
|
||||
|
||||
export const registerSlackRouter = async (server: FastifyZodProvider) => {
|
||||
const appCfg = getConfig();
|
||||
|
||||
@ -69,7 +80,7 @@ export const registerSlackRouter = async (server: FastifyZodProvider) => {
|
||||
}
|
||||
],
|
||||
querystring: z.object({
|
||||
slackIntegrationId: z.string()
|
||||
id: z.string()
|
||||
}),
|
||||
response: {
|
||||
200: z.string()
|
||||
@ -82,7 +93,7 @@ export const registerSlackRouter = async (server: FastifyZodProvider) => {
|
||||
actorId: req.permission.id,
|
||||
actorAuthMethod: req.permission.authMethod,
|
||||
actorOrgId: req.permission.orgId,
|
||||
id: req.query.slackIntegrationId
|
||||
id: req.query.id
|
||||
});
|
||||
|
||||
await server.services.auditLog.createAuditLog({
|
||||
@ -91,7 +102,7 @@ export const registerSlackRouter = async (server: FastifyZodProvider) => {
|
||||
event: {
|
||||
type: EventType.ATTEMPT_REINSTALL_SLACK_INTEGRATION,
|
||||
metadata: {
|
||||
id: req.query.slackIntegrationId
|
||||
id: req.query.id
|
||||
}
|
||||
}
|
||||
});
|
||||
@ -113,12 +124,7 @@ export const registerSlackRouter = async (server: FastifyZodProvider) => {
|
||||
}
|
||||
],
|
||||
response: {
|
||||
200: SlackIntegrationsSchema.pick({
|
||||
id: true,
|
||||
slug: true,
|
||||
description: true,
|
||||
teamName: true
|
||||
}).array()
|
||||
200: sanitizedSlackIntegrationSchema.array()
|
||||
}
|
||||
},
|
||||
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||
@ -136,7 +142,7 @@ export const registerSlackRouter = async (server: FastifyZodProvider) => {
|
||||
|
||||
server.route({
|
||||
method: "DELETE",
|
||||
url: "/:slackIntegrationId",
|
||||
url: "/:id",
|
||||
config: {
|
||||
rateLimit: writeLimit
|
||||
},
|
||||
@ -147,15 +153,10 @@ export const registerSlackRouter = async (server: FastifyZodProvider) => {
|
||||
}
|
||||
],
|
||||
params: z.object({
|
||||
slackIntegrationId: z.string()
|
||||
id: z.string()
|
||||
}),
|
||||
response: {
|
||||
200: SlackIntegrationsSchema.pick({
|
||||
id: true,
|
||||
slug: true,
|
||||
description: true,
|
||||
teamName: true
|
||||
})
|
||||
200: sanitizedSlackIntegrationSchema
|
||||
}
|
||||
},
|
||||
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||
@ -165,7 +166,7 @@ export const registerSlackRouter = async (server: FastifyZodProvider) => {
|
||||
actorId: req.permission.id,
|
||||
actorAuthMethod: req.permission.authMethod,
|
||||
actorOrgId: req.permission.orgId,
|
||||
id: req.params.slackIntegrationId
|
||||
id: req.params.id
|
||||
});
|
||||
|
||||
await server.services.auditLog.createAuditLog({
|
||||
@ -185,7 +186,7 @@ export const registerSlackRouter = async (server: FastifyZodProvider) => {
|
||||
|
||||
server.route({
|
||||
method: "GET",
|
||||
url: "/:slackIntegrationId",
|
||||
url: "/:id",
|
||||
config: {
|
||||
rateLimit: readLimit
|
||||
},
|
||||
@ -196,15 +197,10 @@ export const registerSlackRouter = async (server: FastifyZodProvider) => {
|
||||
}
|
||||
],
|
||||
params: z.object({
|
||||
slackIntegrationId: z.string()
|
||||
id: z.string()
|
||||
}),
|
||||
response: {
|
||||
200: SlackIntegrationsSchema.pick({
|
||||
id: true,
|
||||
slug: true,
|
||||
description: true,
|
||||
teamName: true
|
||||
})
|
||||
200: sanitizedSlackIntegrationSchema
|
||||
}
|
||||
},
|
||||
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||
@ -214,7 +210,7 @@ export const registerSlackRouter = async (server: FastifyZodProvider) => {
|
||||
actorId: req.permission.id,
|
||||
actorAuthMethod: req.permission.authMethod,
|
||||
actorOrgId: req.permission.orgId,
|
||||
id: req.params.slackIntegrationId
|
||||
id: req.params.id
|
||||
});
|
||||
|
||||
await server.services.auditLog.createAuditLog({
|
||||
@ -234,7 +230,7 @@ export const registerSlackRouter = async (server: FastifyZodProvider) => {
|
||||
|
||||
server.route({
|
||||
method: "GET",
|
||||
url: "/:slackIntegrationId/channels",
|
||||
url: "/:id/channels",
|
||||
config: {
|
||||
rateLimit: readLimit
|
||||
},
|
||||
@ -245,7 +241,7 @@ export const registerSlackRouter = async (server: FastifyZodProvider) => {
|
||||
}
|
||||
],
|
||||
params: z.object({
|
||||
slackIntegrationId: z.string()
|
||||
id: z.string()
|
||||
}),
|
||||
response: {
|
||||
200: z
|
||||
@ -263,7 +259,7 @@ export const registerSlackRouter = async (server: FastifyZodProvider) => {
|
||||
actorId: req.permission.id,
|
||||
actorAuthMethod: req.permission.authMethod,
|
||||
actorOrgId: req.permission.orgId,
|
||||
id: req.params.slackIntegrationId
|
||||
id: req.params.id
|
||||
});
|
||||
|
||||
return slackChannels;
|
||||
@ -272,7 +268,7 @@ export const registerSlackRouter = async (server: FastifyZodProvider) => {
|
||||
|
||||
server.route({
|
||||
method: "PATCH",
|
||||
url: "/:slackIntegrationId",
|
||||
url: "/:id",
|
||||
config: {
|
||||
rateLimit: readLimit
|
||||
},
|
||||
@ -283,19 +279,14 @@ export const registerSlackRouter = async (server: FastifyZodProvider) => {
|
||||
}
|
||||
],
|
||||
params: z.object({
|
||||
slackIntegrationId: z.string()
|
||||
id: z.string()
|
||||
}),
|
||||
body: z.object({
|
||||
slug: z.string().optional(),
|
||||
description: z.string().optional()
|
||||
}),
|
||||
response: {
|
||||
200: SlackIntegrationsSchema.pick({
|
||||
id: true,
|
||||
slug: true,
|
||||
description: true,
|
||||
teamName: true
|
||||
})
|
||||
200: sanitizedSlackIntegrationSchema
|
||||
}
|
||||
},
|
||||
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||
@ -305,7 +296,7 @@ export const registerSlackRouter = async (server: FastifyZodProvider) => {
|
||||
actorId: req.permission.id,
|
||||
actorAuthMethod: req.permission.authMethod,
|
||||
actorOrgId: req.permission.orgId,
|
||||
id: req.params.slackIntegrationId,
|
||||
id: req.params.id,
|
||||
...req.body
|
||||
});
|
||||
|
||||
|
42
backend/src/server/routes/v1/workflow-integration-router.ts
Normal file
42
backend/src/server/routes/v1/workflow-integration-router.ts
Normal file
@ -0,0 +1,42 @@
|
||||
import { WorkflowIntegrationsSchema } from "@app/db/schemas";
|
||||
import { readLimit } from "@app/server/config/rateLimiter";
|
||||
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
||||
import { AuthMode } from "@app/services/auth/auth-type";
|
||||
|
||||
const sanitizedWorkflowIntegrationSchema = WorkflowIntegrationsSchema.pick({
|
||||
id: true,
|
||||
description: true,
|
||||
slug: true,
|
||||
integration: true
|
||||
});
|
||||
|
||||
export const registerWorkflowIntegrationRouter = async (server: FastifyZodProvider) => {
|
||||
server.route({
|
||||
method: "GET",
|
||||
url: "/",
|
||||
config: {
|
||||
rateLimit: readLimit
|
||||
},
|
||||
schema: {
|
||||
security: [
|
||||
{
|
||||
bearerAuth: []
|
||||
}
|
||||
],
|
||||
response: {
|
||||
200: sanitizedWorkflowIntegrationSchema.array()
|
||||
}
|
||||
},
|
||||
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||
handler: async (req) => {
|
||||
const workflowIntegrations = await server.services.workflowIntegration.getIntegrationsByOrg({
|
||||
actor: req.permission.type,
|
||||
actorId: req.permission.id,
|
||||
actorAuthMethod: req.permission.authMethod,
|
||||
actorOrgId: req.permission.orgId
|
||||
});
|
||||
|
||||
return workflowIntegrations;
|
||||
}
|
||||
});
|
||||
};
|
@ -81,7 +81,7 @@ type TProjectServiceFactoryDep = {
|
||||
projectKeyDAL: Pick<TProjectKeyDALFactory, "create" | "findLatestProjectKey" | "delete" | "find" | "insertMany">;
|
||||
projectMembershipDAL: Pick<TProjectMembershipDALFactory, "create" | "findProjectGhostUser" | "findOne">;
|
||||
projectSlackConfigDAL: Pick<TProjectSlackConfigDALFactory, "findOne" | "transaction" | "updateById" | "create">;
|
||||
slackIntegrationDAL: Pick<TSlackIntegrationDALFactory, "findById">;
|
||||
slackIntegrationDAL: Pick<TSlackIntegrationDALFactory, "findById" | "findByIdWithWorkflowIntegrationDetails">;
|
||||
projectUserMembershipRoleDAL: Pick<TProjectUserMembershipRoleDALFactory, "create">;
|
||||
certificateAuthorityDAL: Pick<TCertificateAuthorityDALFactory, "find">;
|
||||
certificateDAL: Pick<TCertificateDALFactory, "find" | "countCertificatesInProject">;
|
||||
@ -965,7 +965,7 @@ export const projectServiceFactory = ({
|
||||
});
|
||||
}
|
||||
|
||||
const slackIntegration = await slackIntegrationDAL.findById(slackIntegrationId);
|
||||
const slackIntegration = await slackIntegrationDAL.findByIdWithWorkflowIntegrationDetails(slackIntegrationId);
|
||||
if (!slackIntegration) {
|
||||
throw new NotFoundError({
|
||||
message: "Slack integration not found"
|
||||
|
@ -1,11 +1,56 @@
|
||||
import { Knex } from "knex";
|
||||
|
||||
import { TDbClient } from "@app/db";
|
||||
import { TableName } from "@app/db/schemas";
|
||||
import { ormify } from "@app/lib/knex";
|
||||
import { TableName, TSlackIntegrations, TWorkflowIntegrations } from "@app/db/schemas";
|
||||
import { DatabaseError } from "@app/lib/errors";
|
||||
import { ormify, selectAllTableCols } from "@app/lib/knex";
|
||||
|
||||
export type TSlackIntegrationDALFactory = ReturnType<typeof slackIntegrationDALFactory>;
|
||||
|
||||
export const slackIntegrationDALFactory = (db: TDbClient) => {
|
||||
const slackIntegrationOrm = ormify(db, TableName.SlackIntegrations);
|
||||
|
||||
return slackIntegrationOrm;
|
||||
const findByIdWithWorkflowIntegrationDetails = async (id: string, tx?: Knex) => {
|
||||
try {
|
||||
return await (tx || db.replicaNode())(TableName.SlackIntegrations)
|
||||
.join(
|
||||
TableName.WorkflowIntegrations,
|
||||
`${TableName.SlackIntegrations}.id`,
|
||||
`${TableName.WorkflowIntegrations}.id`
|
||||
)
|
||||
.select(selectAllTableCols(TableName.SlackIntegrations))
|
||||
.select(db.ref("orgId").withSchema(TableName.WorkflowIntegrations))
|
||||
.select(db.ref("description").withSchema(TableName.WorkflowIntegrations))
|
||||
.select(db.ref("integration").withSchema(TableName.WorkflowIntegrations))
|
||||
.select(db.ref("slug").withSchema(TableName.WorkflowIntegrations))
|
||||
.where(`${TableName.WorkflowIntegrations}.id`, "=", id)
|
||||
.first();
|
||||
} catch (error) {
|
||||
throw new DatabaseError({ error, name: "Find by ID with Workflow integration details" });
|
||||
}
|
||||
};
|
||||
|
||||
const findWithWorkflowIntegrationDetails = async (
|
||||
filter: Partial<TSlackIntegrations> & Partial<TWorkflowIntegrations>,
|
||||
tx?: Knex
|
||||
) => {
|
||||
try {
|
||||
return await (tx || db.replicaNode())(TableName.SlackIntegrations)
|
||||
.join(
|
||||
TableName.WorkflowIntegrations,
|
||||
`${TableName.SlackIntegrations}.id`,
|
||||
`${TableName.WorkflowIntegrations}.id`
|
||||
)
|
||||
.select(selectAllTableCols(TableName.SlackIntegrations))
|
||||
.select(db.ref("orgId").withSchema(TableName.WorkflowIntegrations))
|
||||
.select(db.ref("description").withSchema(TableName.WorkflowIntegrations))
|
||||
.select(db.ref("integration").withSchema(TableName.WorkflowIntegrations))
|
||||
.select(db.ref("slug").withSchema(TableName.WorkflowIntegrations))
|
||||
.where(filter);
|
||||
} catch (error) {
|
||||
throw new DatabaseError({ error, name: "Find with Workflow integration details" });
|
||||
}
|
||||
};
|
||||
|
||||
return { ...slackIntegrationOrm, findByIdWithWorkflowIntegrationDetails, findWithWorkflowIntegrationDetails };
|
||||
};
|
||||
|
@ -8,6 +8,8 @@ import { BadRequestError, NotFoundError } from "@app/lib/errors";
|
||||
|
||||
import { TKmsServiceFactory } from "../kms/kms-service";
|
||||
import { KmsDataKey } from "../kms/kms-types";
|
||||
import { TWorkflowIntegrationDALFactory } from "../workflow-integration/workflow-integration-dal";
|
||||
import { WorkflowIntegration } from "../workflow-integration/workflow-integration-types";
|
||||
import { TAdminSlackConfigDALFactory } from "./admin-slack-config-dal";
|
||||
import { fetchSlackChannels, getAdminSlackCredentials } from "./slack-fns";
|
||||
import { TSlackIntegrationDALFactory } from "./slack-integration-dal";
|
||||
@ -24,10 +26,18 @@ import {
|
||||
} from "./slack-types";
|
||||
|
||||
type TSlackServiceFactoryDep = {
|
||||
slackIntegrationDAL: Pick<TSlackIntegrationDALFactory, "find" | "findById" | "deleteById" | "updateById" | "create">;
|
||||
slackIntegrationDAL: Pick<
|
||||
TSlackIntegrationDALFactory,
|
||||
| "deleteById"
|
||||
| "updateById"
|
||||
| "create"
|
||||
| "findByIdWithWorkflowIntegrationDetails"
|
||||
| "findWithWorkflowIntegrationDetails"
|
||||
>;
|
||||
permissionService: Pick<TPermissionServiceFactory, "getProjectPermission" | "getOrgPermission">;
|
||||
kmsService: Pick<TKmsServiceFactory, "createCipherPairWithDataKey" | "encryptWithRootKey" | "decryptWithRootKey">;
|
||||
adminSlackConfigDAL: Pick<TAdminSlackConfigDALFactory, "findById">;
|
||||
workflowIntegrationDAL: Pick<TWorkflowIntegrationDALFactory, "transaction" | "create" | "updateById" | "deleteById">;
|
||||
};
|
||||
|
||||
export type TSlackServiceFactory = ReturnType<typeof slackServiceFactory>;
|
||||
@ -36,7 +46,8 @@ export const slackServiceFactory = ({
|
||||
permissionService,
|
||||
slackIntegrationDAL,
|
||||
kmsService,
|
||||
adminSlackConfigDAL
|
||||
adminSlackConfigDAL,
|
||||
workflowIntegrationDAL
|
||||
}: TSlackServiceFactoryDep) => {
|
||||
const completeSlackIntegration = async ({
|
||||
orgId,
|
||||
@ -59,17 +70,31 @@ export const slackServiceFactory = ({
|
||||
plainText: Buffer.from(botAccessToken, "utf8")
|
||||
});
|
||||
|
||||
await slackIntegrationDAL.create({
|
||||
orgId,
|
||||
slug,
|
||||
description,
|
||||
teamId,
|
||||
teamName,
|
||||
slackUserId,
|
||||
slackAppId,
|
||||
slackBotId,
|
||||
slackBotUserId,
|
||||
encryptedBotAccessToken
|
||||
await workflowIntegrationDAL.transaction(async (tx) => {
|
||||
const workflowIntegration = await workflowIntegrationDAL.create(
|
||||
{
|
||||
description,
|
||||
orgId,
|
||||
slug,
|
||||
integration: WorkflowIntegration.SLACK
|
||||
},
|
||||
tx
|
||||
);
|
||||
|
||||
await slackIntegrationDAL.create(
|
||||
{
|
||||
// @ts-expect-error id is kept as fixed because it is always equal to the workflow integration ID
|
||||
id: workflowIntegration.id,
|
||||
teamId,
|
||||
teamName,
|
||||
slackUserId,
|
||||
slackAppId,
|
||||
slackBotId,
|
||||
slackBotUserId,
|
||||
encryptedBotAccessToken
|
||||
},
|
||||
tx
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
@ -83,7 +108,13 @@ export const slackServiceFactory = ({
|
||||
slackBotId,
|
||||
slackBotUserId
|
||||
}: TReinstallSlackIntegrationDTO) => {
|
||||
const slackIntegration = await slackIntegrationDAL.findById(id);
|
||||
const slackIntegration = await slackIntegrationDAL.findByIdWithWorkflowIntegrationDetails(id);
|
||||
|
||||
if (!slackIntegration) {
|
||||
throw new NotFoundError({
|
||||
message: "Slack integration not found"
|
||||
});
|
||||
}
|
||||
|
||||
const { encryptor: orgDataKeyEncryptor } = await kmsService.createCipherPairWithDataKey({
|
||||
orgId: slackIntegration.orgId,
|
||||
@ -228,7 +259,7 @@ export const slackServiceFactory = ({
|
||||
|
||||
const getReinstallUrl = async ({ actorId, actor, actorOrgId, actorAuthMethod, id }: TGetReinstallUrlDTO) => {
|
||||
const appCfg = getConfig();
|
||||
const slackIntegration = await slackIntegrationDAL.findById(id);
|
||||
const slackIntegration = await slackIntegrationDAL.findByIdWithWorkflowIntegrationDetails(id);
|
||||
|
||||
if (!slackIntegration) {
|
||||
throw new NotFoundError({
|
||||
@ -275,7 +306,7 @@ export const slackServiceFactory = ({
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Create, OrgPermissionSubjects.Settings);
|
||||
|
||||
const slackIntegrations = await slackIntegrationDAL.find({
|
||||
const slackIntegrations = await slackIntegrationDAL.findWithWorkflowIntegrationDetails({
|
||||
orgId: actorOrgId
|
||||
});
|
||||
|
||||
@ -289,7 +320,7 @@ export const slackServiceFactory = ({
|
||||
actorAuthMethod,
|
||||
id
|
||||
}: TGetSlackIntegrationByIdDTO) => {
|
||||
const slackIntegration = await slackIntegrationDAL.findById(id);
|
||||
const slackIntegration = await slackIntegrationDAL.findByIdWithWorkflowIntegrationDetails(id);
|
||||
if (!slackIntegration) {
|
||||
throw new NotFoundError({
|
||||
message: "Slack integration not found."
|
||||
@ -316,7 +347,7 @@ export const slackServiceFactory = ({
|
||||
actorAuthMethod,
|
||||
id
|
||||
}: TGetSlackIntegrationChannelsDTO) => {
|
||||
const slackIntegration = await slackIntegrationDAL.findById(id);
|
||||
const slackIntegration = await slackIntegrationDAL.findByIdWithWorkflowIntegrationDetails(id);
|
||||
if (!slackIntegration) {
|
||||
throw new NotFoundError({
|
||||
message: "Slack integration not found."
|
||||
@ -354,7 +385,7 @@ export const slackServiceFactory = ({
|
||||
slug,
|
||||
description
|
||||
}: TUpdateSlackIntegrationDTO) => {
|
||||
const slackIntegration = await slackIntegrationDAL.findById(id);
|
||||
const slackIntegration = await slackIntegrationDAL.findByIdWithWorkflowIntegrationDetails(id);
|
||||
if (!slackIntegration) {
|
||||
throw new NotFoundError({
|
||||
message: "Slack integration not found"
|
||||
@ -371,9 +402,22 @@ export const slackServiceFactory = ({
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Edit, OrgPermissionSubjects.Settings);
|
||||
|
||||
return slackIntegrationDAL.updateById(slackIntegration.id, {
|
||||
slug,
|
||||
description
|
||||
return workflowIntegrationDAL.transaction(async (tx) => {
|
||||
await workflowIntegrationDAL.updateById(
|
||||
slackIntegration.id,
|
||||
{
|
||||
slug,
|
||||
description
|
||||
},
|
||||
tx
|
||||
);
|
||||
|
||||
const updatedIntegration = await slackIntegrationDAL.findByIdWithWorkflowIntegrationDetails(
|
||||
slackIntegration.id,
|
||||
tx
|
||||
);
|
||||
|
||||
return updatedIntegration!;
|
||||
});
|
||||
};
|
||||
|
||||
@ -384,7 +428,7 @@ export const slackServiceFactory = ({
|
||||
actorAuthMethod,
|
||||
id
|
||||
}: TDeleteSlackIntegrationDTO) => {
|
||||
const slackIntegration = await slackIntegrationDAL.findById(id);
|
||||
const slackIntegration = await slackIntegrationDAL.findByIdWithWorkflowIntegrationDetails(id);
|
||||
if (!slackIntegration) {
|
||||
throw new NotFoundError({
|
||||
message: "Slack integration not found"
|
||||
@ -401,7 +445,9 @@ export const slackServiceFactory = ({
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Delete, OrgPermissionSubjects.Settings);
|
||||
|
||||
return slackIntegrationDAL.deleteById(id);
|
||||
await workflowIntegrationDAL.deleteById(id);
|
||||
|
||||
return slackIntegration;
|
||||
};
|
||||
|
||||
return {
|
||||
|
@ -0,0 +1,11 @@
|
||||
import { TDbClient } from "@app/db";
|
||||
import { TableName } from "@app/db/schemas";
|
||||
import { ormify } from "@app/lib/knex";
|
||||
|
||||
export type TWorkflowIntegrationDALFactory = ReturnType<typeof workflowIntegrationDALFactory>;
|
||||
|
||||
export const workflowIntegrationDALFactory = (db: TDbClient) => {
|
||||
const workflowIntegrationOrm = ormify(db, TableName.WorkflowIntegrations);
|
||||
|
||||
return workflowIntegrationOrm;
|
||||
};
|
@ -0,0 +1,43 @@
|
||||
import { ForbiddenError } from "@casl/ability";
|
||||
|
||||
import { OrgPermissionActions, OrgPermissionSubjects } from "@app/ee/services/permission/org-permission";
|
||||
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
|
||||
|
||||
import { TWorkflowIntegrationDALFactory } from "./workflow-integration-dal";
|
||||
import { TGetWorkflowIntegrationsByOrg } from "./workflow-integration-types";
|
||||
|
||||
type TWorkflowIntegrationServiceFactoryDep = {
|
||||
workflowIntegrationDAL: Pick<TWorkflowIntegrationDALFactory, "find">;
|
||||
permissionService: Pick<TPermissionServiceFactory, "getProjectPermission" | "getOrgPermission">;
|
||||
};
|
||||
|
||||
export type TWorkflowIntegrationServiceFactory = ReturnType<typeof workflowIntegrationServiceFactory>;
|
||||
|
||||
export const workflowIntegrationServiceFactory = ({
|
||||
workflowIntegrationDAL,
|
||||
permissionService
|
||||
}: TWorkflowIntegrationServiceFactoryDep) => {
|
||||
const getIntegrationsByOrg = async ({
|
||||
actorId,
|
||||
actor,
|
||||
actorOrgId,
|
||||
actorAuthMethod
|
||||
}: TGetWorkflowIntegrationsByOrg) => {
|
||||
const { permission } = await permissionService.getOrgPermission(
|
||||
actor,
|
||||
actorId,
|
||||
actorOrgId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
);
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Settings);
|
||||
|
||||
return workflowIntegrationDAL.find({
|
||||
orgId: actorOrgId
|
||||
});
|
||||
};
|
||||
return {
|
||||
getIntegrationsByOrg
|
||||
};
|
||||
};
|
@ -0,0 +1,7 @@
|
||||
import { TOrgPermission } from "@app/lib/types";
|
||||
|
||||
export enum WorkflowIntegration {
|
||||
SLACK = "slack"
|
||||
}
|
||||
|
||||
export type TGetWorkflowIntegrationsByOrg = Omit<TOrgPermission, "orgId">;
|
@ -8,5 +8,6 @@ export {
|
||||
fetchSlackReinstallUrl,
|
||||
useGetSlackIntegrationById,
|
||||
useGetSlackIntegrationChannels,
|
||||
useGetSlackIntegrations
|
||||
useGetSlackIntegrations,
|
||||
useGetWorkflowIntegrations
|
||||
} from "./queries";
|
||||
|
@ -37,7 +37,7 @@ export const useDeleteSlackIntegration = () => {
|
||||
},
|
||||
onSuccess: (_, { orgId, id }) => {
|
||||
queryClient.invalidateQueries(workflowIntegrationKeys.getSlackIntegration(id));
|
||||
queryClient.invalidateQueries(workflowIntegrationKeys.getSlackIntegrations(orgId));
|
||||
queryClient.invalidateQueries(workflowIntegrationKeys.getIntegrations(orgId));
|
||||
}
|
||||
});
|
||||
};
|
||||
|
@ -2,9 +2,10 @@ import { useQuery } from "@tanstack/react-query";
|
||||
|
||||
import { apiRequest } from "@app/config/request";
|
||||
|
||||
import { SlackIntegration, SlackIntegrationChannel } from "./types";
|
||||
import { SlackIntegration, SlackIntegrationChannel, WorkflowIntegration } from "./types";
|
||||
|
||||
export const workflowIntegrationKeys = {
|
||||
getIntegrations: (orgId?: string) => [{ orgId }, "workflow-integrations"],
|
||||
getSlackIntegrations: (orgId?: string) => [{ orgId }, "slack-workflow-integrations"],
|
||||
getSlackIntegration: (id?: string) => [{ id }, "slack-workflow-integration"],
|
||||
getSlackIntegrationChannels: (id?: string) => [{ id }, "slack-workflow-integration-channels"]
|
||||
@ -27,14 +28,10 @@ export const fetchSlackInstallUrl = async ({
|
||||
return data;
|
||||
};
|
||||
|
||||
export const fetchSlackReinstallUrl = async ({
|
||||
slackIntegrationId
|
||||
}: {
|
||||
slackIntegrationId: string;
|
||||
}) => {
|
||||
export const fetchSlackReinstallUrl = async ({ id }: { id: string }) => {
|
||||
const { data } = await apiRequest.get<string>("/api/v1/workflow-integrations/slack/reinstall", {
|
||||
params: {
|
||||
slackIntegrationId
|
||||
id
|
||||
}
|
||||
});
|
||||
|
||||
@ -63,6 +60,12 @@ export const fetchSlackIntegrationChannels = async (id?: string) => {
|
||||
return data;
|
||||
};
|
||||
|
||||
export const fetchWorkflowIntegrations = async () => {
|
||||
const { data } = await apiRequest.get<WorkflowIntegration[]>("/api/v1/workflow-integrations");
|
||||
|
||||
return data;
|
||||
};
|
||||
|
||||
export const useGetSlackIntegrations = (orgId?: string) =>
|
||||
useQuery({
|
||||
queryKey: workflowIntegrationKeys.getSlackIntegrations(orgId),
|
||||
@ -83,3 +86,10 @@ export const useGetSlackIntegrationChannels = (id?: string) =>
|
||||
queryFn: () => fetchSlackIntegrationChannels(id),
|
||||
enabled: Boolean(id)
|
||||
});
|
||||
|
||||
export const useGetWorkflowIntegrations = (id?: string) =>
|
||||
useQuery({
|
||||
queryKey: workflowIntegrationKeys.getIntegrations(id),
|
||||
queryFn: () => fetchWorkflowIntegrations(),
|
||||
enabled: Boolean(id)
|
||||
});
|
||||
|
@ -1,7 +1,14 @@
|
||||
export enum WorkflowIntegrationPlatform {
|
||||
SLACK = "Slack"
|
||||
SLACK = "slack"
|
||||
}
|
||||
|
||||
export type WorkflowIntegration = {
|
||||
id: string;
|
||||
slug: string;
|
||||
description: string;
|
||||
integration: WorkflowIntegrationPlatform;
|
||||
};
|
||||
|
||||
export type SlackIntegration = {
|
||||
id: string;
|
||||
slug: string;
|
||||
|
@ -11,9 +11,12 @@ type Props = {
|
||||
};
|
||||
|
||||
export const IntegrationFormDetails = ({ isOpen, id, onOpenChange, workflowPlatform }: Props) => {
|
||||
const modalTitle =
|
||||
workflowPlatform === WorkflowIntegrationPlatform.SLACK ? "Slack integration" : "Integration";
|
||||
|
||||
return (
|
||||
<Modal isOpen={isOpen} onOpenChange={onOpenChange}>
|
||||
<ModalContent title={`${WorkflowIntegrationPlatform.SLACK} integration`}>
|
||||
<ModalContent title={modalTitle}>
|
||||
{workflowPlatform === WorkflowIntegrationPlatform.SLACK && (
|
||||
<SlackIntegrationForm id={id} onClose={() => onOpenChange(false)} />
|
||||
)}
|
||||
|
@ -29,7 +29,7 @@ import { usePopUp } from "@app/hooks";
|
||||
import {
|
||||
fetchSlackReinstallUrl,
|
||||
useDeleteSlackIntegration,
|
||||
useGetSlackIntegrations
|
||||
useGetWorkflowIntegrations
|
||||
} from "@app/hooks/api";
|
||||
import { WorkflowIntegrationPlatform } from "@app/hooks/api/workflowIntegrations/types";
|
||||
|
||||
@ -46,8 +46,9 @@ export const OrgWorkflowIntegrationTab = withPermission(
|
||||
|
||||
const { currentOrg } = useOrganization();
|
||||
const router = useRouter();
|
||||
const { data: slackIntegrations, isLoading: isSlackIntegrationsLoading } =
|
||||
useGetSlackIntegrations(currentOrg?.id);
|
||||
const { data: workflowIntegrations, isLoading: isWorkflowIntegrationsLoading } =
|
||||
useGetWorkflowIntegrations(currentOrg?.id);
|
||||
|
||||
const { mutateAsync: deleteSlackIntegration } = useDeleteSlackIntegration();
|
||||
|
||||
const handleRemoveIntegration = async () => {
|
||||
@ -74,7 +75,7 @@ export const OrgWorkflowIntegrationTab = withPermission(
|
||||
if (platform === WorkflowIntegrationPlatform.SLACK) {
|
||||
try {
|
||||
const slackReinstallUrl = await fetchSlackReinstallUrl({
|
||||
slackIntegrationId: id
|
||||
id
|
||||
});
|
||||
|
||||
if (slackReinstallUrl) {
|
||||
@ -91,8 +92,6 @@ export const OrgWorkflowIntegrationTab = withPermission(
|
||||
}
|
||||
};
|
||||
|
||||
const isIntegrationsLoading = isSlackIntegrationsLoading;
|
||||
|
||||
return (
|
||||
<div className="mb-6 rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4">
|
||||
<div className="flex justify-between">
|
||||
@ -123,23 +122,25 @@ export const OrgWorkflowIntegrationTab = withPermission(
|
||||
</Tr>
|
||||
</THead>
|
||||
<TBody>
|
||||
{isIntegrationsLoading && (
|
||||
{isWorkflowIntegrationsLoading && (
|
||||
<TableSkeleton columns={2} innerKey="integrations-loading" />
|
||||
)}
|
||||
{!isIntegrationsLoading && slackIntegrations && slackIntegrations.length === 0 && (
|
||||
<Tr>
|
||||
<Td colSpan={5}>
|
||||
<EmptyState title="No workflow integrations found" icon={faGear} />
|
||||
</Td>
|
||||
</Tr>
|
||||
)}
|
||||
{slackIntegrations?.map((slackIntegration) => (
|
||||
<Tr key={slackIntegration.id}>
|
||||
{!isWorkflowIntegrationsLoading &&
|
||||
workflowIntegrations &&
|
||||
workflowIntegrations.length === 0 && (
|
||||
<Tr>
|
||||
<Td colSpan={5}>
|
||||
<EmptyState title="No workflow integrations found" icon={faGear} />
|
||||
</Td>
|
||||
</Tr>
|
||||
)}
|
||||
{workflowIntegrations?.map((workflowIntegration) => (
|
||||
<Tr key={workflowIntegration.id}>
|
||||
<Td className="flex max-w-xs items-center overflow-hidden text-ellipsis hover:overflow-auto hover:break-all">
|
||||
<FontAwesomeIcon icon={faSlack} />
|
||||
<div className="ml-2">SLACK</div>
|
||||
<div className="ml-2">{workflowIntegration.integration.toUpperCase()}</div>
|
||||
</Td>
|
||||
<Td>{slackIntegration.slug}</Td>
|
||||
<Td>{workflowIntegration.slug}</Td>
|
||||
<Td>
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild className="rounded-lg">
|
||||
@ -153,8 +154,8 @@ export const OrgWorkflowIntegrationTab = withPermission(
|
||||
e.stopPropagation();
|
||||
|
||||
handlePopUpOpen("integrationDetails", {
|
||||
id: slackIntegration.id,
|
||||
platform: WorkflowIntegrationPlatform.SLACK
|
||||
id: workflowIntegration.id,
|
||||
platform: workflowIntegration.integration
|
||||
});
|
||||
}}
|
||||
>
|
||||
@ -174,8 +175,8 @@ export const OrgWorkflowIntegrationTab = withPermission(
|
||||
e.stopPropagation();
|
||||
|
||||
triggerReinstall(
|
||||
WorkflowIntegrationPlatform.SLACK,
|
||||
slackIntegration.id
|
||||
workflowIntegration.integration,
|
||||
workflowIntegration.id
|
||||
);
|
||||
}}
|
||||
>
|
||||
@ -197,9 +198,9 @@ export const OrgWorkflowIntegrationTab = withPermission(
|
||||
e.stopPropagation();
|
||||
|
||||
handlePopUpOpen("removeIntegration", {
|
||||
id: slackIntegration.id,
|
||||
slug: slackIntegration.slug,
|
||||
platform: WorkflowIntegrationPlatform.SLACK
|
||||
id: workflowIntegration.id,
|
||||
slug: workflowIntegration.slug,
|
||||
platform: workflowIntegration.integration
|
||||
});
|
||||
}}
|
||||
>
|
||||
|
Reference in New Issue
Block a user