diff --git a/src/api/generated/@tanstack/react-query.gen.ts b/src/api/generated/@tanstack/react-query.gen.ts index 102ed855..a22a7421 100644 --- a/src/api/generated/@tanstack/react-query.gen.ts +++ b/src/api/generated/@tanstack/react-query.gen.ts @@ -28,6 +28,7 @@ import { v1DeleteWorkspaceCustomInstructions, v1GetWorkspaceMuxes, v1SetWorkspaceMuxes, + v1ListWorkspacesByProvider, v1StreamSse, v1VersionCheck, v1GetWorkspaceTokenUsage, @@ -76,6 +77,7 @@ import type { V1SetWorkspaceMuxesData, V1SetWorkspaceMuxesError, V1SetWorkspaceMuxesResponse, + V1ListWorkspacesByProviderData, V1GetWorkspaceTokenUsageData, } from '../types.gen' @@ -687,6 +689,27 @@ export const v1SetWorkspaceMuxesMutation = ( return mutationOptions } +export const v1ListWorkspacesByProviderQueryKey = ( + options: OptionsLegacyParser +) => [createQueryKey('v1ListWorkspacesByProvider', options)] + +export const v1ListWorkspacesByProviderOptions = ( + options: OptionsLegacyParser +) => { + return queryOptions({ + queryFn: async ({ queryKey, signal }) => { + const { data } = await v1ListWorkspacesByProvider({ + ...options, + ...queryKey[0], + signal, + throwOnError: true, + }) + return data + }, + queryKey: v1ListWorkspacesByProviderQueryKey(options), + }) +} + export const v1StreamSseQueryKey = (options?: OptionsLegacyParser) => [ createQueryKey('v1StreamSse', options), ] diff --git a/src/api/generated/sdk.gen.ts b/src/api/generated/sdk.gen.ts index 9834d09b..e9d216d9 100644 --- a/src/api/generated/sdk.gen.ts +++ b/src/api/generated/sdk.gen.ts @@ -73,6 +73,9 @@ import type { V1SetWorkspaceMuxesData, V1SetWorkspaceMuxesError, V1SetWorkspaceMuxesResponse, + V1ListWorkspacesByProviderData, + V1ListWorkspacesByProviderError, + V1ListWorkspacesByProviderResponse, V1StreamSseError, V1StreamSseResponse, V1VersionCheckError, @@ -512,6 +515,25 @@ export const v1SetWorkspaceMuxes = ( }) } +/** + * List Workspaces By Provider + * List workspaces by provider ID. + */ +export const v1ListWorkspacesByProvider = < + ThrowOnError extends boolean = false, +>( + options: OptionsLegacyParser +) => { + return (options?.client ?? client).get< + V1ListWorkspacesByProviderResponse, + V1ListWorkspacesByProviderError, + ThrowOnError + >({ + ...options, + url: '/api/v1/workspaces/{provider_id}', + }) +} + /** * Stream Sse * Send alerts event diff --git a/src/api/generated/types.gen.ts b/src/api/generated/types.gen.ts index 5f2dc9be..50634d20 100644 --- a/src/api/generated/types.gen.ts +++ b/src/api/generated/types.gen.ts @@ -35,11 +35,11 @@ export type Alert = { | { [key: string]: unknown } - | null; - trigger_type: string; - trigger_category: AlertSeverity; - timestamp: string; -}; + | null + trigger_type: string + trigger_category: AlertSeverity + timestamp: string +} /** * Represents an alert with it's respective conversation. @@ -60,8 +60,8 @@ export type AlertConversation = { } export enum AlertSeverity { - INFO = "info", - CRITICAL = "critical", + INFO = 'info', + CRITICAL = 'critical', } /** @@ -111,6 +111,7 @@ export type Conversation = { export type CreateOrRenameWorkspaceRequest = { name: string + config?: WorkspaceConfig | null rename_to?: string | null } @@ -145,15 +146,16 @@ export type ModelByProvider = { * Represents the different types of matchers we support. */ export enum MuxMatcherType { - CATCH_ALL = "catch_all", - FILENAME_MATCH = "filename_match", - REQUEST_TYPE_MATCH = "request_type_match", + CATCH_ALL = 'catch_all', + FILENAME_MATCH = 'filename_match', + REQUEST_TYPE_MATCH = 'request_type_match', } /** * Represents a mux rule for a provider. */ export type MuxRule = { + provider_name?: string | null provider_id: string model: string matcher_type: MuxMatcherType @@ -251,6 +253,20 @@ export type Workspace = { is_active: boolean } +export type WorkspaceConfig = { + system_prompt: string + muxing_rules: Array +} + +/** + * Returns a workspace ID with model name + */ +export type WorkspaceWithModel = { + id: string + name: string + provider_model_name: string +} + export type HealthCheckHealthGetResponse = unknown export type HealthCheckHealthGetError = unknown @@ -462,6 +478,16 @@ export type V1SetWorkspaceMuxesResponse = void export type V1SetWorkspaceMuxesError = HTTPValidationError +export type V1ListWorkspacesByProviderData = { + path: { + provider_id: string + } +} + +export type V1ListWorkspacesByProviderResponse = Array + +export type V1ListWorkspacesByProviderError = HTTPValidationError + export type V1StreamSseResponse = unknown export type V1StreamSseError = unknown diff --git a/src/api/openapi.json b/src/api/openapi.json index cde65b55..a6d16753 100644 --- a/src/api/openapi.json +++ b/src/api/openapi.json @@ -989,6 +989,55 @@ } } }, + "/api/v1/workspaces/{provider_id}": { + "get": { + "tags": [ + "CodeGate API", + "Workspaces" + ], + "summary": "List Workspaces By Provider", + "description": "List workspaces by provider ID.", + "operationId": "v1_list_workspaces_by_provider", + "parameters": [ + { + "name": "provider_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "title": "Provider Id" + } + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/WorkspaceWithModel" + }, + "title": "Response V1 List Workspaces By Provider" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, "/api/v1/alerts_notification": { "get": { "tags": [ @@ -1478,6 +1527,16 @@ "type": "string", "title": "Name" }, + "config": { + "anyOf": [ + { + "$ref": "#/components/schemas/WorkspaceConfig" + }, + { + "type": "null" + } + ] + }, "rename_to": { "anyOf": [ { @@ -1590,6 +1649,17 @@ }, "MuxRule": { "properties": { + "provider_name": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Provider Name" + }, "provider_id": { "type": "string", "title": "Provider Id" @@ -1842,6 +1912,52 @@ "is_active" ], "title": "Workspace" + }, + "WorkspaceConfig": { + "properties": { + "system_prompt": { + "type": "string", + "title": "System Prompt" + }, + "muxing_rules": { + "items": { + "$ref": "#/components/schemas/MuxRule" + }, + "type": "array", + "title": "Muxing Rules" + } + }, + "type": "object", + "required": [ + "system_prompt", + "muxing_rules" + ], + "title": "WorkspaceConfig" + }, + "WorkspaceWithModel": { + "properties": { + "id": { + "type": "string", + "title": "Id" + }, + "name": { + "type": "string", + "pattern": "^[a-zA-Z0-9_-]+$", + "title": "Name" + }, + "provider_model_name": { + "type": "string", + "title": "Provider Model Name" + } + }, + "type": "object", + "required": [ + "id", + "name", + "provider_model_name" + ], + "title": "WorkspaceWithModel", + "description": "Returns a workspace ID with model name" } } } diff --git a/src/features/providers/components/table-providers.tsx b/src/features/providers/components/table-providers.tsx index a24bea52..d10dfab4 100644 --- a/src/features/providers/components/table-providers.tsx +++ b/src/features/providers/components/table-providers.tsx @@ -51,12 +51,12 @@ const COLUMNS: Column[] = [ function CellRenderer({ column, row, - deleteProvider, }: { column: Column row: ProviderEndpoint - deleteProvider: () => void }) { + const deleteProvider = useConfirmDeleteProvider(row.id) + return match(column.id) .with(COLUMN_MAP.provider, () => ( <> @@ -90,7 +90,15 @@ function CellRenderer({ )) .with(COLUMN_MAP.configuration, () => ( - )) @@ -99,7 +107,6 @@ function CellRenderer({ export function TableProviders() { const { data: providers = [] } = useProviders() - const deleteProvider = useConfirmDeleteProvider() return ( @@ -117,15 +124,7 @@ export function TableProviders() { id={column.id} alignment={column.alignment} > - { - deleteProvider({ - path: { provider_id: row.id as string }, - }) - }} - /> + )} diff --git a/src/features/providers/components/workspaces-by-provider.tsx b/src/features/providers/components/workspaces-by-provider.tsx new file mode 100644 index 00000000..63a35df0 --- /dev/null +++ b/src/features/providers/components/workspaces-by-provider.tsx @@ -0,0 +1,25 @@ +import { V1ListWorkspacesByProviderResponse } from '@/api/generated' +import { Badge } from '@stacklok/ui-kit' +import { uniqBy } from 'lodash' + +export function WorkspacesByProvider({ + workspaces = [], +}: { + workspaces: V1ListWorkspacesByProviderResponse | undefined +}) { + if (workspaces.length === 0) return null + return ( +
+

The following workspaces will be impacted by this action

+
+ {uniqBy(workspaces, 'name').map((item, index) => { + return ( + + {item.name} + + ) + })} +
+
+ ) +} diff --git a/src/features/providers/hooks/use-confirm-delete-provider.tsx b/src/features/providers/hooks/use-confirm-delete-provider.tsx index 3cce4bd6..24b23623 100644 --- a/src/features/providers/hooks/use-confirm-delete-provider.tsx +++ b/src/features/providers/hooks/use-confirm-delete-provider.tsx @@ -1,19 +1,22 @@ import { useConfirm } from '@/hooks/use-confirm' import { useCallback } from 'react' import { useMutationDeleteProvider } from './use-mutation-delete-provider' +import { useQueryWorkspacesByProvider } from './use-query-workspaces-by-provider' +import { WorkspacesByProvider } from '../components/workspaces-by-provider' -export function useConfirmDeleteProvider() { +export function useConfirmDeleteProvider( + providerId: string | undefined | null +) { const { mutateAsync: deleteProvider } = useMutationDeleteProvider() - + const { data: workspaces } = useQueryWorkspacesByProvider(providerId) const { confirm } = useConfirm() return useCallback( async (...params: Parameters) => { const answer = await confirm( <> -

- Are you sure you want to permanently delete this provider? -

+ +

Are you sure you want to permanently delete this provider?

, { buttons: { @@ -28,6 +31,6 @@ export function useConfirmDeleteProvider() { return deleteProvider(...params) } }, - [confirm, deleteProvider] + [confirm, deleteProvider, workspaces] ) } diff --git a/src/features/providers/hooks/use-invalidate-providers-queries.ts b/src/features/providers/hooks/use-invalidate-providers-queries.ts index 3c147681..d18dc080 100644 --- a/src/features/providers/hooks/use-invalidate-providers-queries.ts +++ b/src/features/providers/hooks/use-invalidate-providers-queries.ts @@ -1,6 +1,7 @@ import { v1ListAllModelsForAllProvidersQueryKey, v1ListProviderEndpointsQueryKey, + v1ListWorkspacesByProviderQueryKey, } from '@/api/generated/@tanstack/react-query.gen' import { useQueryClient } from '@tanstack/react-query' import { useCallback } from 'react' @@ -13,6 +14,7 @@ export function useInvalidateProvidersQueries() { invalidateQueries(queryClient, [ v1ListProviderEndpointsQueryKey, v1ListAllModelsForAllProvidersQueryKey, + v1ListWorkspacesByProviderQueryKey, ]) }, [queryClient]) diff --git a/src/features/providers/hooks/use-query-workspaces-by-provider.ts b/src/features/providers/hooks/use-query-workspaces-by-provider.ts new file mode 100644 index 00000000..ef3484a1 --- /dev/null +++ b/src/features/providers/hooks/use-query-workspaces-by-provider.ts @@ -0,0 +1,16 @@ +import { v1ListWorkspacesByProviderOptions } from '@/api/generated/@tanstack/react-query.gen' +import { useQuery } from '@tanstack/react-query' + +export function useQueryWorkspacesByProvider( + providerId: string | undefined | null +) { + if (!providerId) { + throw new Error('providerId is required') + } + + return useQuery({ + ...v1ListWorkspacesByProviderOptions({ path: { provider_id: providerId } }), + // eslint-disable-next-line no-restricted-syntax + refetchOnMount: true, + }) +} diff --git a/src/features/workspace/hooks/use-invalidate-workspace-queries.ts b/src/features/workspace/hooks/use-invalidate-workspace-queries.ts index 277db5b4..58957dc6 100644 --- a/src/features/workspace/hooks/use-invalidate-workspace-queries.ts +++ b/src/features/workspace/hooks/use-invalidate-workspace-queries.ts @@ -1,5 +1,7 @@ import { + v1GetWorkspaceMuxesQueryKey, v1ListArchivedWorkspacesQueryKey, + v1ListWorkspacesByProviderQueryKey, v1ListWorkspacesQueryKey, } from '@/api/generated/@tanstack/react-query.gen' import { invalidateQueries } from '@/lib/react-query-utils' @@ -13,6 +15,8 @@ export function useInvalidateWorkspaceQueries() { invalidateQueries(queryClient, [ v1ListWorkspacesQueryKey, v1ListArchivedWorkspacesQueryKey, + v1GetWorkspaceMuxesQueryKey, + v1ListWorkspacesByProviderQueryKey, ]) }, [queryClient])