This commit is contained in:
devin-ai-integration[bot] 2026-09-23 14:38:05 +00:00 • committed by GitHub
commit 50fe35ea3e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 117 additions and 16 deletions

View file

@ -3,6 +3,7 @@ import {
filterSensitiveMetadata,
extractLoggingSettings,
formatMetadataForDisplay,
mergeKeyTags,
stripTagsFromMetadata,
} from "./key_info_utils";
@ -57,3 +58,30 @@ describe("stripTagsFromMetadata", () => {
expect(input).toEqual(originalCopy);
});
});
describe("mergeKeyTags", () => {
it("returns tags from the dedicated field", () => {
expect(mergeKeyTags(["a", "b"], {})).toEqual(["a", "b"]);
});
it("returns tags from metadata when the dedicated field is undefined", () => {
expect(mergeKeyTags(undefined, { tags: ["a", "b"] })).toEqual(["a", "b"]);
});
it("deduplicates overlapping tags with dedicated field order first", () => {
expect(mergeKeyTags(["a", "b"], { tags: ["b", "c"] })).toEqual(["a", "b", "c"]);
});
it("drops non-string metadata tags", () => {
expect(mergeKeyTags(undefined, { tags: ["a", 1, null, false, "b"] })).toEqual(["a", "b"]);
});
it("returns field tags when metadata is null or a string", () => {
expect(mergeKeyTags(["a"], null)).toEqual(["a"]);
expect(mergeKeyTags(["b"], "metadata")).toEqual(["b"]);
});
it("returns field tags when metadata has no tags key", () => {
expect(mergeKeyTags(["a"], { keep: "x" })).toEqual(["a"]);
});
});

View file

@ -61,3 +61,12 @@ export const stripTagsFromMetadata = (metadata: any) => {
const { tags, ...rest } = metadata as Record<string, any>;
return rest;
};
export const mergeKeyTags = (fieldTags: unknown, metadata: unknown): string[] => {
const isString = (value: unknown): value is string => typeof value === "string";
const fromField = Array.isArray(fieldTags) ? fieldTags.filter(isString) : [];
const metadataTags =
metadata && typeof metadata === "object" ? (metadata as Record<string, unknown>).tags : undefined;
const fromMetadata = Array.isArray(metadataTags) ? metadataTags.filter(isString) : [];
return Array.from(new Set([...fromField, ...fromMetadata]));
};

View file

