diff --git a/clients/admin-ui/openapi-ts-api.config.ts b/clients/admin-ui/openapi-ts-api.config.ts new file mode 100644 index 00000000000..f36202aed0e --- /dev/null +++ b/clients/admin-ui/openapi-ts-api.config.ts @@ -0,0 +1,134 @@ +// eslint-disable-next-line import/no-extraneous-dependencies +import { defineConfig } from "@hey-api/openapi-ts"; + +export default defineConfig({ + input: "http://localhost:8080/openapi.json", + output: { + path: "./src/types/api", + format: "prettier", + fileName: { + suffix: null, + }, + }, + parser: { + patch: { + schemas: { + ScopeRegistryEnum: (schema) => { + // Add x-enum-varnames to replace colons with underscores in enum keys + if (schema.enum && Array.isArray(schema.enum)) { + // eslint-disable-next-line no-param-reassign + schema["x-enum-varnames"] = schema.enum.map((value: string) => + value.toUpperCase().replace(/:/g, "_").replace(/-/g, "_"), + ); + } + }, + DATAMAP_GROUPING: (schema) => { + // Replace commas and spaces in enum values to produce valid identifier keys + if (schema.enum && Array.isArray(schema.enum)) { + // eslint-disable-next-line no-param-reassign + schema["x-enum-varnames"] = schema.enum.map((value: string) => + value + .toUpperCase() + .replace(/,\s*/g, "_") + .replace(/\s+/g, "_") + .replace(/-/g, "_"), + ); + } + }, + AllowedTypes: (schema) => { + // Assign distinct enum key names since both values would map to STRING + if (schema.enum && Array.isArray(schema.enum)) { + // eslint-disable-next-line no-param-reassign + schema["x-enum-varnames"] = schema.enum.map((value: string) => { + if (value === "string") { + return "STRING"; + } + if (value === "string[]") { + return "STRING_ARRAY"; + } + return value.toUpperCase().replace(/[^A-Z0-9]/g, "_"); + }); + } + }, + // -- Fidesplus computed properties not in base OpenAPI spec -- + StagedResourceAPIResponse: (schema: any) => { + schema.properties.preferred_data_uses = { + type: "array", + items: { type: "string" }, + nullable: true, + description: + "Computed: user_assigned_data_uses ?? data_uses (fidesplus)", + }; + }, + Field: (schema: any) => { + schema.properties.preferred_data_categories = { + type: "array", + items: { type: "string" }, + nullable: true, + description: "Computed: field_data_categories (fidesplus)", + }; + // Remove additionalProperties to prevent catch-all index signature + // that causes TypeScript to resolve all properties to `{}` + delete schema.additionalProperties; + }, + ExperienceConfigCreate: (schema: any) => { + schema.properties.resurface_behavior = { + type: "array", + items: { type: "string", enum: ["reject", "dismiss"] }, + nullable: true, + description: "Resurface behavior options (fidesplus)", + }; + }, + ExperienceConfigUpdate: (schema: any) => { + schema.properties.resurface_behavior = { + type: "array", + items: { type: "string", enum: ["reject", "dismiss"] }, + nullable: true, + description: "Resurface behavior options (fidesplus)", + }; + }, + SystemStagedResourcesAggregateRecord: (schema: any) => { + schema.properties.metadata = { + $ref: "#/components/schemas/IdentityProviderApplicationMetadata", + nullable: true, + description: + "Okta application metadata for identity provider resources (fidesplus)", + }; + }, + }, + }, + hooks: { + symbols: { + getFilePath: (symbol) => { + // Skip endpoint-specific types (camelCase pattern: operationNameApiV1PathMethodData/Response/Errors/etc) + // These are auto-generated for each endpoint and bloat the output + if ( + symbol.name && + /Api[A-Z].*(?:Data|Response|Responses|Error|Errors)$/.test( + symbol.name, + ) + ) { + // Return undefined to skip this symbol entirely + return undefined; + } + + // Keep one type/enum per file in models/ directory + // Note: enums have kind: undefined, only types have kind: "type" + // So we match on any symbol with a name (enums, types, interfaces all get split) + if (symbol.name) { + return `models/${symbol.name}`; + } + // Let other symbols use default placement + return undefined; + }, + }, + }, + }, + plugins: [ + { + name: "@hey-api/typescript", + enums: "typescript", + case: "preserve", + }, + ], +}); diff --git a/clients/admin-ui/openapi-ts-dictionary.config.ts b/clients/admin-ui/openapi-ts-dictionary.config.ts new file mode 100644 index 00000000000..81ae61d01eb --- /dev/null +++ b/clients/admin-ui/openapi-ts-dictionary.config.ts @@ -0,0 +1,14 @@ +// eslint-disable-next-line import/no-extraneous-dependencies +import { defineConfig } from "@hey-api/openapi-ts"; + +export default defineConfig({ + input: "http://localhost:8081/openapi.json", + output: { + path: "./src/types/dictionary-api", + format: "prettier", + }, + types: { + enums: "typescript", + identifierCase: "preserve", + }, +}); diff --git a/clients/admin-ui/package.json b/clients/admin-ui/package.json index 79679c86ccc..74fa91a1ceb 100644 --- a/clients/admin-ui/package.json +++ b/clients/admin-ui/package.json @@ -24,8 +24,8 @@ "lint": "next lint && eslint ./cypress/e2e/**/*.ts ./cypress/support/**/*.ts", "lint:fix": "eslint --fix . && eslint --fix ./cypress/e2e/**/*.ts ./cypress/support/**/*.ts", "lint-staged:fix": "lint-staged --diff=main", - "openapi:generate": "openapi --input http://localhost:8080/openapi.json --output ./src/types/api --exportCore false --exportServices false --indent 2 && prettier --write .", - "openapi:generate-dictionary": "openapi --input http://localhost:8081/openapi.json --output ./src/types/dictionary-api --exportCore false --exportServices false --indent 2", + "openapi:generate": "openapi-ts --file openapi-ts-api.config.ts", + "openapi:generate-dictionary": "openapi-ts --file openapi-ts-dictionary.config.ts", "start": "next start", "test": "jest", "test:watch": "jest --watchAll", @@ -78,6 +78,7 @@ "yup": "^1.4.0" }, "devDependencies": { + "@hey-api/openapi-ts": "^0.88.0", "@jest/globals": "^29.7.0", "@next/bundle-analyzer": "^13.2.4", "@testing-library/cypress": "^10.0.3", @@ -121,7 +122,6 @@ "jest-environment-jsdom": "^29.7.0", "lint-staged": "^13.2.0", "msw": "^1.2.1", - "openapi-typescript-codegen": "^0.23.0", "postcss": "^8.4.41", "prettier": "^3.3.3", "sass": "^1.80.6", diff --git a/clients/admin-ui/src/features/chat-provider/ChatConfigurations.tsx b/clients/admin-ui/src/features/chat-provider/ChatConfigurations.tsx index 8f8588cddb6..95325e6d244 100644 --- a/clients/admin-ui/src/features/chat-provider/ChatConfigurations.tsx +++ b/clients/admin-ui/src/features/chat-provider/ChatConfigurations.tsx @@ -20,7 +20,7 @@ import { getErrorMessage, isErrorResult } from "~/features/common/helpers"; import { CHAT_PROVIDERS_CONFIGURE_ROUTE } from "~/features/common/nav/routes"; import { useHasPermission } from "~/features/common/Restrict"; import { TableSkeletonLoader } from "~/features/common/table/v2"; -import { ChatProviderSettingsResponse, ScopeRegistryEnum } from "~/types/api"; +import { ChatConfigResponse, ScopeRegistryEnum } from "~/types/api"; import { SlackLogo } from "../common/logos/SlackLogo"; import { @@ -71,7 +71,7 @@ export const ChatConfigurations = () => { // Delete modal state const [deleteModalOpen, setDeleteModalOpen] = useState(false); const [configToDelete, setConfigToDelete] = - useState(null); + useState(null); // Use items from the list response const tableData = useMemo(() => { @@ -86,7 +86,7 @@ export const ChatConfigurations = () => { ); const handleDeleteConfiguration = useCallback( - (config: ChatProviderSettingsResponse) => { + (config: ChatConfigResponse) => { setConfigToDelete(config); setDeleteModalOpen(true); }, @@ -134,12 +134,12 @@ export const ChatConfigurations = () => { }, []); // Column definitions - const columns: ColumnsType = useMemo( + const columns: ColumnsType = useMemo( () => [ { title: "Provider", key: ChatProviderColumnKeys.PROVIDER, - render: (_value: unknown, record: ChatProviderSettingsResponse) => { + render: (_value: unknown, record: ChatConfigResponse) => { const getProviderIcon = () => { switch (record.provider_type) { case "slack": @@ -172,7 +172,7 @@ export const ChatConfigurations = () => { { title: "Status", key: ChatProviderColumnKeys.STATUS, - render: (_value: unknown, record: ChatProviderSettingsResponse) => { + render: (_value: unknown, record: ChatConfigResponse) => { if (record.authorized) { return ( @@ -194,7 +194,7 @@ export const ChatConfigurations = () => { title: "Enabled", key: ChatProviderColumnKeys.ENABLED, width: 100, - render: (_value: unknown, record: ChatProviderSettingsResponse) => ( + render: (_value: unknown, record: ChatConfigResponse) => ( { { title: "Actions", key: ChatProviderColumnKeys.ACTIONS, - render: (_value: unknown, record: ChatProviderSettingsResponse) => ( + render: (_value: unknown, record: ChatConfigResponse) => ( {userCanUpdate && !record.authorized && record.client_id && (