From 7d3a62cc4c8eff1f3f69314bfdfac486dd5e416c Mon Sep 17 00:00:00 2001 From: Sheen Capadngan Date: Mon, 20 May 2024 20:56:29 +0800 Subject: [PATCH] feat: added integration sync status --- ...40520064127_add-integration-sync-status.ts | 43 ++++++++++++++++++ backend/src/db/schemas/integrations.ts | 5 ++- backend/src/services/secret/secret-queue.ts | 45 +++++++++++++------ frontend/src/hooks/api/integrations/types.ts | 3 ++ .../IntegrationsSection.tsx | 36 ++++++++++++--- 5 files changed, 112 insertions(+), 20 deletions(-) create mode 100644 backend/src/db/migrations/20240520064127_add-integration-sync-status.ts diff --git a/backend/src/db/migrations/20240520064127_add-integration-sync-status.ts b/backend/src/db/migrations/20240520064127_add-integration-sync-status.ts new file mode 100644 index 000000000..74b828714 --- /dev/null +++ b/backend/src/db/migrations/20240520064127_add-integration-sync-status.ts @@ -0,0 +1,43 @@ +import { Knex } from "knex"; + +import { TableName } from "../schemas"; + +export async function up(knex: Knex): Promise { + const hasIsSyncedColumn = await knex.schema.hasColumn(TableName.Integration, "isSynced"); + const hasSyncMessageColumn = await knex.schema.hasColumn(TableName.Integration, "syncMessage"); + const hasLastSyncJobId = await knex.schema.hasColumn(TableName.Integration, "lastSyncJobId"); + + await knex.schema.alterTable(TableName.Integration, (t) => { + if (!hasIsSyncedColumn) { + t.boolean("isSynced").nullable(); + } + + if (!hasSyncMessageColumn) { + t.text("syncMessage").nullable(); + } + + if (!hasLastSyncJobId) { + t.string("lastSyncJobId").nullable(); + } + }); +} + +export async function down(knex: Knex): Promise { + const hasIsSyncedColumn = await knex.schema.hasColumn(TableName.Integration, "isSynced"); + const hasSyncMessageColumn = await knex.schema.hasColumn(TableName.Integration, "syncMessage"); + const hasLastSyncJobId = await knex.schema.hasColumn(TableName.Integration, "lastSyncJobId"); + + await knex.schema.alterTable(TableName.Integration, (t) => { + if (hasIsSyncedColumn) { + t.dropColumn("isSynced"); + } + + if (hasSyncMessageColumn) { + t.dropColumn("syncMessage"); + } + + if (hasLastSyncJobId) { + t.dropColumn("lastSyncJobId"); + } + }); +} diff --git a/backend/src/db/schemas/integrations.ts b/backend/src/db/schemas/integrations.ts index 203498c85..47cf9e627 100644 --- a/backend/src/db/schemas/integrations.ts +++ b/backend/src/db/schemas/integrations.ts @@ -28,7 +28,10 @@ export const IntegrationsSchema = z.object({ secretPath: z.string().default("/"), createdAt: z.date(), updatedAt: z.date(), - lastUsed: z.date().nullable().optional() + lastUsed: z.date().nullable().optional(), + isSynced: z.boolean().nullable().optional(), + syncMessage: z.string().nullable().optional(), + lastSyncJobId: z.string().nullable().optional() }); export type TIntegrations = z.infer; diff --git a/backend/src/services/secret/secret-queue.ts b/backend/src/services/secret/secret-queue.ts index e1c8d61af..f3e3f1731 100644 --- a/backend/src/services/secret/secret-queue.ts +++ b/backend/src/services/secret/secret-queue.ts @@ -463,20 +463,37 @@ export const secretQueueFactory = ({ }); } - await syncIntegrationSecrets({ - createManySecretsRawFn, - updateManySecretsRawFn, - integrationDAL, - integration, - integrationAuth, - secrets: Object.keys(suffixedSecrets).length !== 0 ? suffixedSecrets : secrets, - accessId: accessId as string, - accessToken, - appendices: { - prefix: metadata?.secretPrefix || "", - suffix: metadata?.secretSuffix || "" - } - }); + try { + await syncIntegrationSecrets({ + createManySecretsRawFn, + updateManySecretsRawFn, + integrationDAL, + integration, + integrationAuth, + secrets: Object.keys(suffixedSecrets).length !== 0 ? suffixedSecrets : secrets, + accessId: accessId as string, + accessToken, + appendices: { + prefix: metadata?.secretPrefix || "", + suffix: metadata?.secretSuffix || "" + } + }); + + await integrationDAL.updateById(integration.id, { + lastSyncJobId: job.id, + lastUsed: new Date(), + syncMessage: "", + isSynced: true + }); + } catch (err: unknown) { + logger.info("Secret integration sync error:", err); + await integrationDAL.updateById(integration.id, { + lastSyncJobId: job.id, + lastUsed: new Date(), + syncMessage: (err as Error)?.message, + isSynced: false + }); + } } logger.info("Secret integration sync ended: %s", job.id); diff --git a/frontend/src/hooks/api/integrations/types.ts b/frontend/src/hooks/api/integrations/types.ts index a67ce15a8..345e41b1a 100644 --- a/frontend/src/hooks/api/integrations/types.ts +++ b/frontend/src/hooks/api/integrations/types.ts @@ -29,6 +29,9 @@ export type TIntegration = { secretPath: string; createdAt: string; updatedAt: string; + lastUsed?: string; + isSynced?: boolean; + syncMessage?: string; __v: number; metadata?: { secretSuffix?: string; diff --git a/frontend/src/views/IntegrationsPage/components/IntegrationsSection/IntegrationsSection.tsx b/frontend/src/views/IntegrationsPage/components/IntegrationsSection/IntegrationsSection.tsx index 185cc411f..46ef496ea 100644 --- a/frontend/src/views/IntegrationsPage/components/IntegrationsSection/IntegrationsSection.tsx +++ b/frontend/src/views/IntegrationsPage/components/IntegrationsSection/IntegrationsSection.tsx @@ -1,6 +1,7 @@ import Link from "next/link"; -import { faArrowRight, faXmark } from "@fortawesome/free-solid-svg-icons"; +import { faArrowRight, faCheck, faXmark } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { format } from "date-fns"; import { integrationSlugNameMapping } from "public/data/frequentConstants"; import { ProjectPermissionCan } from "@app/components/permissions"; @@ -74,7 +75,7 @@ export const IntegrationsSection = ({ )} {!isLoading && isBotActive && ( -
+
{integrations?.map((integration) => (
-
+
{(integration.integration === "hashicorp-vault" && `${integration.app} - path: ${integration.path}`) || (integration.scope === "github-org" && `${integration.owner}`) || - (integration.integration === "aws-parameter-store" && `${integration.path}`) || + (integration.integration === "aws-parameter-store" && + `${integration.path}`) || (integration.scope?.startsWith("github-") && `${integration.owner}/${integration.app}`) || integration.app} @@ -188,6 +190,28 @@ export const IntegrationsSection = ({ )}
+ {!!integration.isSynced && !!integration.lastUsed && ( +
+
+ Last sync: {format(new Date(integration.lastUsed), "yyyy-MM-dd, hh:mm aaa")} +
+ + + +
+ )} handlePopUpToggle("deleteConfirmation", isOpen)} deleteKey={ (popUp?.deleteConfirmation?.data as TIntegration)?.app ||