diff --git a/backend/src/db/migrations/20240806185442_drop-tag-name.ts b/backend/src/db/migrations/20240806185442_drop-tag-name.ts new file mode 100644 index 0000000000..db85ab2165 --- /dev/null +++ b/backend/src/db/migrations/20240806185442_drop-tag-name.ts @@ -0,0 +1,21 @@ +import { Knex } from "knex"; + +import { TableName } from "../schemas"; + +export async function up(knex: Knex): Promise { + const hasNameField = await knex.schema.hasColumn(TableName.SecretTag, "name"); + if (hasNameField) { + await knex.schema.alterTable(TableName.SecretTag, (t) => { + t.dropColumn("name"); + }); + } +} + +export async function down(knex: Knex): Promise { + const hasNameField = await knex.schema.hasColumn(TableName.SecretTag, "name"); + if (!hasNameField) { + await knex.schema.alterTable(TableName.SecretTag, (t) => { + t.string("name"); + }); + } +} diff --git a/backend/src/db/schemas/secret-tags.ts b/backend/src/db/schemas/secret-tags.ts index 04bb7b7524..7ba7204a4d 100644 --- a/backend/src/db/schemas/secret-tags.ts +++ b/backend/src/db/schemas/secret-tags.ts @@ -9,7 +9,6 @@ import { TImmutableDBKeys } from "./models"; export const SecretTagsSchema = z.object({ id: z.string().uuid(), - name: z.string(), slug: z.string(), color: z.string().nullable().optional(), createdAt: z.date(), diff --git a/backend/src/ee/services/secret-approval-request/secret-approval-request-secret-dal.ts b/backend/src/ee/services/secret-approval-request/secret-approval-request-secret-dal.ts index b827f6a212..7bd6a3fa04 100644 --- a/backend/src/ee/services/secret-approval-request/secret-approval-request-secret-dal.ts +++ b/backend/src/ee/services/secret-approval-request/secret-approval-request-secret-dal.ts @@ -88,8 +88,7 @@ export const secretApprovalRequestSecretDALFactory = (db: TDbClient) => { db.ref("id").withSchema(TableName.SecretTag).as("tagId"), db.ref("id").withSchema(TableName.SecretApprovalRequestSecretTag).as("tagJnId"), db.ref("color").withSchema(TableName.SecretTag).as("tagColor"), - db.ref("slug").withSchema(TableName.SecretTag).as("tagSlug"), - db.ref("name").withSchema(TableName.SecretTag).as("tagName") + db.ref("slug").withSchema(TableName.SecretTag).as("tagSlug") ) .select( db.ref("secretBlindIndex").withSchema(TableName.Secret).as("orgSecBlindIndex"), @@ -124,9 +123,9 @@ export const secretApprovalRequestSecretDALFactory = (db: TDbClient) => { { key: "tagJnId", label: "tags" as const, - mapper: ({ tagId: id, tagName: name, tagSlug: slug, tagColor: color }) => ({ + mapper: ({ tagId: id, tagSlug: slug, tagColor: color }) => ({ id, - name, + name: slug, slug, color }) @@ -269,8 +268,7 @@ export const secretApprovalRequestSecretDALFactory = (db: TDbClient) => { db.ref("id").withSchema(TableName.SecretTag).as("tagId"), db.ref("id").withSchema(TableName.SecretApprovalRequestSecretTagV2).as("tagJnId"), db.ref("color").withSchema(TableName.SecretTag).as("tagColor"), - db.ref("slug").withSchema(TableName.SecretTag).as("tagSlug"), - db.ref("name").withSchema(TableName.SecretTag).as("tagName") + db.ref("slug").withSchema(TableName.SecretTag).as("tagSlug") ) .select( db.ref("version").withSchema(TableName.SecretV2).as("orgSecVersion"), @@ -292,9 +290,9 @@ export const secretApprovalRequestSecretDALFactory = (db: TDbClient) => { { key: "tagJnId", label: "tags" as const, - mapper: ({ tagId: id, tagName: name, tagSlug: slug, tagColor: color }) => ({ + mapper: ({ tagId: id, tagSlug: slug, tagColor: color }) => ({ id, - name, + name: slug, slug, color }) diff --git a/backend/src/ee/services/secret-snapshot/snapshot-dal.ts b/backend/src/ee/services/secret-snapshot/snapshot-dal.ts index 8e5014fdd9..e4d341c1a8 100644 --- a/backend/src/ee/services/secret-snapshot/snapshot-dal.ts +++ b/backend/src/ee/services/secret-snapshot/snapshot-dal.ts @@ -100,8 +100,7 @@ export const snapshotDALFactory = (db: TDbClient) => { db.ref("id").withSchema(TableName.SecretTag).as("tagId"), db.ref("id").withSchema(TableName.SecretVersionTag).as("tagVersionId"), db.ref("color").withSchema(TableName.SecretTag).as("tagColor"), - db.ref("slug").withSchema(TableName.SecretTag).as("tagSlug"), - db.ref("name").withSchema(TableName.SecretTag).as("tagName") + db.ref("slug").withSchema(TableName.SecretTag).as("tagSlug") ); return sqlNestRelationships({ data, @@ -132,9 +131,9 @@ export const snapshotDALFactory = (db: TDbClient) => { { key: "tagVersionId", label: "tags" as const, - mapper: ({ tagId: id, tagName: name, tagSlug: slug, tagColor: color, tagVersionId: vId }) => ({ + mapper: ({ tagId: id, tagSlug: slug, tagColor: color, tagVersionId: vId }) => ({ id, - name, + name: slug, slug, color, vId @@ -195,8 +194,7 @@ export const snapshotDALFactory = (db: TDbClient) => { db.ref("id").withSchema(TableName.SecretTag).as("tagId"), db.ref("id").withSchema(TableName.SecretVersionV2Tag).as("tagVersionId"), db.ref("color").withSchema(TableName.SecretTag).as("tagColor"), - db.ref("slug").withSchema(TableName.SecretTag).as("tagSlug"), - db.ref("name").withSchema(TableName.SecretTag).as("tagName") + db.ref("slug").withSchema(TableName.SecretTag).as("tagSlug") ); return sqlNestRelationships({ data, @@ -227,9 +225,9 @@ export const snapshotDALFactory = (db: TDbClient) => { { key: "tagVersionId", label: "tags" as const, - mapper: ({ tagId: id, tagName: name, tagSlug: slug, tagColor: color, tagVersionId: vId }) => ({ + mapper: ({ tagId: id, tagSlug: slug, tagColor: color, tagVersionId: vId }) => ({ id, - name, + name: slug, slug, color, vId @@ -353,8 +351,7 @@ export const snapshotDALFactory = (db: TDbClient) => { db.ref("id").withSchema(TableName.SecretTag).as("tagId"), db.ref("id").withSchema(TableName.SecretVersionTag).as("tagVersionId"), db.ref("color").withSchema(TableName.SecretTag).as("tagColor"), - db.ref("slug").withSchema(TableName.SecretTag).as("tagSlug"), - db.ref("name").withSchema(TableName.SecretTag).as("tagName") + db.ref("slug").withSchema(TableName.SecretTag).as("tagSlug") ); const formated = sqlNestRelationships({ @@ -377,9 +374,9 @@ export const snapshotDALFactory = (db: TDbClient) => { { key: "tagVersionId", label: "tags" as const, - mapper: ({ tagId: id, tagName: name, tagSlug: slug, tagColor: color, tagVersionId: vId }) => ({ + mapper: ({ tagId: id, tagSlug: slug, tagColor: color, tagVersionId: vId }) => ({ id, - name, + name: slug, slug, color, vId @@ -508,8 +505,7 @@ export const snapshotDALFactory = (db: TDbClient) => { db.ref("id").withSchema(TableName.SecretTag).as("tagId"), db.ref("id").withSchema(TableName.SecretVersionV2Tag).as("tagVersionId"), db.ref("color").withSchema(TableName.SecretTag).as("tagColor"), - db.ref("slug").withSchema(TableName.SecretTag).as("tagSlug"), - db.ref("name").withSchema(TableName.SecretTag).as("tagName") + db.ref("slug").withSchema(TableName.SecretTag).as("tagSlug") ); const formated = sqlNestRelationships({ @@ -532,9 +528,9 @@ export const snapshotDALFactory = (db: TDbClient) => { { key: "tagVersionId", label: "tags" as const, - mapper: ({ tagId: id, tagName: name, tagSlug: slug, tagColor: color, tagVersionId: vId }) => ({ + mapper: ({ tagId: id, tagSlug: slug, tagColor: color, tagVersionId: vId }) => ({ id, - name, + name: slug, slug, color, vId diff --git a/backend/src/lib/api-docs/constants.ts b/backend/src/lib/api-docs/constants.ts index aa7d2fc510..43c40d1540 100644 --- a/backend/src/lib/api-docs/constants.ts +++ b/backend/src/lib/api-docs/constants.ts @@ -596,7 +596,8 @@ export const RAW_SECRETS = { "The slug of the project to list secrets from. This parameter is only applicable by machine identities.", environment: "The slug of the environment to list secrets from.", secretPath: "The secret path to list secrets from.", - includeImports: "Weather to include imported secrets or not." + includeImports: "Weather to include imported secrets or not.", + tagSlugs: "The comma separated tag slugs to filter secrets" }, CREATE: { secretName: "The name of the secret to create.", diff --git a/backend/src/server/routes/v1/secret-tag-router.ts b/backend/src/server/routes/v1/secret-tag-router.ts index ce92409f6b..7d696999e8 100644 --- a/backend/src/server/routes/v1/secret-tag-router.ts +++ b/backend/src/server/routes/v1/secret-tag-router.ts @@ -1,3 +1,4 @@ +import slugify from "@sindresorhus/slugify"; import { z } from "zod"; import { SecretTagsSchema } from "@app/db/schemas"; @@ -49,7 +50,8 @@ export const registerSecretTagRouter = async (server: FastifyZodProvider) => { }), response: { 200: z.object({ - workspaceTag: SecretTagsSchema + // akhilmhdh: for terraform backward compatiability + workspaceTag: SecretTagsSchema.extend({ name: z.string() }) }) } }, @@ -79,7 +81,8 @@ export const registerSecretTagRouter = async (server: FastifyZodProvider) => { }), response: { 200: z.object({ - workspaceTag: SecretTagsSchema + // akhilmhdh: for terraform backward compatiability + workspaceTag: SecretTagsSchema.extend({ name: z.string() }) }) } }, @@ -108,8 +111,14 @@ export const registerSecretTagRouter = async (server: FastifyZodProvider) => { projectId: z.string().trim().describe(SECRET_TAGS.CREATE.projectId) }), body: z.object({ - name: z.string().trim().describe(SECRET_TAGS.CREATE.name), - slug: z.string().trim().describe(SECRET_TAGS.CREATE.slug), + slug: z + .string() + .toLowerCase() + .trim() + .describe(SECRET_TAGS.CREATE.slug) + .refine((v) => slugify(v) === v, { + message: "Invalid slug. Slug can only contain alphanumeric characters and hyphens." + }), color: z.string().trim().describe(SECRET_TAGS.CREATE.color) }), response: { @@ -144,8 +153,14 @@ export const registerSecretTagRouter = async (server: FastifyZodProvider) => { tagId: z.string().trim().describe(SECRET_TAGS.UPDATE.tagId) }), body: z.object({ - name: z.string().trim().describe(SECRET_TAGS.UPDATE.name), - slug: z.string().trim().describe(SECRET_TAGS.UPDATE.slug), + slug: z + .string() + .toLowerCase() + .trim() + .describe(SECRET_TAGS.UPDATE.slug) + .refine((v) => slugify(v) === v, { + message: "Invalid slug. Slug can only contain alphanumeric characters and hyphens." + }), color: z.string().trim().describe(SECRET_TAGS.UPDATE.color) }), response: { diff --git a/backend/src/server/routes/v3/secret-router.ts b/backend/src/server/routes/v3/secret-router.ts index b0776d150c..7a1868fef5 100644 --- a/backend/src/server/routes/v3/secret-router.ts +++ b/backend/src/server/routes/v3/secret-router.ts @@ -59,9 +59,10 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => { tags: SecretTagsSchema.pick({ id: true, slug: true, - name: true, color: true - }).array() + }) + .extend({ name: z.string() }) + .array() }) ) }) @@ -116,16 +117,15 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => { }), response: { 200: z.object({ - secret: SecretsSchema.omit({ secretBlindIndex: true }).merge( - z.object({ - tags: SecretTagsSchema.pick({ - id: true, - slug: true, - name: true, - color: true - }).array() + secret: SecretsSchema.omit({ secretBlindIndex: true }).extend({ + tags: SecretTagsSchema.pick({ + id: true, + slug: true, + color: true }) - ) + .extend({ name: z.string() }) + .array() + }) }) } }, @@ -180,7 +180,13 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => { .enum(["true", "false"]) .default("false") .transform((value) => value === "true") - .describe(RAW_SECRETS.LIST.includeImports) + .describe(RAW_SECRETS.LIST.includeImports), + tagSlugs: z + .string() + .describe(RAW_SECRETS.LIST.tagSlugs) + .optional() + // split by comma and trim the strings + .transform((el) => (el ? el.split(",").map((i) => i.trim()) : [])) }), response: { 200: z.object({ @@ -190,9 +196,9 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => { tags: SecretTagsSchema.pick({ id: true, slug: true, - name: true, color: true }) + .extend({ name: z.string() }) .array() .optional() }) @@ -251,7 +257,8 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => { projectId: workspaceId, path: secretPath, includeImports: req.query.include_imports, - recursive: req.query.recursive + recursive: req.query.recursive, + tagSlugs: req.query.tagSlugs }); await server.services.auditLog.createAuditLog({ @@ -325,9 +332,9 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => { tags: SecretTagsSchema.pick({ id: true, slug: true, - name: true, color: true }) + .extend({ name: z.string() }) .array() .optional() }) @@ -731,9 +738,10 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => { tags: SecretTagsSchema.pick({ id: true, slug: true, - name: true, color: true - }).array() + }) + .extend({ name: z.string() }) + .array() }) .array(), imports: z diff --git a/backend/src/services/secret-tag/secret-tag-service.ts b/backend/src/services/secret-tag/secret-tag-service.ts index 76b57dc906..dd595f046d 100644 --- a/backend/src/services/secret-tag/secret-tag-service.ts +++ b/backend/src/services/secret-tag/secret-tag-service.ts @@ -22,16 +22,7 @@ type TSecretTagServiceFactoryDep = { export type TSecretTagServiceFactory = ReturnType; export const secretTagServiceFactory = ({ secretTagDAL, permissionService }: TSecretTagServiceFactoryDep) => { - const createTag = async ({ - name, - slug, - actor, - color, - actorId, - actorOrgId, - actorAuthMethod, - projectId - }: TCreateTagDTO) => { + const createTag = async ({ slug, actor, color, actorId, actorOrgId, actorAuthMethod, projectId }: TCreateTagDTO) => { const { permission } = await permissionService.getProjectPermission( actor, actorId, @@ -46,7 +37,6 @@ export const secretTagServiceFactory = ({ secretTagDAL, permissionService }: TSe const newTag = await secretTagDAL.create({ projectId, - name, slug, color, createdBy: actorId, @@ -55,7 +45,7 @@ export const secretTagServiceFactory = ({ secretTagDAL, permissionService }: TSe return newTag; }; - const updateTag = async ({ actorId, actor, actorOrgId, actorAuthMethod, id, name, color, slug }: TUpdateTagDTO) => { + const updateTag = async ({ actorId, actor, actorOrgId, actorAuthMethod, id, color, slug }: TUpdateTagDTO) => { const tag = await secretTagDAL.findById(id); if (!tag) throw new BadRequestError({ message: "Tag doesn't exist" }); @@ -73,7 +63,7 @@ export const secretTagServiceFactory = ({ secretTagDAL, permissionService }: TSe ); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.Tags); - const updatedTag = await secretTagDAL.updateById(tag.id, { name, color, slug }); + const updatedTag = await secretTagDAL.updateById(tag.id, { color, slug }); return updatedTag; }; @@ -107,7 +97,7 @@ export const secretTagServiceFactory = ({ secretTagDAL, permissionService }: TSe ); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Tags); - return tag; + return { ...tag, name: tag.slug }; }; const getTagBySlug = async ({ actorId, actor, actorOrgId, actorAuthMethod, slug, projectId }: TGetTagBySlugDTO) => { @@ -123,7 +113,7 @@ export const secretTagServiceFactory = ({ secretTagDAL, permissionService }: TSe ); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Tags); - return tag; + return { ...tag, name: tag.slug }; }; const getProjectTags = async ({ actor, actorId, actorOrgId, actorAuthMethod, projectId }: TListProjectTagsDTO) => { diff --git a/backend/src/services/secret-tag/secret-tag-types.ts b/backend/src/services/secret-tag/secret-tag-types.ts index f2ace09018..ede0c55a32 100644 --- a/backend/src/services/secret-tag/secret-tag-types.ts +++ b/backend/src/services/secret-tag/secret-tag-types.ts @@ -1,14 +1,12 @@ import { TProjectPermission } from "@app/lib/types"; export type TCreateTagDTO = { - name: string; color: string; slug: string; } & TProjectPermission; export type TUpdateTagDTO = { id: string; - name?: string; slug?: string; color?: string; } & Omit; diff --git a/backend/src/services/secret-v2-bridge/secret-v2-bridge-dal.ts b/backend/src/services/secret-v2-bridge/secret-v2-bridge-dal.ts index b41e2c2507..53ad29b177 100644 --- a/backend/src/services/secret-v2-bridge/secret-v2-bridge-dal.ts +++ b/backend/src/services/secret-v2-bridge/secret-v2-bridge-dal.ts @@ -136,7 +136,6 @@ export const secretV2BridgeDALFactory = (db: TDbClient) => { .select(db.ref("id").withSchema(TableName.SecretTag).as("tagId")) .select(db.ref("color").withSchema(TableName.SecretTag).as("tagColor")) .select(db.ref("slug").withSchema(TableName.SecretTag).as("tagSlug")) - .select(db.ref("name").withSchema(TableName.SecretTag).as("tagName")) .orderBy("id", "asc"); const data = sqlNestRelationships({ @@ -147,11 +146,11 @@ export const secretV2BridgeDALFactory = (db: TDbClient) => { { key: "tagId", label: "tags" as const, - mapper: ({ tagId: id, tagColor: color, tagSlug: slug, tagName: name }) => ({ + mapper: ({ tagId: id, tagColor: color, tagSlug: slug }) => ({ id, color, slug, - name + name: slug }) } ] @@ -169,14 +168,13 @@ export const secretV2BridgeDALFactory = (db: TDbClient) => { .where({ [`${TableName.SecretV2}Id` as const]: secretId }) .select(db.ref("id").withSchema(TableName.SecretTag).as("tagId")) .select(db.ref("color").withSchema(TableName.SecretTag).as("tagColor")) - .select(db.ref("slug").withSchema(TableName.SecretTag).as("tagSlug")) - .select(db.ref("name").withSchema(TableName.SecretTag).as("tagName")); + .select(db.ref("slug").withSchema(TableName.SecretTag).as("tagSlug")); return tags.map((el) => ({ id: el.tagId, color: el.tagColor, slug: el.tagSlug, - name: el.tagName + name: el.tagSlug })); } catch (error) { throw new DatabaseError({ error, name: "get secret tags" }); @@ -210,7 +208,6 @@ export const secretV2BridgeDALFactory = (db: TDbClient) => { .select(db.ref("id").withSchema(TableName.SecretTag).as("tagId")) .select(db.ref("color").withSchema(TableName.SecretTag).as("tagColor")) .select(db.ref("slug").withSchema(TableName.SecretTag).as("tagSlug")) - .select(db.ref("name").withSchema(TableName.SecretTag).as("tagName")) .orderBy("id", "asc"); const data = sqlNestRelationships({ @@ -221,11 +218,11 @@ export const secretV2BridgeDALFactory = (db: TDbClient) => { { key: "tagId", label: "tags" as const, - mapper: ({ tagId: id, tagColor: color, tagSlug: slug, tagName: name }) => ({ + mapper: ({ tagId: id, tagColor: color, tagSlug: slug }) => ({ id, color, slug, - name + name: slug }) } ] @@ -350,8 +347,7 @@ export const secretV2BridgeDALFactory = (db: TDbClient) => { .select(selectAllTableCols(TableName.SecretV2)) .select(db.ref("id").withSchema(TableName.SecretTag).as("tagId")) .select(db.ref("color").withSchema(TableName.SecretTag).as("tagColor")) - .select(db.ref("slug").withSchema(TableName.SecretTag).as("tagSlug")) - .select(db.ref("name").withSchema(TableName.SecretTag).as("tagName")); + .select(db.ref("slug").withSchema(TableName.SecretTag).as("tagSlug")); const docs = sqlNestRelationships({ data: rawDocs, key: "id", @@ -360,11 +356,11 @@ export const secretV2BridgeDALFactory = (db: TDbClient) => { { key: "tagId", label: "tags" as const, - mapper: ({ tagId: id, tagColor: color, tagSlug: slug, tagName: name }) => ({ + mapper: ({ tagId: id, tagColor: color, tagSlug: slug }) => ({ id, color, slug, - name + name: slug }) } ] diff --git a/backend/src/services/secret-v2-bridge/secret-v2-bridge-service.ts b/backend/src/services/secret-v2-bridge/secret-v2-bridge-service.ts index 2cb03cf6dd..d98d23823b 100644 --- a/backend/src/services/secret-v2-bridge/secret-v2-bridge-service.ts +++ b/backend/src/services/secret-v2-bridge/secret-v2-bridge-service.ts @@ -429,6 +429,7 @@ export const secretV2BridgeServiceFactory = ({ actorAuthMethod, includeImports, recursive, + tagSlugs = [], expandSecretReferences: shouldExpandSecretReferences }: TGetSecretsDTO) => { const { permission } = await permissionService.getProjectPermission( @@ -496,6 +497,9 @@ export const secretV2BridgeServiceFactory = ({ : "" }) ); + const filteredSecrets = tagSlugs.length + ? decryptedSecrets.filter((secret) => Boolean(secret.tags?.find((el) => tagSlugs.includes(el.slug)))) + : decryptedSecrets; const expandSecretReferences = expandSecretReferencesFactory({ projectId, folderDAL, @@ -504,7 +508,7 @@ export const secretV2BridgeServiceFactory = ({ }); if (shouldExpandSecretReferences) { - const secretsGroupByPath = groupBy(decryptedSecrets, (i) => i.secretPath); + const secretsGroupByPath = groupBy(filteredSecrets, (i) => i.secretPath); for (const secretPathKey in secretsGroupByPath) { if (Object.hasOwn(secretsGroupByPath, secretPathKey)) { const secretsGroupByKey = secretsGroupByPath[secretPathKey].reduce( @@ -530,7 +534,7 @@ export const secretV2BridgeServiceFactory = ({ if (!includeImports) { return { - secrets: decryptedSecrets + secrets: filteredSecrets }; } @@ -558,7 +562,7 @@ export const secretV2BridgeServiceFactory = ({ }); return { - secrets: decryptedSecrets, + secrets: filteredSecrets, imports: importedSecrets }; }; diff --git a/backend/src/services/secret-v2-bridge/secret-v2-bridge-types.ts b/backend/src/services/secret-v2-bridge/secret-v2-bridge-types.ts index 1964aac051..8c7a32a7fc 100644 --- a/backend/src/services/secret-v2-bridge/secret-v2-bridge-types.ts +++ b/backend/src/services/secret-v2-bridge/secret-v2-bridge-types.ts @@ -20,6 +20,7 @@ export type TGetSecretsDTO = { environment: string; includeImports?: boolean; recursive?: boolean; + tagSlugs?: string[]; } & TProjectPermission; export type TGetASecretDTO = { diff --git a/backend/src/services/secret/secret-dal.ts b/backend/src/services/secret/secret-dal.ts index aac01d3939..290a9597a1 100644 --- a/backend/src/services/secret/secret-dal.ts +++ b/backend/src/services/secret/secret-dal.ts @@ -123,7 +123,6 @@ export const secretDALFactory = (db: TDbClient) => { .select(db.ref("id").withSchema(TableName.SecretTag).as("tagId")) .select(db.ref("color").withSchema(TableName.SecretTag).as("tagColor")) .select(db.ref("slug").withSchema(TableName.SecretTag).as("tagSlug")) - .select(db.ref("name").withSchema(TableName.SecretTag).as("tagName")) .orderBy("id", "asc"); const data = sqlNestRelationships({ data: secs, @@ -133,11 +132,11 @@ export const secretDALFactory = (db: TDbClient) => { { key: "tagId", label: "tags" as const, - mapper: ({ tagId: id, tagColor: color, tagSlug: slug, tagName: name }) => ({ + mapper: ({ tagId: id, tagColor: color, tagSlug: slug }) => ({ id, color, slug, - name + name: slug }) } ] @@ -155,14 +154,13 @@ export const secretDALFactory = (db: TDbClient) => { .where({ [`${TableName.Secret}Id` as const]: secretId }) .select(db.ref("id").withSchema(TableName.SecretTag).as("tagId")) .select(db.ref("color").withSchema(TableName.SecretTag).as("tagColor")) - .select(db.ref("slug").withSchema(TableName.SecretTag).as("tagSlug")) - .select(db.ref("name").withSchema(TableName.SecretTag).as("tagName")); + .select(db.ref("slug").withSchema(TableName.SecretTag).as("tagSlug")); return tags.map((el) => ({ id: el.tagId, color: el.tagColor, slug: el.tagSlug, - name: el.tagName + name: el.tagSlug })); } catch (error) { throw new DatabaseError({ error, name: "get secret tags" }); @@ -188,7 +186,6 @@ export const secretDALFactory = (db: TDbClient) => { .select(db.ref("id").withSchema(TableName.SecretTag).as("tagId")) .select(db.ref("color").withSchema(TableName.SecretTag).as("tagColor")) .select(db.ref("slug").withSchema(TableName.SecretTag).as("tagSlug")) - .select(db.ref("name").withSchema(TableName.SecretTag).as("tagName")) .orderBy("id", "asc"); const data = sqlNestRelationships({ data: secs, @@ -198,11 +195,11 @@ export const secretDALFactory = (db: TDbClient) => { { key: "tagId", label: "tags" as const, - mapper: ({ tagId: id, tagColor: color, tagSlug: slug, tagName: name }) => ({ + mapper: ({ tagId: id, tagColor: color, tagSlug: slug }) => ({ id, color, slug, - name + name: slug }) } ] @@ -318,8 +315,7 @@ export const secretDALFactory = (db: TDbClient) => { .select(selectAllTableCols(TableName.Secret)) .select(db.ref("id").withSchema(TableName.SecretTag).as("tagId")) .select(db.ref("color").withSchema(TableName.SecretTag).as("tagColor")) - .select(db.ref("slug").withSchema(TableName.SecretTag).as("tagSlug")) - .select(db.ref("name").withSchema(TableName.SecretTag).as("tagName")); + .select(db.ref("slug").withSchema(TableName.SecretTag).as("tagSlug")); const docs = sqlNestRelationships({ data: rawDocs, key: "id", @@ -328,11 +324,11 @@ export const secretDALFactory = (db: TDbClient) => { { key: "tagId", label: "tags" as const, - mapper: ({ tagId: id, tagColor: color, tagSlug: slug, tagName: name }) => ({ + mapper: ({ tagId: id, tagColor: color, tagSlug: slug }) => ({ id, color, slug, - name + name: slug }) } ] diff --git a/backend/src/services/secret/secret-fns.ts b/backend/src/services/secret/secret-fns.ts index 662067645b..d417708469 100644 --- a/backend/src/services/secret/secret-fns.ts +++ b/backend/src/services/secret/secret-fns.ts @@ -370,7 +370,6 @@ export const decryptSecretRaw = ( id: string; slug: string; color?: string | null; - name: string; }[]; }, key: string @@ -412,7 +411,7 @@ export const decryptSecretRaw = ( _id: secret.id, id: secret.id, user: secret.userId, - tags: secret.tags, + tags: secret.tags?.map((el) => ({ ...el, name: el.slug })), skipMultilineEncoding: secret.skipMultilineEncoding, secretReminderRepeatDays: secret.secretReminderRepeatDays, secretReminderNote: secret.secretReminderNote, diff --git a/backend/src/services/secret/secret-service.ts b/backend/src/services/secret/secret-service.ts index 3a14f0e307..57f3015947 100644 --- a/backend/src/services/secret/secret-service.ts +++ b/backend/src/services/secret/secret-service.ts @@ -964,7 +964,8 @@ export const secretServiceFactory = ({ environment, includeImports, expandSecretReferences, - recursive + recursive, + tagSlugs = [] }: TGetSecretsRawDTO) => { const { botKey, shouldUseSecretV2Bridge } = await projectBotService.getBotKey(projectId); if (shouldUseSecretV2Bridge) { @@ -978,7 +979,8 @@ export const secretServiceFactory = ({ path, recursive, actorAuthMethod, - includeImports + includeImports, + tagSlugs }); return { secrets, imports }; } @@ -998,6 +1000,9 @@ export const secretServiceFactory = ({ }); const decryptedSecrets = secrets.map((el) => decryptSecretRaw(el, botKey)); + const filteredSecrets = tagSlugs.length + ? decryptedSecrets.filter((secret) => Boolean(secret.tags?.find((el) => tagSlugs.includes(el.slug)))) + : decryptedSecrets; const processedImports = (imports || [])?.map(({ secrets: importedSecrets, ...el }) => { const decryptedImportSecrets = importedSecrets.map((sec) => decryptSecretRaw( @@ -1106,14 +1111,14 @@ export const secretServiceFactory = ({ }; // expand secrets - await batchSecretsExpand(decryptedSecrets); + await batchSecretsExpand(filteredSecrets); // expand imports by batch await Promise.all(processedImports.map((processedImport) => batchSecretsExpand(processedImport.secrets))); } return { - secrets: decryptedSecrets, + secrets: filteredSecrets, imports: processedImports }; }; @@ -2081,7 +2086,7 @@ export const secretServiceFactory = ({ return { ...updatedSecret[0], - tags: [...existingSecretTags, ...tags].map((t) => ({ id: t.id, slug: t.slug, name: t.name, color: t.color })) + tags: [...existingSecretTags, ...tags].map((t) => ({ id: t.id, slug: t.slug, name: t.slug, color: t.color })) }; }; diff --git a/backend/src/services/secret/secret-types.ts b/backend/src/services/secret/secret-types.ts index 99bde05f2d..1686ee4885 100644 --- a/backend/src/services/secret/secret-types.ts +++ b/backend/src/services/secret/secret-types.ts @@ -149,6 +149,7 @@ export type TGetSecretsRawDTO = { environment: string; includeImports?: boolean; recursive?: boolean; + tagSlugs?: string[]; } & TProjectPermission; export type TGetASecretRawDTO = { diff --git a/cli/packages/api/api.go b/cli/packages/api/api.go index 9eab5e759a..c6ecd8bcda 100644 --- a/cli/packages/api/api.go +++ b/cli/packages/api/api.go @@ -404,6 +404,10 @@ func CallGetRawSecretsV3(httpClient *resty.Client, request GetRawSecretsV3Reques SetQueryParam("environment", request.Environment). SetQueryParam("secretPath", request.SecretPath) + if request.TagSlugs != "" { + req.SetQueryParam("tagSlugs", request.TagSlugs) + } + if request.IncludeImport { req.SetQueryParam("include_imports", "true") } diff --git a/cli/packages/api/model.go b/cli/packages/api/model.go index dc577bf9d5..f954531e2a 100644 --- a/cli/packages/api/model.go +++ b/cli/packages/api/model.go @@ -574,6 +574,7 @@ type GetRawSecretsV3Request struct { SecretPath string `json:"secretPath"` IncludeImport bool `json:"include_imports"` Recursive bool `json:"recursive"` + TagSlugs string `json:"tagSlugs,omitempty"` } type GetRawSecretsV3Response struct { diff --git a/cli/packages/cmd/agent.go b/cli/packages/cmd/agent.go index 59e496ec7f..0c99377ae0 100644 --- a/cli/packages/cmd/agent.go +++ b/cli/packages/cmd/agent.go @@ -312,7 +312,7 @@ func ParseAgentConfig(configFile []byte) (*Config, error) { func secretTemplateFunction(accessToken string, existingEtag string, currentEtag *string) func(string, string, string) ([]models.SingleEnvironmentVariable, error) { return func(projectID, envSlug, secretPath string) ([]models.SingleEnvironmentVariable, error) { - res, err := util.GetPlainTextSecretsV3(accessToken, projectID, envSlug, secretPath, false, false) + res, err := util.GetPlainTextSecretsV3(accessToken, projectID, envSlug, secretPath, false, false, "") if err != nil { return nil, err } diff --git a/cli/packages/util/secrets.go b/cli/packages/util/secrets.go index c51338d61d..91daf088ed 100644 --- a/cli/packages/util/secrets.go +++ b/cli/packages/util/secrets.go @@ -20,7 +20,7 @@ import ( "github.com/zalando/go-keyring" ) -func GetPlainTextSecretsViaServiceToken(fullServiceToken string, environment string, secretPath string, includeImports bool, recursive bool) ([]models.SingleEnvironmentVariable, error) { +func GetPlainTextSecretsViaServiceToken(fullServiceToken string, environment string, secretPath string, includeImports bool, recursive bool, tagSlugs string) ([]models.SingleEnvironmentVariable, error) { serviceTokenParts := strings.SplitN(fullServiceToken, ".", 4) if len(serviceTokenParts) < 4 { return nil, fmt.Errorf("invalid service token entered. Please double check your service token and try again") @@ -53,6 +53,7 @@ func GetPlainTextSecretsViaServiceToken(fullServiceToken string, environment str SecretPath: secretPath, IncludeImport: includeImports, Recursive: recursive, + TagSlugs: tagSlugs, }) if err != nil { @@ -76,7 +77,7 @@ func GetPlainTextSecretsViaServiceToken(fullServiceToken string, environment str } -func GetPlainTextSecretsV3(accessToken string, workspaceId string, environmentName string, secretsPath string, includeImports bool, recursive bool) (models.PlaintextSecretResult, error) { +func GetPlainTextSecretsV3(accessToken string, workspaceId string, environmentName string, secretsPath string, includeImports bool, recursive bool, tagSlugs string) (models.PlaintextSecretResult, error) { httpClient := resty.New() httpClient.SetAuthToken(accessToken). SetHeader("Accept", "application/json") @@ -86,7 +87,7 @@ func GetPlainTextSecretsV3(accessToken string, workspaceId string, environmentNa Environment: environmentName, IncludeImport: includeImports, Recursive: recursive, - // TagSlugs: tagSlugs, + TagSlugs: tagSlugs, } if secretsPath != "" { @@ -281,7 +282,7 @@ func GetAllEnvironmentVariables(params models.GetAllSecretsParameters, projectCo } res, err := GetPlainTextSecretsV3(loggedInUserDetails.UserCredentials.JTWToken, infisicalDotJson.WorkspaceId, - params.Environment, params.SecretsPath, params.IncludeImport, params.Recursive) + params.Environment, params.SecretsPath, params.IncludeImport, params.Recursive, params.TagSlugs) log.Debug().Msgf("GetAllEnvironmentVariables: Trying to fetch secrets JTW token [err=%s]", err) if err == nil { @@ -303,7 +304,7 @@ func GetAllEnvironmentVariables(params models.GetAllSecretsParameters, projectCo } else { if params.InfisicalToken != "" { log.Debug().Msg("Trying to fetch secrets using service token") - secretsToReturn, errorToReturn = GetPlainTextSecretsViaServiceToken(params.InfisicalToken, params.Environment, params.SecretsPath, params.IncludeImport, params.Recursive) + secretsToReturn, errorToReturn = GetPlainTextSecretsViaServiceToken(params.InfisicalToken, params.Environment, params.SecretsPath, params.IncludeImport, params.Recursive, params.TagSlugs) } else if params.UniversalAuthAccessToken != "" { if params.WorkspaceId == "" { @@ -311,7 +312,7 @@ func GetAllEnvironmentVariables(params models.GetAllSecretsParameters, projectCo } log.Debug().Msg("Trying to fetch secrets using universal auth") - res, err := GetPlainTextSecretsV3(params.UniversalAuthAccessToken, params.WorkspaceId, params.Environment, params.SecretsPath, params.IncludeImport, params.Recursive) + res, err := GetPlainTextSecretsV3(params.UniversalAuthAccessToken, params.WorkspaceId, params.Environment, params.SecretsPath, params.IncludeImport, params.Recursive, params.TagSlugs) errorToReturn = err secretsToReturn = res.Secrets diff --git a/frontend/src/components/tags/CreateTagModal/CreateTagModal.tsx b/frontend/src/components/tags/CreateTagModal/CreateTagModal.tsx index c65229a869..acbcdcb2de 100644 --- a/frontend/src/components/tags/CreateTagModal/CreateTagModal.tsx +++ b/frontend/src/components/tags/CreateTagModal/CreateTagModal.tsx @@ -3,6 +3,7 @@ import { Controller, useForm } from "react-hook-form"; import { faCheck } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { zodResolver } from "@hookform/resolvers/zod"; +import slugify from "@sindresorhus/slugify"; import { z } from "zod"; import { createNotification } from "@app/components/notifications"; @@ -87,7 +88,13 @@ type Props = { }; const createTagSchema = z.object({ - name: z.string().trim(), + slug: z + .string() + .trim() + .toLowerCase() + .refine((v) => slugify(v) === v, { + message: "Invalid slug. Slug can only contain alphanumeric characters and hyphens." + }), color: z.string().trim() }); @@ -110,7 +117,7 @@ export const CreateTagModal = ({ isOpen, onToggle }: Props): JSX.Element => { } = useForm({ resolver: zodResolver(createTagSchema) }); - + const { currentWorkspace } = useWorkspace(); const workspaceId = currentWorkspace?.id || ""; @@ -123,13 +130,12 @@ export const CreateTagModal = ({ isOpen, onToggle }: Props): JSX.Element => { if (!isOpen) reset(); }, [isOpen]); - const onFormSubmit = async ({ name, color }: FormData) => { + const onFormSubmit = async ({ slug, color }: FormData) => { try { await createWsTag({ workspaceID: workspaceId, - tagName: name, tagColor: color, - tagSlug: name.replace(" ", "_") + tagSlug: slug }); onToggle(false); reset(); @@ -155,11 +161,11 @@ export const CreateTagModal = ({ isOpen, onToggle }: Props): JSX.Element => {
( - - + + )} /> diff --git a/frontend/src/hooks/api/tags/queries.tsx b/frontend/src/hooks/api/tags/queries.tsx index 311ed09416..d1c4b533d6 100644 --- a/frontend/src/hooks/api/tags/queries.tsx +++ b/frontend/src/hooks/api/tags/queries.tsx @@ -28,11 +28,10 @@ export const useCreateWsTag = () => { const queryClient = useQueryClient(); return useMutation({ - mutationFn: async ({ workspaceID, tagName, tagColor, tagSlug }) => { + mutationFn: async ({ workspaceID, tagColor, tagSlug }) => { const { data } = await apiRequest.post<{ workspaceTag: WsTag }>( `/api/v1/workspace/${workspaceID}/tags`, { - name: tagName, color: tagColor || "", slug: tagSlug } diff --git a/frontend/src/hooks/api/tags/types.ts b/frontend/src/hooks/api/tags/types.ts index 9b4f70587d..72d710cfa0 100644 --- a/frontend/src/hooks/api/tags/types.ts +++ b/frontend/src/hooks/api/tags/types.ts @@ -2,7 +2,6 @@ export type UserWsTags = WsTag[]; export type WsTag = { id: string; - name: string; slug: string; color?: string; projectId: string; @@ -16,7 +15,6 @@ export type WorkspaceTag = { id: string; name: string; slug: string }; export type CreateTagDTO = { workspaceID: string; tagSlug: string; - tagName: string; tagColor: string; }; diff --git a/frontend/src/views/SecretApprovalPage/components/SecretApprovalRequest/components/SecretApprovalRequestChangeItem.tsx b/frontend/src/views/SecretApprovalPage/components/SecretApprovalRequest/components/SecretApprovalRequestChangeItem.tsx index 3fbe4357b3..6adf591e70 100644 --- a/frontend/src/views/SecretApprovalPage/components/SecretApprovalRequest/components/SecretApprovalRequestChangeItem.tsx +++ b/frontend/src/views/SecretApprovalPage/components/SecretApprovalRequest/components/SecretApprovalRequestChangeItem.tsx @@ -97,7 +97,7 @@ export const SecretApprovalRequestChangeItem = ({ {secretVersion?.secretComment} - {secretVersion?.tags?.map(({ name, id: tagId, color }) => ( + {secretVersion?.tags?.map(({ slug, id: tagId, color }) => ( -
{name}
+
{slug}
))} @@ -119,7 +119,7 @@ export const SecretApprovalRequestChangeItem = ({ {newVersion?.secretComment} - {newVersion?.tags?.map(({ name, id: tagId, color }) => ( + {newVersion?.tags?.map(({ slug, id: tagId, color }) => ( -
{name}
+
{slug}
))} @@ -157,7 +157,7 @@ export const SecretApprovalRequestChangeItem = ({ {(op === CommitType.CREATE ? newVersion?.tags : secretVersion?.tags)?.map( - ({ name, id: tagId, color }) => ( + ({ slug, id: tagId, color }) => ( -
{name}
+
{slug}
) )} diff --git a/frontend/src/views/SecretMainPage/components/ActionBar/ActionBar.tsx b/frontend/src/views/SecretMainPage/components/ActionBar/ActionBar.tsx index 705234f633..65cb0389f2 100644 --- a/frontend/src/views/SecretMainPage/components/ActionBar/ActionBar.tsx +++ b/frontend/src/views/SecretMainPage/components/ActionBar/ActionBar.tsx @@ -327,7 +327,7 @@ export const ActionBar = ({ Apply tags to filter secrets - {tags.map(({ id, name, color }) => ( + {tags.map(({ id, slug, color }) => ( { evt.preventDefault(); @@ -342,7 +342,7 @@ export const ActionBar = ({ className="mr-2 h-2 w-2 rounded-full" style={{ background: color || "#bec2c8" }} /> - {name} + {slug} ))} diff --git a/frontend/src/views/SecretMainPage/components/SecretListView/SecretDetaiSidebar.tsx b/frontend/src/views/SecretMainPage/components/SecretListView/SecretDetaiSidebar.tsx index 2e983bda66..e2a1c3b87c 100644 --- a/frontend/src/views/SecretMainPage/components/SecretListView/SecretDetaiSidebar.tsx +++ b/frontend/src/views/SecretMainPage/components/SecretListView/SecretDetaiSidebar.tsx @@ -254,7 +254,7 @@ export const SecretDetailSidebar = ({ )}
- {fields.map(({ tagColor, id: formId, name, id }) => ( + {fields.map(({ tagColor, id: formId, slug, id }) => ( -
{name}
+
{slug}
))} @@ -296,7 +296,7 @@ export const SecretDetailSidebar = ({ Add tags to this secret {tags.map((tag) => { - const { id: tagId, name, color } = tag; + const { id: tagId, slug, color } = tag; const isSelected = selectedTagsGroupById?.[tagId]; return ( @@ -311,7 +311,7 @@ export const SecretDetailSidebar = ({ className="mr-2 h-2 w-2 rounded-full" style={{ background: color || "#bec2c8" }} /> - {name} + {slug}
); diff --git a/frontend/src/views/SecretMainPage/components/SecretListView/SecretItem.tsx b/frontend/src/views/SecretMainPage/components/SecretListView/SecretItem.tsx index 22207cb900..5b62dd526b 100644 --- a/frontend/src/views/SecretMainPage/components/SecretListView/SecretItem.tsx +++ b/frontend/src/views/SecretMainPage/components/SecretListView/SecretItem.tsx @@ -336,7 +336,7 @@ export const SecretItem = memo( Add tags to this secret {tags.map((tag) => { - const { id: tagId, name, color } = tag; + const { id: tagId, slug, color } = tag; const isTagSelected = selectedTagsGroupById?.[tagId]; return ( @@ -358,7 +358,7 @@ export const SecretItem = memo( className="mr-2 h-2 w-2 rounded-full" style={{ background: color || "#bec2c8" }} /> - {name} + {slug} ); diff --git a/frontend/src/views/SecretMainPage/components/SecretListView/SecretListView.utils.ts b/frontend/src/views/SecretMainPage/components/SecretListView/SecretListView.utils.ts index a3040b43cc..eb9e4d195a 100644 --- a/frontend/src/views/SecretMainPage/components/SecretListView/SecretListView.utils.ts +++ b/frontend/src/views/SecretMainPage/components/SecretListView/SecretListView.utils.ts @@ -49,7 +49,6 @@ export const formSchema = z.object({ tags: z .object({ id: z.string(), - name: z.string(), slug: z.string(), tagColor: z.string().optional() }) diff --git a/frontend/src/views/SecretMainPage/components/SnapshotView/SecretItem.tsx b/frontend/src/views/SecretMainPage/components/SnapshotView/SecretItem.tsx index 2a7397ec94..d6680bcef9 100644 --- a/frontend/src/views/SecretMainPage/components/SnapshotView/SecretItem.tsx +++ b/frontend/src/views/SecretMainPage/components/SnapshotView/SecretItem.tsx @@ -151,7 +151,7 @@ export const SecretItem = ({ mode, preSecret, postSecret }: Props) => { Tags {isModified && ( - {preSecret?.tags?.map(({ name, id: tagId, color }) => ( + {preSecret?.tags?.map(({ slug, id: tagId, color }) => ( { className="h-3 w-3 rounded-full" style={{ backgroundColor: color || "#bec2c8" }} /> -
{name}
+
{slug}
))} )} - {postSecret?.tags?.map(({ name, id: tagId, color }) => ( + {postSecret?.tags?.map(({ slug, id: tagId, color }) => ( { className="h-3 w-3 rounded-full" style={{ backgroundColor: color || "#bec2c8" }} /> -
{name}
+
{slug}
))} diff --git a/frontend/src/views/Settings/ProjectSettingsPage/components/SecretTagsSection/AddSecretTagModal.tsx b/frontend/src/views/Settings/ProjectSettingsPage/components/SecretTagsSection/AddSecretTagModal.tsx index 7cc1a926eb..75f6b69bfb 100644 --- a/frontend/src/views/Settings/ProjectSettingsPage/components/SecretTagsSection/AddSecretTagModal.tsx +++ b/frontend/src/views/Settings/ProjectSettingsPage/components/SecretTagsSection/AddSecretTagModal.tsx @@ -1,6 +1,7 @@ import { Controller, useForm } from "react-hook-form"; -import { yupResolver } from "@hookform/resolvers/yup"; -import * as yup from "yup"; +import { zodResolver } from "@hookform/resolvers/zod"; +import slugify from "@sindresorhus/slugify"; +import { z } from "zod"; import { createNotification } from "@app/components/notifications"; import { Button, FormControl, Input, Modal, ModalClose, ModalContent } from "@app/components/v2"; @@ -8,11 +9,13 @@ import { useWorkspace } from "@app/context"; import { useCreateWsTag } from "@app/hooks/api"; import { UsePopUpState } from "@app/hooks/usePopUp"; -const schema = yup.object({ - name: yup.string().required().label("Tag Name") +const schema = z.object({ + slug: z.string().refine((v) => slugify(v) === v, { + message: "Invalid slug. Slug can only contain alphanumeric characters and hyphens." + }) }); -export type FormData = yup.InferType; +export type FormData = z.infer; type Props = { popUp: UsePopUpState<["CreateSecretTag", "deleteTagConfirmation"]>; @@ -26,7 +29,6 @@ type Props = { }; export const AddSecretTagModal = ({ popUp, handlePopUpClose, handlePopUpToggle }: Props) => { - const { currentWorkspace } = useWorkspace(); const createWsTag = useCreateWsTag(); const { @@ -35,17 +37,16 @@ export const AddSecretTagModal = ({ popUp, handlePopUpClose, handlePopUpToggle } handleSubmit, formState: { isSubmitting } } = useForm({ - resolver: yupResolver(schema) + resolver: zodResolver(schema) }); - const onFormSubmit = async ({ name }: FormData) => { + const onFormSubmit = async ({ slug }: FormData) => { try { if (!currentWorkspace?.id) return; await createWsTag.mutateAsync({ workspaceID: currentWorkspace?.id, - tagName: name, - tagSlug: name.replace(/\s+/g, " ").replace(" ", "_"), + tagSlug: slug, tagColor: "" }); @@ -80,11 +81,11 @@ export const AddSecretTagModal = ({ popUp, handlePopUpClose, handlePopUpToggle } ( - - + + )} /> diff --git a/frontend/src/views/Settings/ProjectSettingsPage/components/SecretTagsSection/SecretTagsTable.tsx b/frontend/src/views/Settings/ProjectSettingsPage/components/SecretTagsSection/SecretTagsTable.tsx index d0655f9843..cc68b07003 100644 --- a/frontend/src/views/Settings/ProjectSettingsPage/components/SecretTagsSection/SecretTagsTable.tsx +++ b/frontend/src/views/Settings/ProjectSettingsPage/components/SecretTagsSection/SecretTagsTable.tsx @@ -40,7 +40,6 @@ export const SecretTagsTable = ({ handlePopUpOpen }: Props) => { - @@ -49,9 +48,8 @@ export const SecretTagsTable = ({ handlePopUpOpen }: Props) => { {isLoading && } {!isLoading && data && - data.map(({ id, name, slug }) => ( - - + data.map(({ id, slug }) => ( +
Tag Slug
{name}
{slug} { handlePopUpOpen("deleteTagConfirmation", { - name, + name: slug, id }) }