diff --git a/packages/cypress/cypress/support/commands/odh.ts b/packages/cypress/cypress/support/commands/odh.ts index 8566ef425e1..df798f227c9 100644 --- a/packages/cypress/cypress/support/commands/odh.ts +++ b/packages/cypress/cypress/support/commands/odh.ts @@ -1131,6 +1131,10 @@ declare global { type: 'POST /maas/api/v1/api-keys', response: { data: OdhResponse }, ) => Cypress.Chainable) & + (( + type: 'GET /maas/api/v1/is-maas-admin', + response: OdhResponse<{ data: { allowed: boolean } }>, + ) => Cypress.Chainable) & (( type: 'GET /maas/api/v1/user', response: OdhResponse<{ data: { userId: string; clusterAdmin: boolean } }>, diff --git a/packages/cypress/cypress/tests/mocked/modelsAsAService/maasApiKeys.cy.ts b/packages/cypress/cypress/tests/mocked/modelsAsAService/maasApiKeys.cy.ts index 3c7e4dfafd1..b966e0d193e 100644 --- a/packages/cypress/cypress/tests/mocked/modelsAsAService/maasApiKeys.cy.ts +++ b/packages/cypress/cypress/tests/mocked/modelsAsAService/maasApiKeys.cy.ts @@ -1,7 +1,7 @@ import { mockDashboardConfig, mockDscStatus } from '@odh-dashboard/internal/__mocks__'; import { DataScienceStackComponent } from '@odh-dashboard/internal/concepts/areas/types'; import type { APIKey } from '@odh-dashboard/maas/types/api-key'; -import { asProductAdminUser } from '../../../utils/mockUsers'; +import { asClusterAdminUser, asProjectAdminUser } from '../../../utils/mockUsers'; import { apiKeysPage, bulkRevokeAPIKeyModal, @@ -22,7 +22,7 @@ const mockSearchResponse = (keys: APIKey[]) => ({ describe('API Keys Page', () => { beforeEach(() => { - asProductAdminUser(); + asClusterAdminUser(); cy.interceptOdh( 'GET /api/config', mockDashboardConfig({ @@ -33,6 +33,7 @@ describe('API Keys Page', () => { cy.interceptOdh('GET /maas/api/v1/user', { data: { userId: 'test-user', clusterAdmin: false }, }); + cy.interceptOdh('GET /maas/api/v1/is-maas-admin', { data: { allowed: true } }); cy.interceptOdh('GET /maas/api/v1/namespaces', { data: [] }); cy.interceptOdh( @@ -92,6 +93,13 @@ describe('API Keys Page', () => { .should('contain.text', 'production-backend'); }); + it('should not display the username filter for non-MaaS admins', () => { + asProjectAdminUser(); + apiKeysPage.visit(); + apiKeysPage.findFilterInput().should('not.exist'); + apiKeysPage.findUsernameFilterTooltip().should('not.exist'); + }); + it('should filter api keys by status', () => { const nonActiveKeys = mockAPIKeys().filter((k) => k.status !== 'active'); diff --git a/packages/maas/frontend/src/app/api/subscriptions.ts b/packages/maas/frontend/src/app/api/subscriptions.ts index 60371bc77e3..ad7fbc58258 100644 --- a/packages/maas/frontend/src/app/api/subscriptions.ts +++ b/packages/maas/frontend/src/app/api/subscriptions.ts @@ -14,8 +14,8 @@ const isMaaSSubscriptionRef = (v: unknown): v is ModelSubscriptionRef => isRecord(v) && typeof v.name === 'string' && typeof v.namespace === 'string' && - Array.isArray(v.tokenRateLimits) && - v.tokenRateLimits.every(isTokenRateLimit) && + (v.tokenRateLimits === undefined || + (Array.isArray(v.tokenRateLimits) && v.tokenRateLimits.every(isTokenRateLimit))) && (v.tokenRateLimitRef === undefined || typeof v.tokenRateLimitRef === 'string') && (v.billingRate === undefined || typeof v.billingRate === 'object'); @@ -23,13 +23,13 @@ const isMaaSSubscription = (v: unknown): v is MaaSSubscription => isRecord(v) && typeof v.name === 'string' && typeof v.namespace === 'string' && - typeof v.phase === 'string' && + (v.phase === undefined || typeof v.phase === 'string') && (v.priority === undefined || typeof v.priority === 'number') && typeof v.owner === 'object' && Array.isArray(v.modelRefs) && v.modelRefs.every(isMaaSSubscriptionRef) && (v.tokenMetadata === undefined || typeof v.tokenMetadata === 'object') && - typeof v.creationTimestamp === 'string'; + (v.creationTimestamp === undefined || typeof v.creationTimestamp === 'string'); const isTokenRateLimit = (v: unknown): v is TokenRateLimit => isRecord(v) && typeof v.limit === 'number' && typeof v.window === 'string'; diff --git a/packages/maas/frontend/src/app/pages/api-keys/AllApiKeysPage.tsx b/packages/maas/frontend/src/app/pages/api-keys/AllApiKeysPage.tsx index 4d0e3926fd7..168dea6c06b 100644 --- a/packages/maas/frontend/src/app/pages/api-keys/AllApiKeysPage.tsx +++ b/packages/maas/frontend/src/app/pages/api-keys/AllApiKeysPage.tsx @@ -27,8 +27,6 @@ const AllApiKeysPage: React.FC = () => { const [isModalOpen, setIsModalOpen] = React.useState(false); const [revokeApiKey, setRevokeApiKey] = React.useState(undefined); - // TODO: use this for hiding the username search for non-admins and for allowing admins to see all keys - // eslint-disable-next-line @typescript-eslint/no-unused-vars const [isMaasAdmin] = useIsMaasAdmin(); const [filterData, setFilterData] = React.useState(initialApiKeyFilterData); @@ -157,6 +155,7 @@ const AllApiKeysPage: React.FC = () => { isFetching={isFetching} toolbarContent={ void; onClearFilters: () => void; + isMaasAdmin: boolean; }; const ApiKeysToolbar: React.FC = ({ @@ -42,6 +43,7 @@ const ApiKeysToolbar: React.FC = ({ activeApiKeys, refresh, onClearFilters, + isMaasAdmin, }) => { const [isStatusSelectOpen, setIsStatusSelectOpen] = React.useState(false); @@ -109,34 +111,36 @@ const ApiKeysToolbar: React.FC = ({ - { - setLocalUsername(''); - onUsernameChange(''); - }} - categoryName="Username" - > - { + setLocalUsername(''); + onUsernameChange(''); + }} + categoryName="Username" > - { - setLocalUsername(value); - }} - onSearch={(_event, value) => onUsernameChange(value)} - onClear={() => { - setLocalUsername(''); - onUsernameChange(''); - }} - /> - - + + { + setLocalUsername(value); + }} + onSearch={(_event, value) => onUsernameChange(value)} + onClear={() => { + setLocalUsername(''); + onUsernameChange(''); + }} + /> + + + )} diff --git a/packages/maas/frontend/src/app/types/subscriptions.ts b/packages/maas/frontend/src/app/types/subscriptions.ts index bd1aad871a3..8392ef8453e 100644 --- a/packages/maas/frontend/src/app/types/subscriptions.ts +++ b/packages/maas/frontend/src/app/types/subscriptions.ts @@ -1,18 +1,18 @@ export type MaaSSubscription = { name: string; namespace: string; - phase: string; + phase?: string; priority?: number; owner: OwnerSpec; modelRefs: ModelSubscriptionRef[]; tokenMetadata?: TokenMetadata; - creationTimestamp: string; + creationTimestamp?: string; }; export type ModelSubscriptionRef = { name: string; namespace: string; - tokenRateLimits: TokenRateLimit[]; + tokenRateLimits?: TokenRateLimit[]; tokenRateLimitRef?: string; billingRate?: BillingRate; };