@ -49,11 +49,14 @@ vi.mock("@/utils/dataUtils", () => ({
copyToClipboard: async () => true,
formatNumberWithCommas: (n: any) => String(n),
}));
vi.mock("../key_info_utils", () => ({
extractLoggingSettings: () => ({}),
formatMetadataForDisplay: (m: any) => JSON.stringify(m, null, 2),
stripTagsFromMetadata: (m: any) => m,
}));
vi.mock("../key_info_utils", async (importOriginal) => {
const actual = await importOriginal<typeof import("../key_info_utils")>();
return {
...actual,
extractLoggingSettings: () => ({}),
formatMetadataForDisplay: (m: any) => JSON.stringify(m, null, 2),
};
});
vi.mock("../callback_info_helpers", () => ({
callback_map: {},
mapInternalToDisplayNames: (x: any) => x,
@ -344,6 +347,66 @@ describe("KeyInfoView handleKeyUpdate guardrails guard", () => {
});
});
describe("KeyInfoView handleKeyUpdate metadata tags", () => {
it("merges tags from the field and metadata JSON while preserving metadata", async () => {
renderView(true);
fireEvent.click(screen.getByText("Settings"));
fireEvent.click(screen.getByText("Edit Settings"));
(globalThis as any).__TEST_FORM_VALUES = {
token: "tok_123",
tags: ["ui-tag"],
metadata: JSON.stringify({ tags: ["headroom-pilot"], keep: "x" }),
};
fireEvent.click(screen.getByText("Mock Submit"));
await waitFor(() => expect(keyUpdateCallMock).toHaveBeenCalled());
const [, sentPayload] = keyUpdateCallMock.mock.calls[0];
expect(sentPayload.metadata.tags).toEqual(["ui-tag", "headroom-pilot"]);
expect(sentPayload.metadata.keep).toBe("x");
expect("tags" in sentPayload).toBe(false);
});
it("preserves metadata tags when the dedicated field is absent", async () => {
renderView(true);
fireEvent.click(screen.getByText("Settings"));
fireEvent.click(screen.getByText("Edit Settings"));
(globalThis as any).__TEST_FORM_VALUES = {
token: "tok_123",
metadata: JSON.stringify({ tags: ["headroom-pilot"] }),
};
fireEvent.click(screen.getByText("Mock Submit"));
await waitFor(() => expect(keyUpdateCallMock).toHaveBeenCalled());
const [, sentPayload] = keyUpdateCallMock.mock.calls[0];
expect(sentPayload.metadata.tags).toEqual(["headroom-pilot"]);
});
it("merges and deduplicates tags from object metadata", async () => {
renderView(true);
fireEvent.click(screen.getByText("Settings"));
fireEvent.click(screen.getByText("Edit Settings"));
(globalThis as any).__TEST_FORM_VALUES = {
token: "tok_123",
tags: ["ui-tag", "headroom-pilot"],
metadata: { tags: ["headroom-pilot"] },
};
fireEvent.click(screen.getByText("Mock Submit"));
await waitFor(() => expect(keyUpdateCallMock).toHaveBeenCalled());
const [, sentPayload] = keyUpdateCallMock.mock.calls[0];
expect(sentPayload.metadata.tags).toEqual(["ui-tag", "headroom-pilot"]);
});
});
describe("KeyInfoView handleKeyUpdate mcp_toolsets", () => {
it("should forward the toolsets the edit form supplies into object_permission", async () => {
renderView(true);

View file

@ -31,7 +31,12 @@ import AutoRotationView from "../common_components/AutoRotationView";
import DeleteResourceModal from "../common_components/DeleteResourceModal";
import RouterSettingsSummary from "../common_components/RouterSettingsSummary";
import { hasRouterSettings } from "../common_components/routerSettingsPayload";
import { extractLoggingSettings, formatMetadataForDisplay, stripTagsFromMetadata } from "../key_info_utils";
import {
extractLoggingSettings,
formatMetadataForDisplay,
mergeKeyTags,
stripTagsFromMetadata,
} from "../key_info_utils";
import { KeyResponse } from "../key_team_helpers/key_list";
import LoggingSettingsView from "../logging_settings_view";
import { toast } from "@/lib/toast";
@ -297,13 +302,10 @@ export default function KeyInfoView({
if (formValues.metadata && typeof formValues.metadata === "string") {
try {
const parsedMetadata = JSON.parse(formValues.metadata);
// Ensure tags are controlled via dedicated field, not in metadata textarea
if ("tags" in parsedMetadata) {
delete parsedMetadata["tags"];
}
const tags = mergeKeyTags(formValues.tags, parsedMetadata);
formValues.metadata = {
...parsedMetadata,
...(Array.isArray(formValues.tags) && formValues.tags.length > 0 ? { tags: formValues.tags } : {}),
...stripTagsFromMetadata(parsedMetadata),
...(tags.length > 0 ? { tags } : {}),
...(formValues.guardrails?.length > 0 ? { guardrails: formValues.guardrails } : {}),
...(Array.isArray(formValues.logging_settings) && formValues.logging_settings.length > 0
? { logging: formValues.logging_settings }
@ -321,10 +323,10 @@ export default function KeyInfoView({
}
} else {
const baseMetadata = formValues.metadata || {};
const { tags: _omitTags, ...rest } = baseMetadata;
const tags = mergeKeyTags(formValues.tags, baseMetadata);
formValues.metadata = {
...rest,
...(Array.isArray(formValues.tags) && formValues.tags.length > 0 ? { tags: formValues.tags } : {}),
...stripTagsFromMetadata(baseMetadata),
...(tags.length > 0 ? { tags } : {}),
...(formValues.guardrails?.length > 0 ? { guardrails: formValues.guardrails } : {}),
...(Array.isArray(formValues.logging_settings) && formValues.logging_settings.length > 0
? { logging: formValues.logging_settings }
@ -337,7 +339,6 @@ export default function KeyInfoView({
};
}
// tags are merged into metadata; do not send as top-level field
if ("tags" in formValues) {
delete formValues.tags;
}