feat: added user status signs for replication failure etc

This commit is contained in:
=
2024-05-31 23:10:02 +05:30
parent 25a615cbb3
commit b0c472b5e1
6 changed files with 292 additions and 194 deletions
@@ -6,6 +6,9 @@ export async function up(knex: Knex): Promise<void> {
if (await knex.schema.hasTable(TableName.SecretImport)) {
await knex.schema.alterTable(TableName.SecretImport, (t) => {
t.boolean("isReplication").defaultTo(false);
t.boolean("isReplicationSuccess").nullable();
t.text("replicationStatus").nullable();
t.datetime("lastReplicated").nullable();
});
}
@@ -38,6 +41,9 @@ export async function down(knex: Knex): Promise<void> {
if (await knex.schema.hasTable(TableName.SecretImport)) {
await knex.schema.alterTable(TableName.SecretImport, (t) => {
t.dropColumns("isReplication");
t.dropColumns("isReplicationSuccess");
t.dropColumns("replicationStatus");
t.dropColumns("lastReplicated");
});
}
+4 -1
View File
@@ -16,7 +16,10 @@ export const SecretImportsSchema = z.object({
createdAt: z.date(),
updatedAt: z.date(),
folderId: z.string().uuid(),
isReplication: z.boolean().default(false).nullable().optional()
isReplication: z.boolean().default(false).nullable().optional(),
isReplicationSuccess: z.boolean().nullable().optional(),
replicationStatus: z.string().nullable().optional(),
lastReplicated: z.date().nullable().optional()
});
export type TSecretImports = z.infer<typeof SecretImportsSchema>;
@@ -30,7 +30,7 @@ type TSecretReplicationServiceFactoryDep = {
"find" | "findByBlindIndexes" | "insertMany" | "bulkUpdate" | "delete" | "upsertSecretReferences"
>;
secretVersionDAL: Pick<TSecretVersionDALFactory, "find" | "insertMany" | "update" | "findLatestVersionMany">;
secretImportDAL: Pick<TSecretImportDALFactory, "find">;
secretImportDAL: Pick<TSecretImportDALFactory, "find" | "updateById">;
folderDAL: Pick<TSecretFolderDALFactory, "findSecretPathByFolderIds" | "findBySecretPath">;
secretVersionTagDAL: Pick<TSecretVersionTagDALFactory, "find" | "insertMany">;
secretQueueService: Pick<TSecretQueueFactory, "syncSecrets">;
@@ -115,6 +115,7 @@ export const secretReplicationServiceFactory = ({
try {
/* eslint-disable no-await-in-loop */
for (const secretImport of secretImports) {
try {
const hasJobCompleted = await keyStore.getItem(
keystoreReplicationSuccessKey(job.id as string, secretImport.id),
KeyStorePrefixes.SecretReplication
@@ -340,13 +341,32 @@ export const secretReplicationServiceFactory = ({
});
}
// this is used to avoid multiple times generating secret approval by failed one
await keyStore.setItemWithExpiry(
keystoreReplicationSuccessKey(job.id as string, secretImport.id),
SECRET_IMPORT_SUCCESS_LOCK,
1,
KeyStorePrefixes.SecretReplication
);
await secretImportDAL.updateById(secretImport.id, {
lastReplicated: new Date(),
replicationStatus: null,
isReplicationSuccess: true
});
} catch (err) {
logger.error(
err,
`Failed to replicate secret with import id=[${secretImport.id}] env=[${secretImport.importEnv.slug}] path=[${secretImport.importPath}]`
);
await secretImportDAL.updateById(secretImport.id, {
lastReplicated: new Date(),
replicationStatus: (err as Error)?.message.slice(0, 500),
isReplicationSuccess: false
});
}
}
await secretVersionDAL.update({ $in: { id: replicatedSecrets.map(({ id }) => id) } }, { isReplicated: true });
/* eslint-enable no-await-in-loop */
} finally {
@@ -11,6 +11,9 @@ export type TSecretImport = {
createdAt: string;
updatedAt: string;
isReplication?: boolean;
isReplicationSuccess?: boolean;
replicationStatus?: string;
lastReplicated?: string;
};
export type TGetImportedFoldersByEnvDTO = {
@@ -2,19 +2,31 @@ import { useEffect } from "react";
import { subject } from "@casl/ability";
import { useSortable } from "@dnd-kit/sortable";
import {
faCalendarCheck,
faClose,
faFileImport,
faFolder,
faInfoCircle,
faKey,
faRotate,
faUpDown
faUpDown,
faWarning,
faXmark
} from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { format } from "date-fns";
import { twMerge } from "tailwind-merge";
import { createNotification } from "@app/components/notifications";
import { ProjectPermissionCan } from "@app/components/permissions";
import { EmptyState, IconButton, SecretInput, TableContainer, Tag } from "@app/components/v2";
import {
EmptyState,
IconButton,
SecretInput,
TableContainer,
Tag,
Tooltip
} from "@app/components/v2";
import { ProjectPermissionActions, ProjectPermissionSub, useWorkspace } from "@app/context";
import { useToggle } from "@app/hooks";
import { useResyncSecretReplication } from "@app/hooks/api";
@@ -24,6 +36,9 @@ type Props = {
environment: string;
secretPath?: string;
isReplication?: boolean;
isReplicationSuccess?: boolean;
replicationStatus?: string;
lastReplicated?: string;
importEnvName: string;
importEnvPath: string;
importedSecrets: { key: string; value: string; overriden: { env: string; secretPath: string } }[];
@@ -62,7 +77,10 @@ export const SecretImportItem = ({
importedSecrets = [],
searchTerm = "",
secretPath,
environment
environment,
isReplicationSuccess,
replicationStatus,
lastReplicated
}: Props) => {
const { currentWorkspace } = useWorkspace();
const [isExpanded, setIsExpanded] = useToggle();
@@ -137,7 +155,44 @@ export const SecretImportItem = ({
isReplication={isReplication}
/>
</div>
<div className="flex items-center space-x-2 px-4 py-2">
<div className="flex items-center space-x-4 px-4 py-2">
{lastReplicated && (
<Tooltip
position="left"
className="max-w-md whitespace-normal break-words"
content={
<div className="flex max-h-[10rem] flex-col overflow-auto ">
<div className="flex self-start">
<FontAwesomeIcon icon={faCalendarCheck} className="pt-0.5 pr-2 text-sm" />
<div className="text-sm">Last Replication</div>
</div>
<div className="pl-5 text-left text-xs">
{lastReplicated
? format(new Date(lastReplicated), "yyyy-MM-dd, hh:mm aaa")
: "-"}
</div>
{!isReplicationSuccess && (
<>
<div className="mt-2 flex self-start">
<FontAwesomeIcon icon={faXmark} className="pt-1 pr-2 text-sm" />
<div className="text-sm">Fail reason</div>
</div>
<div className="pl-5 text-left text-xs">{replicationStatus}</div>
</>
)}
</div>
}
>
<div
className={twMerge(
"opacity-0 group-hover:opacity-100",
!isReplicationSuccess && "text-red-600"
)}
>
<FontAwesomeIcon icon={isReplicationSuccess ? faInfoCircle : faWarning} />
</div>
</Tooltip>
)}
<ProjectPermissionCan
I={ProjectPermissionActions.Edit}
a={subject(ProjectPermissionSub.Secrets, { environment, secretPath })}
@@ -159,7 +159,15 @@ export const SecretImportListView = ({
>
<SortableContext items={items} strategy={verticalListSortingStrategy}>
{items?.map((item) => {
const { importPath, importEnv, id, isReplication } = item;
const {
importPath,
importEnv,
id,
isReplication,
replicationStatus,
lastReplicated,
isReplicationSuccess
} = item;
return (
<SecretImportItem
searchTerm={searchTerm}
@@ -168,6 +176,9 @@ export const SecretImportListView = ({
isReplication={isReplication}
importEnvPath={importPath}
importEnvName={importEnv.name}
lastReplicated={lastReplicated}
replicationStatus={replicationStatus}
isReplicationSuccess={isReplicationSuccess}
importedSecrets={computeImportedSecretRows(
importEnv.slug,
importPath,