From 527b9c05783740ac8debf7629160311789159404 Mon Sep 17 00:00:00 2001 From: michal-zielonka <146939528+michal-zielonka@users.noreply.github.com> Date: Thu, 5 Nov 2026 08:01:24 +0000 Subject: [PATCH] feat(MPT-26777): Validate datasource ID based on API regexp --- frontend/src/api/ffc-api-model/index.ts | 2 +- frontend/src/api/ffc-api-model/types.gen.ts | 568 ++++++++++++++---- .../CreateEntitlement.Schema.spec.ts | 63 +- .../CreateEntitlement.Schema.tsx | 64 +- .../CreateEntitlementWizard.spec.mocks.tsx | 52 +- .../CreateEntitlementWizard.spec.tsx | 200 +++++- .../CreateEntitlementWizard.tsx | 21 +- .../steps/AffiliateStep.spec.tsx | 22 +- .../steps/AffiliateStep.tsx | 10 +- .../steps/wizardStepTestUtils.tsx | 9 +- frontend/src/i18n/en.json | 5 +- 11 files changed, 869 insertions(+), 147 deletions(-) diff --git a/frontend/src/api/ffc-api-model/index.ts b/frontend/src/api/ffc-api-model/index.ts index fc05c02a..d62cf1bf 100644 --- a/frontend/src/api/ffc-api-model/index.ts +++ b/frontend/src/api/ffc-api-model/index.ts @@ -1,4 +1,4 @@ // This file is auto-generated by @hey-api/openapi-ts export { addAdditionalAdminOpsV1OrganizationsOrganizationIdAddAdminPost, createAccountOpsV1AccountsPost, createEntitlementOpsV1EntitlementsPost, deleteEntitlementByIdOpsV1EntitlementsIdDelete, deleteOrganizationByIdOpsV1OrganizationsOrganizationIdDelete, forceReimportDatasourceOpsV1OrganizationsOrganizationIdDatasourcesDatasourceIdForceReimportPost, getAccountByIdOpsV1AccountsIdGet, getAccountsOpsV1AccountsGet, getDatasourceByIdOpsV1OrganizationsOrganizationIdDatasourcesDatasourceIdGet, getDatasourcesByOrganizationIdOpsV1OrganizationsOrganizationIdDatasourcesGet, getEmployeesByOrganizationIdOpsV1OrganizationsOrganizationIdEmployeesGet, getEntitlementByIdOpsV1EntitlementsIdGet, getEntitlementsOpsV1EntitlementsGet, getEventsByOrganizationIdOpsV1OrganizationsOrganizationIdEventsGet, getOrganizationByIdOpsV1OrganizationsOrganizationIdGet, getOrganizationsOpsV1OrganizationsGet, listAccountUsersOpsV1AccountsIdUsersGet, listDatasourceExpensesOpsV1ExpensesGet, makeOrganizationUserAdminOpsV1OrganizationsOrganizationIdEmployeesUserIdMakeAdminPost, meOpsV1MeGet, type Options, terminateEntitlementOpsV1EntitlementsIdTerminatePost, updateAccountOpsV1AccountsIdPut, updateOrganizationOpsV1OrganizationsOrganizationIdPut } from './sdk.gen'; -export type { AccountCreate, AccountIntegration, AccountRead, AccountReference, AccountStats, AccountStatus, AccountType, AccountUpdate, AccountUserReferenceWithAccount, AccountUserReferenceWithUser, AccountUserStatus, ActorReference, ActorType, AddAdditionalAdminOpsV1OrganizationsOrganizationIdAddAdminPostData, AddAdditionalAdminOpsV1OrganizationsOrganizationIdAddAdminPostError, AddAdditionalAdminOpsV1OrganizationsOrganizationIdAddAdminPostErrors, AddAdditionalAdminOpsV1OrganizationsOrganizationIdAddAdminPostResponses, AdditionalAdminRequestCreate, AuditEventsSchema, AuditFieldSchema, AwsAccountType, AwsAuthenticationType, AwsDetails, AwsExportType, AzureSubscriptionDetails, AzureTenantDetails, ClientAccountReference, ClientOptions, CreateAccountOpsV1AccountsPostData, CreateAccountOpsV1AccountsPostError, CreateAccountOpsV1AccountsPostErrors, CreateAccountOpsV1AccountsPostResponse, CreateAccountOpsV1AccountsPostResponses, CreateEntitlementOpsV1EntitlementsPostData, CreateEntitlementOpsV1EntitlementsPostError, CreateEntitlementOpsV1EntitlementsPostErrors, CreateEntitlementOpsV1EntitlementsPostResponse, CreateEntitlementOpsV1EntitlementsPostResponses, DatasourceBase, DatasourceBillingInfo, DatasourceDetailRead, DatasourceDiscoveryItem, DatasourceExpenseRead, DatasourceForceReimport, DatasourceImportStatus, DatasourceMetricsInfo, DatasourceRead, DatasourceType, DeleteEntitlementByIdOpsV1EntitlementsIdDeleteData, DeleteEntitlementByIdOpsV1EntitlementsIdDeleteError, DeleteEntitlementByIdOpsV1EntitlementsIdDeleteErrors, DeleteEntitlementByIdOpsV1EntitlementsIdDeleteResponse, DeleteEntitlementByIdOpsV1EntitlementsIdDeleteResponses, DeleteOrganizationByIdOpsV1OrganizationsOrganizationIdDeleteData, DeleteOrganizationByIdOpsV1OrganizationsOrganizationIdDeleteError, DeleteOrganizationByIdOpsV1OrganizationsOrganizationIdDeleteErrors, DeleteOrganizationByIdOpsV1OrganizationsOrganizationIdDeleteResponse, DeleteOrganizationByIdOpsV1OrganizationsOrganizationIdDeleteResponses, EmployeeRead, EmployeeReadWritable, EmployeeRole, EntitlementCreate, EntitlementRead, EntitlementReedemEventSchema, EntitlementsEventsSchema, EntitlementStats, EntitlementStatus, EventLevel, ForceReimportDatasourceOpsV1OrganizationsOrganizationIdDatasourcesDatasourceIdForceReimportPostData, ForceReimportDatasourceOpsV1OrganizationsOrganizationIdDatasourcesDatasourceIdForceReimportPostError, ForceReimportDatasourceOpsV1OrganizationsOrganizationIdDatasourcesDatasourceIdForceReimportPostErrors, ForceReimportDatasourceOpsV1OrganizationsOrganizationIdDatasourcesDatasourceIdForceReimportPostResponse, ForceReimportDatasourceOpsV1OrganizationsOrganizationIdDatasourcesDatasourceIdForceReimportPostResponses, GcpDetails, GetAccountByIdOpsV1AccountsIdGetData, GetAccountByIdOpsV1AccountsIdGetError, GetAccountByIdOpsV1AccountsIdGetErrors, GetAccountByIdOpsV1AccountsIdGetResponse, GetAccountByIdOpsV1AccountsIdGetResponses, GetAccountsOpsV1AccountsGetData, GetAccountsOpsV1AccountsGetResponse, GetAccountsOpsV1AccountsGetResponses, GetDatasourceByIdOpsV1OrganizationsOrganizationIdDatasourcesDatasourceIdGetData, GetDatasourceByIdOpsV1OrganizationsOrganizationIdDatasourcesDatasourceIdGetError, GetDatasourceByIdOpsV1OrganizationsOrganizationIdDatasourcesDatasourceIdGetErrors, GetDatasourceByIdOpsV1OrganizationsOrganizationIdDatasourcesDatasourceIdGetResponse, GetDatasourceByIdOpsV1OrganizationsOrganizationIdDatasourcesDatasourceIdGetResponses, GetDatasourcesByOrganizationIdOpsV1OrganizationsOrganizationIdDatasourcesGetData, GetDatasourcesByOrganizationIdOpsV1OrganizationsOrganizationIdDatasourcesGetError, GetDatasourcesByOrganizationIdOpsV1OrganizationsOrganizationIdDatasourcesGetErrors, GetDatasourcesByOrganizationIdOpsV1OrganizationsOrganizationIdDatasourcesGetResponse, GetDatasourcesByOrganizationIdOpsV1OrganizationsOrganizationIdDatasourcesGetResponses, GetEmployeesByOrganizationIdOpsV1OrganizationsOrganizationIdEmployeesGetData, GetEmployeesByOrganizationIdOpsV1OrganizationsOrganizationIdEmployeesGetError, GetEmployeesByOrganizationIdOpsV1OrganizationsOrganizationIdEmployeesGetErrors, GetEmployeesByOrganizationIdOpsV1OrganizationsOrganizationIdEmployeesGetResponse, GetEmployeesByOrganizationIdOpsV1OrganizationsOrganizationIdEmployeesGetResponses, GetEntitlementByIdOpsV1EntitlementsIdGetData, GetEntitlementByIdOpsV1EntitlementsIdGetError, GetEntitlementByIdOpsV1EntitlementsIdGetErrors, GetEntitlementByIdOpsV1EntitlementsIdGetResponse, GetEntitlementByIdOpsV1EntitlementsIdGetResponses, GetEntitlementsOpsV1EntitlementsGetData, GetEntitlementsOpsV1EntitlementsGetResponse, GetEntitlementsOpsV1EntitlementsGetResponses, GetEventsByOrganizationIdOpsV1OrganizationsOrganizationIdEventsGetData, GetEventsByOrganizationIdOpsV1OrganizationsOrganizationIdEventsGetError, GetEventsByOrganizationIdOpsV1OrganizationsOrganizationIdEventsGetErrors, GetEventsByOrganizationIdOpsV1OrganizationsOrganizationIdEventsGetResponse, GetEventsByOrganizationIdOpsV1OrganizationsOrganizationIdEventsGetResponses, GetOrganizationByIdOpsV1OrganizationsOrganizationIdGetData, GetOrganizationByIdOpsV1OrganizationsOrganizationIdGetError, GetOrganizationByIdOpsV1OrganizationsOrganizationIdGetErrors, GetOrganizationByIdOpsV1OrganizationsOrganizationIdGetResponse, GetOrganizationByIdOpsV1OrganizationsOrganizationIdGetResponses, GetOrganizationsOpsV1OrganizationsGetData, GetOrganizationsOpsV1OrganizationsGetResponse, GetOrganizationsOpsV1OrganizationsGetResponses, HttpValidationError, IdSchema, LimitOffsetPageAccountRead, LimitOffsetPageDatasourceExpenseRead, LimitOffsetPageDatasourceRead, LimitOffsetPageEmployeeRead, LimitOffsetPageEmployeeReadWritable, LimitOffsetPageEntitlementRead, LimitOffsetPageOrganizationEventRead, LimitOffsetPageOrganizationRead, LimitOffsetPageOrganizationReadWritable, LimitOffsetPageUserRead, ListAccountUsersOpsV1AccountsIdUsersGetData, ListAccountUsersOpsV1AccountsIdUsersGetError, ListAccountUsersOpsV1AccountsIdUsersGetErrors, ListAccountUsersOpsV1AccountsIdUsersGetResponse, ListAccountUsersOpsV1AccountsIdUsersGetResponses, ListDatasourceExpensesOpsV1ExpensesGetData, ListDatasourceExpensesOpsV1ExpensesGetResponse, ListDatasourceExpensesOpsV1ExpensesGetResponses, MakeOrganizationUserAdminOpsV1OrganizationsOrganizationIdEmployeesUserIdMakeAdminPostData, MakeOrganizationUserAdminOpsV1OrganizationsOrganizationIdEmployeesUserIdMakeAdminPostError, MakeOrganizationUserAdminOpsV1OrganizationsOrganizationIdEmployeesUserIdMakeAdminPostErrors, MakeOrganizationUserAdminOpsV1OrganizationsOrganizationIdEmployeesUserIdMakeAdminPostResponse, MakeOrganizationUserAdminOpsV1OrganizationsOrganizationIdEmployeesUserIdMakeAdminPostResponses, Me, MeOpsV1MeGetData, MeOpsV1MeGetResponse, MeOpsV1MeGetResponses, OrganizationEventRead, OrganizationEventsSchema, OrganizationExpensesInfo, OrganizationRead, OrganizationReadWritable, OrganizationReference, OrganizationStatus, OrganizationUpdate, SystemReference, TerminateEntitlementOpsV1EntitlementsIdTerminatePostData, TerminateEntitlementOpsV1EntitlementsIdTerminatePostError, TerminateEntitlementOpsV1EntitlementsIdTerminatePostErrors, TerminateEntitlementOpsV1EntitlementsIdTerminatePostResponse, TerminateEntitlementOpsV1EntitlementsIdTerminatePostResponses, TerminationFeedbackRead, TerminationReason, UpdateAccountOpsV1AccountsIdPutData, UpdateAccountOpsV1AccountsIdPutError, UpdateAccountOpsV1AccountsIdPutErrors, UpdateAccountOpsV1AccountsIdPutResponse, UpdateAccountOpsV1AccountsIdPutResponses, UpdateOrganizationOpsV1OrganizationsOrganizationIdPutData, UpdateOrganizationOpsV1OrganizationsOrganizationIdPutError, UpdateOrganizationOpsV1OrganizationsOrganizationIdPutErrors, UpdateOrganizationOpsV1OrganizationsOrganizationIdPutResponse, UpdateOrganizationOpsV1OrganizationsOrganizationIdPutResponses, UserRead, UserReference, UserStatus, ValidationError } from './types.gen'; +export type { AccountCreate, AccountIntegration, AccountRead, AccountReference, AccountStats, AccountStatus, AccountType, AccountUpdate, AccountUserReferenceWithAccount, AccountUserReferenceWithUser, AccountUserStatus, ActorReference, ActorType, AddAdditionalAdminOpsV1OrganizationsOrganizationIdAddAdminPostData, AddAdditionalAdminOpsV1OrganizationsOrganizationIdAddAdminPostError, AddAdditionalAdminOpsV1OrganizationsOrganizationIdAddAdminPostErrors, AddAdditionalAdminOpsV1OrganizationsOrganizationIdAddAdminPostResponses, AdditionalAdminRequestCreate, AuditEventsSchema, AuditFieldSchema, AwsAccountType, AwsAuthenticationType, AwsDetails, AwsExportType, AzureSubscriptionDetails, AzureTenantDetails, ClientAccountReference, ClientOptions, CloudProviderRead, CreateAccountOpsV1AccountsPostData, CreateAccountOpsV1AccountsPostError, CreateAccountOpsV1AccountsPostErrors, CreateAccountOpsV1AccountsPostResponse, CreateAccountOpsV1AccountsPostResponses, CreateEntitlementOpsV1EntitlementsPostData, CreateEntitlementOpsV1EntitlementsPostError, CreateEntitlementOpsV1EntitlementsPostErrors, CreateEntitlementOpsV1EntitlementsPostResponse, CreateEntitlementOpsV1EntitlementsPostResponses, DatasourceBase, DatasourceBillingInfo, DatasourceDetailRead, DatasourceDiscoveryItem, DatasourceExpenseRead, DatasourceForceReimport, DatasourceImportStatus, DatasourceMetricsInfo, DatasourceRead, DatasourceType, DeleteEntitlementByIdOpsV1EntitlementsIdDeleteData, DeleteEntitlementByIdOpsV1EntitlementsIdDeleteError, DeleteEntitlementByIdOpsV1EntitlementsIdDeleteErrors, DeleteEntitlementByIdOpsV1EntitlementsIdDeleteResponse, DeleteEntitlementByIdOpsV1EntitlementsIdDeleteResponses, DeleteOrganizationByIdOpsV1OrganizationsOrganizationIdDeleteData, DeleteOrganizationByIdOpsV1OrganizationsOrganizationIdDeleteError, DeleteOrganizationByIdOpsV1OrganizationsOrganizationIdDeleteErrors, DeleteOrganizationByIdOpsV1OrganizationsOrganizationIdDeleteResponse, DeleteOrganizationByIdOpsV1OrganizationsOrganizationIdDeleteResponses, EmployeeRead, EmployeeReadWritable, EmployeeRole, EntitlementCreate, EntitlementRead, EntitlementReedemEventSchema, EntitlementsEventsSchema, EntitlementStats, EntitlementStatus, EventLevel, ForceReimportDatasourceOpsV1OrganizationsOrganizationIdDatasourcesDatasourceIdForceReimportPostData, ForceReimportDatasourceOpsV1OrganizationsOrganizationIdDatasourcesDatasourceIdForceReimportPostError, ForceReimportDatasourceOpsV1OrganizationsOrganizationIdDatasourcesDatasourceIdForceReimportPostErrors, ForceReimportDatasourceOpsV1OrganizationsOrganizationIdDatasourcesDatasourceIdForceReimportPostResponse, ForceReimportDatasourceOpsV1OrganizationsOrganizationIdDatasourcesDatasourceIdForceReimportPostResponses, GcpDetails, GetAccountByIdOpsV1AccountsIdGetData, GetAccountByIdOpsV1AccountsIdGetError, GetAccountByIdOpsV1AccountsIdGetErrors, GetAccountByIdOpsV1AccountsIdGetResponse, GetAccountByIdOpsV1AccountsIdGetResponses, GetAccountsOpsV1AccountsGetData, GetAccountsOpsV1AccountsGetResponse, GetAccountsOpsV1AccountsGetResponses, GetDatasourceByIdOpsV1OrganizationsOrganizationIdDatasourcesDatasourceIdGetData, GetDatasourceByIdOpsV1OrganizationsOrganizationIdDatasourcesDatasourceIdGetError, GetDatasourceByIdOpsV1OrganizationsOrganizationIdDatasourcesDatasourceIdGetErrors, GetDatasourceByIdOpsV1OrganizationsOrganizationIdDatasourcesDatasourceIdGetResponse, GetDatasourceByIdOpsV1OrganizationsOrganizationIdDatasourcesDatasourceIdGetResponses, GetDatasourcesByOrganizationIdOpsV1OrganizationsOrganizationIdDatasourcesGetData, GetDatasourcesByOrganizationIdOpsV1OrganizationsOrganizationIdDatasourcesGetError, GetDatasourcesByOrganizationIdOpsV1OrganizationsOrganizationIdDatasourcesGetErrors, GetDatasourcesByOrganizationIdOpsV1OrganizationsOrganizationIdDatasourcesGetResponse, GetDatasourcesByOrganizationIdOpsV1OrganizationsOrganizationIdDatasourcesGetResponses, GetEmployeesByOrganizationIdOpsV1OrganizationsOrganizationIdEmployeesGetData, GetEmployeesByOrganizationIdOpsV1OrganizationsOrganizationIdEmployeesGetError, GetEmployeesByOrganizationIdOpsV1OrganizationsOrganizationIdEmployeesGetErrors, GetEmployeesByOrganizationIdOpsV1OrganizationsOrganizationIdEmployeesGetResponse, GetEmployeesByOrganizationIdOpsV1OrganizationsOrganizationIdEmployeesGetResponses, GetEntitlementByIdOpsV1EntitlementsIdGetData, GetEntitlementByIdOpsV1EntitlementsIdGetError, GetEntitlementByIdOpsV1EntitlementsIdGetErrors, GetEntitlementByIdOpsV1EntitlementsIdGetResponse, GetEntitlementByIdOpsV1EntitlementsIdGetResponses, GetEntitlementsOpsV1EntitlementsGetData, GetEntitlementsOpsV1EntitlementsGetResponse, GetEntitlementsOpsV1EntitlementsGetResponses, GetEventsByOrganizationIdOpsV1OrganizationsOrganizationIdEventsGetData, GetEventsByOrganizationIdOpsV1OrganizationsOrganizationIdEventsGetError, GetEventsByOrganizationIdOpsV1OrganizationsOrganizationIdEventsGetErrors, GetEventsByOrganizationIdOpsV1OrganizationsOrganizationIdEventsGetResponse, GetEventsByOrganizationIdOpsV1OrganizationsOrganizationIdEventsGetResponses, GetOrganizationByIdOpsV1OrganizationsOrganizationIdGetData, GetOrganizationByIdOpsV1OrganizationsOrganizationIdGetError, GetOrganizationByIdOpsV1OrganizationsOrganizationIdGetErrors, GetOrganizationByIdOpsV1OrganizationsOrganizationIdGetResponse, GetOrganizationByIdOpsV1OrganizationsOrganizationIdGetResponses, GetOrganizationsOpsV1OrganizationsGetData, GetOrganizationsOpsV1OrganizationsGetResponse, GetOrganizationsOpsV1OrganizationsGetResponses, HttpValidationError, IdSchema, LimitOffsetPageAccountRead, LimitOffsetPageDatasourceExpenseRead, LimitOffsetPageDatasourceRead, LimitOffsetPageEmployeeRead, LimitOffsetPageEmployeeReadWritable, LimitOffsetPageEntitlementRead, LimitOffsetPageOrganizationEventRead, LimitOffsetPageOrganizationRead, LimitOffsetPageOrganizationReadWritable, LimitOffsetPageUserRead, ListAccountUsersOpsV1AccountsIdUsersGetData, ListAccountUsersOpsV1AccountsIdUsersGetError, ListAccountUsersOpsV1AccountsIdUsersGetErrors, ListAccountUsersOpsV1AccountsIdUsersGetResponse, ListAccountUsersOpsV1AccountsIdUsersGetResponses, ListDatasourceExpensesOpsV1ExpensesGetData, ListDatasourceExpensesOpsV1ExpensesGetResponse, ListDatasourceExpensesOpsV1ExpensesGetResponses, MakeOrganizationUserAdminOpsV1OrganizationsOrganizationIdEmployeesUserIdMakeAdminPostData, MakeOrganizationUserAdminOpsV1OrganizationsOrganizationIdEmployeesUserIdMakeAdminPostError, MakeOrganizationUserAdminOpsV1OrganizationsOrganizationIdEmployeesUserIdMakeAdminPostErrors, MakeOrganizationUserAdminOpsV1OrganizationsOrganizationIdEmployeesUserIdMakeAdminPostResponse, MakeOrganizationUserAdminOpsV1OrganizationsOrganizationIdEmployeesUserIdMakeAdminPostResponses, Me, MeOpsV1MeGetData, MeOpsV1MeGetResponse, MeOpsV1MeGetResponses, OrganizationEventRead, OrganizationEventsSchema, OrganizationExpensesInfo, OrganizationRead, OrganizationReadWritable, OrganizationReference, OrganizationStatus, OrganizationUpdate, SystemReference, TerminateEntitlementOpsV1EntitlementsIdTerminatePostData, TerminateEntitlementOpsV1EntitlementsIdTerminatePostError, TerminateEntitlementOpsV1EntitlementsIdTerminatePostErrors, TerminateEntitlementOpsV1EntitlementsIdTerminatePostResponse, TerminateEntitlementOpsV1EntitlementsIdTerminatePostResponses, TerminationFeedbackRead, TerminationReason, UpdateAccountOpsV1AccountsIdPutData, UpdateAccountOpsV1AccountsIdPutError, UpdateAccountOpsV1AccountsIdPutErrors, UpdateAccountOpsV1AccountsIdPutResponse, UpdateAccountOpsV1AccountsIdPutResponses, UpdateOrganizationOpsV1OrganizationsOrganizationIdPutData, UpdateOrganizationOpsV1OrganizationsOrganizationIdPutError, UpdateOrganizationOpsV1OrganizationsOrganizationIdPutErrors, UpdateOrganizationOpsV1OrganizationsOrganizationIdPutResponse, UpdateOrganizationOpsV1OrganizationsOrganizationIdPutResponses, UserRead, UserReference, UserStatus, ValidationError } from './types.gen'; diff --git a/frontend/src/api/ffc-api-model/types.gen.ts b/frontend/src/api/ffc-api-model/types.gen.ts index 90724117..57de4ffc 100644 --- a/frontend/src/api/ffc-api-model/types.gen.ts +++ b/frontend/src/api/ffc-api-model/types.gen.ts @@ -68,7 +68,7 @@ export type AccountRead = { /** * ActorType */ - type: 'user' | 'system'; + type: 'user' | 'system' | 'app'; /** * Name */ @@ -91,7 +91,7 @@ export type AccountRead = { /** * ActorType */ - type: 'user' | 'system'; + type: 'user' | 'system' | 'app'; /** * Name */ @@ -111,7 +111,7 @@ export type AccountRead = { /** * ActorType */ - type: 'user' | 'system'; + type: 'user' | 'system' | 'app'; /** * Name */ @@ -171,6 +171,24 @@ export type AccountRead = { * AccountType */ type: 'admin' | 'operations' | 'affiliate'; + cloud_provider?: { + /** + * Name + */ + name: string; + /** + * Icon + */ + icon: string; + /** + * Datasource Id Format + */ + datasource_id_format: string; + /** + * Id + */ + id: string; + } | null; /** * AccountStats */ @@ -218,6 +236,24 @@ export type AccountReference = { * An external identifier for the account */ external_id: string; + cloud_provider?: { + /** + * Name + */ + name: string; + /** + * Icon + */ + icon: string; + /** + * Datasource Id Format + */ + datasource_id_format: string; + /** + * Id + */ + id: string; + } | null; }; /** @@ -311,6 +347,24 @@ export type AccountUserReferenceWithAccount = { * An external identifier for the account */ external_id: string; + cloud_provider?: { + /** + * Name + */ + name: string; + /** + * Icon + */ + icon: string; + /** + * Datasource Id Format + */ + datasource_id_format: string; + /** + * Id + */ + id: string; + } | null; }; }; @@ -373,7 +427,7 @@ export type ActorReference = { /** * ActorType */ - type: 'user' | 'system'; + type: 'user' | 'system' | 'app'; /** * Name */ @@ -383,7 +437,7 @@ export type ActorReference = { /** * ActorType */ -export type ActorType = 'user' | 'system'; +export type ActorType = 'user' | 'system' | 'app'; /** * AdditionalAdminRequestCreate @@ -423,7 +477,7 @@ export type AuditEventsSchema = { /** * ActorType */ - type: 'user' | 'system'; + type: 'user' | 'system' | 'app'; /** * Name */ @@ -446,7 +500,7 @@ export type AuditEventsSchema = { /** * ActorType */ - type: 'user' | 'system'; + type: 'user' | 'system' | 'app'; /** * Name */ @@ -466,7 +520,7 @@ export type AuditEventsSchema = { /** * ActorType */ - type: 'user' | 'system'; + type: 'user' | 'system' | 'app'; /** * Name */ @@ -491,7 +545,7 @@ export type AuditFieldSchema = { /** * ActorType */ - type: 'user' | 'system'; + type: 'user' | 'system' | 'app'; /** * Name */ @@ -657,6 +711,28 @@ export type ClientAccountReference = { icon?: string | null; }; +/** + * CloudProviderRead + */ +export type CloudProviderRead = { + /** + * Name + */ + name: string; + /** + * Icon + */ + icon: string; + /** + * Datasource Id Format + */ + datasource_id_format: string; + /** + * Id + */ + id: string; +}; + /** * DatasourceBase */ @@ -1033,7 +1109,7 @@ export type DatasourceExpenseRead = { /** * ActorType */ - type: 'user' | 'system'; + type: 'user' | 'system' | 'app'; /** * Name */ @@ -1056,7 +1132,7 @@ export type DatasourceExpenseRead = { /** * ActorType */ - type: 'user' | 'system'; + type: 'user' | 'system' | 'app'; /** * Name */ @@ -1076,7 +1152,7 @@ export type DatasourceExpenseRead = { /** * ActorType */ - type: 'user' | 'system'; + type: 'user' | 'system' | 'app'; /** * Name */ @@ -1391,6 +1467,24 @@ export type EntitlementRead = { * An external identifier for the account */ external_id: string; + cloud_provider?: { + /** + * Name + */ + name: string; + /** + * Icon + */ + icon: string; + /** + * Datasource Id Format + */ + datasource_id_format: string; + /** + * Id + */ + id: string; + } | null; }; /** * EntitlementStatus @@ -1416,7 +1510,7 @@ export type EntitlementRead = { /** * ActorType */ - type: 'user' | 'system'; + type: 'user' | 'system' | 'app'; /** * Name */ @@ -1439,7 +1533,7 @@ export type EntitlementRead = { /** * ActorType */ - type: 'user' | 'system'; + type: 'user' | 'system' | 'app'; /** * Name */ @@ -1459,7 +1553,7 @@ export type EntitlementRead = { /** * ActorType */ - type: 'user' | 'system'; + type: 'user' | 'system' | 'app'; /** * Name */ @@ -1502,7 +1596,7 @@ export type EntitlementRead = { /** * ActorType */ - type: 'user' | 'system'; + type: 'user' | 'system' | 'app'; /** * Name */ @@ -1582,7 +1676,7 @@ export type EntitlementsEventsSchema = { /** * ActorType */ - type: 'user' | 'system'; + type: 'user' | 'system' | 'app'; /** * Name */ @@ -1605,7 +1699,7 @@ export type EntitlementsEventsSchema = { /** * ActorType */ - type: 'user' | 'system'; + type: 'user' | 'system' | 'app'; /** * Name */ @@ -1625,7 +1719,7 @@ export type EntitlementsEventsSchema = { /** * ActorType */ - type: 'user' | 'system'; + type: 'user' | 'system' | 'app'; /** * Name */ @@ -1668,7 +1762,7 @@ export type EntitlementsEventsSchema = { /** * ActorType */ - type: 'user' | 'system'; + type: 'user' | 'system' | 'app'; /** * Name */ @@ -1791,7 +1885,7 @@ export type LimitOffsetPageAccountRead = { /** * ActorType */ - type: 'user' | 'system'; + type: 'user' | 'system' | 'app'; /** * Name */ @@ -1814,7 +1908,7 @@ export type LimitOffsetPageAccountRead = { /** * ActorType */ - type: 'user' | 'system'; + type: 'user' | 'system' | 'app'; /** * Name */ @@ -1834,7 +1928,7 @@ export type LimitOffsetPageAccountRead = { /** * ActorType */ - type: 'user' | 'system'; + type: 'user' | 'system' | 'app'; /** * Name */ @@ -1894,6 +1988,24 @@ export type LimitOffsetPageAccountRead = { * AccountType */ type: 'admin' | 'operations' | 'affiliate'; + cloud_provider?: { + /** + * Name + */ + name: string; + /** + * Icon + */ + icon: string; + /** + * Datasource Id Format + */ + datasource_id_format: string; + /** + * Id + */ + id: string; + } | null; /** * AccountStats */ @@ -1959,7 +2071,7 @@ export type LimitOffsetPageDatasourceExpenseRead = { /** * ActorType */ - type: 'user' | 'system'; + type: 'user' | 'system' | 'app'; /** * Name */ @@ -1982,7 +2094,7 @@ export type LimitOffsetPageDatasourceExpenseRead = { /** * ActorType */ - type: 'user' | 'system'; + type: 'user' | 'system' | 'app'; /** * Name */ @@ -2002,7 +2114,7 @@ export type LimitOffsetPageDatasourceExpenseRead = { /** * ActorType */ - type: 'user' | 'system'; + type: 'user' | 'system' | 'app'; /** * Name */ @@ -2282,6 +2394,24 @@ export type LimitOffsetPageEntitlementRead = { * An external identifier for the account */ external_id: string; + cloud_provider?: { + /** + * Name + */ + name: string; + /** + * Icon + */ + icon: string; + /** + * Datasource Id Format + */ + datasource_id_format: string; + /** + * Id + */ + id: string; + } | null; }; /** * EntitlementStatus @@ -2307,7 +2437,7 @@ export type LimitOffsetPageEntitlementRead = { /** * ActorType */ - type: 'user' | 'system'; + type: 'user' | 'system' | 'app'; /** * Name */ @@ -2330,7 +2460,7 @@ export type LimitOffsetPageEntitlementRead = { /** * ActorType */ - type: 'user' | 'system'; + type: 'user' | 'system' | 'app'; /** * Name */ @@ -2350,7 +2480,7 @@ export type LimitOffsetPageEntitlementRead = { /** * ActorType */ - type: 'user' | 'system'; + type: 'user' | 'system' | 'app'; /** * Name */ @@ -2393,7 +2523,7 @@ export type LimitOffsetPageEntitlementRead = { /** * ActorType */ - type: 'user' | 'system'; + type: 'user' | 'system' | 'app'; /** * Name */ @@ -2576,7 +2706,7 @@ export type LimitOffsetPageOrganizationRead = { /** * ActorType */ - type: 'user' | 'system'; + type: 'user' | 'system' | 'app'; /** * Name */ @@ -2599,7 +2729,7 @@ export type LimitOffsetPageOrganizationRead = { /** * ActorType */ - type: 'user' | 'system'; + type: 'user' | 'system' | 'app'; /** * Name */ @@ -2619,7 +2749,7 @@ export type LimitOffsetPageOrganizationRead = { /** * ActorType */ - type: 'user' | 'system'; + type: 'user' | 'system' | 'app'; /** * Name */ @@ -2639,7 +2769,7 @@ export type LimitOffsetPageOrganizationRead = { /** * ActorType */ - type: 'user' | 'system'; + type: 'user' | 'system' | 'app'; /** * Name */ @@ -2745,7 +2875,7 @@ export type LimitOffsetPageUserRead = { /** * ActorType */ - type: 'user' | 'system'; + type: 'user' | 'system' | 'app'; /** * Name */ @@ -2768,7 +2898,7 @@ export type LimitOffsetPageUserRead = { /** * ActorType */ - type: 'user' | 'system'; + type: 'user' | 'system' | 'app'; /** * Name */ @@ -2788,7 +2918,7 @@ export type LimitOffsetPageUserRead = { /** * ActorType */ - type: 'user' | 'system'; + type: 'user' | 'system' | 'app'; /** * Name */ @@ -2844,6 +2974,24 @@ export type LimitOffsetPageUserRead = { * An external identifier for the account */ external_id: string; + cloud_provider?: { + /** + * Name + */ + name: string; + /** + * Icon + */ + icon: string; + /** + * Datasource Id Format + */ + datasource_id_format: string; + /** + * Id + */ + id: string; + } | null; }; } | null; }>; @@ -2888,6 +3036,24 @@ export type Me = { * An external identifier for the account */ external_id: string; + cloud_provider?: { + /** + * Name + */ + name: string; + /** + * Icon + */ + icon: string; + /** + * Datasource Id Format + */ + datasource_id_format: string; + /** + * Id + */ + id: string; + } | null; }; system: { /** @@ -3009,7 +3175,7 @@ export type OrganizationEventsSchema = { /** * ActorType */ - type: 'user' | 'system'; + type: 'user' | 'system' | 'app'; /** * Name */ @@ -3032,7 +3198,7 @@ export type OrganizationEventsSchema = { /** * ActorType */ - type: 'user' | 'system'; + type: 'user' | 'system' | 'app'; /** * Name */ @@ -3052,7 +3218,7 @@ export type OrganizationEventsSchema = { /** * ActorType */ - type: 'user' | 'system'; + type: 'user' | 'system' | 'app'; /** * Name */ @@ -3072,7 +3238,7 @@ export type OrganizationEventsSchema = { /** * ActorType */ - type: 'user' | 'system'; + type: 'user' | 'system' | 'app'; /** * Name */ @@ -3176,7 +3342,7 @@ export type OrganizationRead = { /** * ActorType */ - type: 'user' | 'system'; + type: 'user' | 'system' | 'app'; /** * Name */ @@ -3199,7 +3365,7 @@ export type OrganizationRead = { /** * ActorType */ - type: 'user' | 'system'; + type: 'user' | 'system' | 'app'; /** * Name */ @@ -3219,7 +3385,7 @@ export type OrganizationRead = { /** * ActorType */ - type: 'user' | 'system'; + type: 'user' | 'system' | 'app'; /** * Name */ @@ -3239,7 +3405,7 @@ export type OrganizationRead = { /** * ActorType */ - type: 'user' | 'system'; + type: 'user' | 'system' | 'app'; /** * Name */ @@ -3408,7 +3574,7 @@ export type UserRead = { /** * ActorType */ - type: 'user' | 'system'; + type: 'user' | 'system' | 'app'; /** * Name */ @@ -3431,7 +3597,7 @@ export type UserRead = { /** * ActorType */ - type: 'user' | 'system'; + type: 'user' | 'system' | 'app'; /** * Name */ @@ -3451,7 +3617,7 @@ export type UserRead = { /** * ActorType */ - type: 'user' | 'system'; + type: 'user' | 'system' | 'app'; /** * Name */ @@ -3507,6 +3673,24 @@ export type UserRead = { * An external identifier for the account */ external_id: string; + cloud_provider?: { + /** + * Name + */ + name: string; + /** + * Icon + */ + icon: string; + /** + * Datasource Id Format + */ + datasource_id_format: string; + /** + * Id + */ + id: string; + } | null; }; } | null; }; @@ -3720,7 +3904,7 @@ export type LimitOffsetPageOrganizationReadWritable = { /** * ActorType */ - type: 'user' | 'system'; + type: 'user' | 'system' | 'app'; /** * Name */ @@ -3743,7 +3927,7 @@ export type LimitOffsetPageOrganizationReadWritable = { /** * ActorType */ - type: 'user' | 'system'; + type: 'user' | 'system' | 'app'; /** * Name */ @@ -3763,7 +3947,7 @@ export type LimitOffsetPageOrganizationReadWritable = { /** * ActorType */ - type: 'user' | 'system'; + type: 'user' | 'system' | 'app'; /** * Name */ @@ -3783,7 +3967,7 @@ export type LimitOffsetPageOrganizationReadWritable = { /** * ActorType */ - type: 'user' | 'system'; + type: 'user' | 'system' | 'app'; /** * Name */ @@ -3916,7 +4100,7 @@ export type OrganizationReadWritable = { /** * ActorType */ - type: 'user' | 'system'; + type: 'user' | 'system' | 'app'; /** * Name */ @@ -3939,7 +4123,7 @@ export type OrganizationReadWritable = { /** * ActorType */ - type: 'user' | 'system'; + type: 'user' | 'system' | 'app'; /** * Name */ @@ -3959,7 +4143,7 @@ export type OrganizationReadWritable = { /** * ActorType */ - type: 'user' | 'system'; + type: 'user' | 'system' | 'app'; /** * Name */ @@ -3979,7 +4163,7 @@ export type OrganizationReadWritable = { /** * ActorType */ - type: 'user' | 'system'; + type: 'user' | 'system' | 'app'; /** * Name */ @@ -4064,7 +4248,7 @@ export type ListDatasourceExpensesOpsV1ExpensesGetResponses = { /** * ActorType */ - type: 'user' | 'system'; + type: 'user' | 'system' | 'app'; /** * Name */ @@ -4087,7 +4271,7 @@ export type ListDatasourceExpensesOpsV1ExpensesGetResponses = { /** * ActorType */ - type: 'user' | 'system'; + type: 'user' | 'system' | 'app'; /** * Name */ @@ -4107,7 +4291,7 @@ export type ListDatasourceExpensesOpsV1ExpensesGetResponses = { /** * ActorType */ - type: 'user' | 'system'; + type: 'user' | 'system' | 'app'; /** * Name */ @@ -4260,6 +4444,24 @@ export type GetEntitlementsOpsV1EntitlementsGetResponses = { * An external identifier for the account */ external_id: string; + cloud_provider?: { + /** + * Name + */ + name: string; + /** + * Icon + */ + icon: string; + /** + * Datasource Id Format + */ + datasource_id_format: string; + /** + * Id + */ + id: string; + } | null; }; /** * EntitlementStatus @@ -4285,7 +4487,7 @@ export type GetEntitlementsOpsV1EntitlementsGetResponses = { /** * ActorType */ - type: 'user' | 'system'; + type: 'user' | 'system' | 'app'; /** * Name */ @@ -4308,7 +4510,7 @@ export type GetEntitlementsOpsV1EntitlementsGetResponses = { /** * ActorType */ - type: 'user' | 'system'; + type: 'user' | 'system' | 'app'; /** * Name */ @@ -4328,7 +4530,7 @@ export type GetEntitlementsOpsV1EntitlementsGetResponses = { /** * ActorType */ - type: 'user' | 'system'; + type: 'user' | 'system' | 'app'; /** * Name */ @@ -4371,7 +4573,7 @@ export type GetEntitlementsOpsV1EntitlementsGetResponses = { /** * ActorType */ - type: 'user' | 'system'; + type: 'user' | 'system' | 'app'; /** * Name */ @@ -4528,6 +4730,24 @@ export type CreateEntitlementOpsV1EntitlementsPostResponses = { * An external identifier for the account */ external_id: string; + cloud_provider?: { + /** + * Name + */ + name: string; + /** + * Icon + */ + icon: string; + /** + * Datasource Id Format + */ + datasource_id_format: string; + /** + * Id + */ + id: string; + } | null; }; /** * EntitlementStatus @@ -4553,7 +4773,7 @@ export type CreateEntitlementOpsV1EntitlementsPostResponses = { /** * ActorType */ - type: 'user' | 'system'; + type: 'user' | 'system' | 'app'; /** * Name */ @@ -4576,7 +4796,7 @@ export type CreateEntitlementOpsV1EntitlementsPostResponses = { /** * ActorType */ - type: 'user' | 'system'; + type: 'user' | 'system' | 'app'; /** * Name */ @@ -4596,7 +4816,7 @@ export type CreateEntitlementOpsV1EntitlementsPostResponses = { /** * ActorType */ - type: 'user' | 'system'; + type: 'user' | 'system' | 'app'; /** * Name */ @@ -4639,7 +4859,7 @@ export type CreateEntitlementOpsV1EntitlementsPostResponses = { /** * ActorType */ - type: 'user' | 'system'; + type: 'user' | 'system' | 'app'; /** * Name */ @@ -4822,6 +5042,24 @@ export type GetEntitlementByIdOpsV1EntitlementsIdGetResponses = { * An external identifier for the account */ external_id: string; + cloud_provider?: { + /** + * Name + */ + name: string; + /** + * Icon + */ + icon: string; + /** + * Datasource Id Format + */ + datasource_id_format: string; + /** + * Id + */ + id: string; + } | null; }; /** * EntitlementStatus @@ -4847,7 +5085,7 @@ export type GetEntitlementByIdOpsV1EntitlementsIdGetResponses = { /** * ActorType */ - type: 'user' | 'system'; + type: 'user' | 'system' | 'app'; /** * Name */ @@ -4870,7 +5108,7 @@ export type GetEntitlementByIdOpsV1EntitlementsIdGetResponses = { /** * ActorType */ - type: 'user' | 'system'; + type: 'user' | 'system' | 'app'; /** * Name */ @@ -4890,7 +5128,7 @@ export type GetEntitlementByIdOpsV1EntitlementsIdGetResponses = { /** * ActorType */ - type: 'user' | 'system'; + type: 'user' | 'system' | 'app'; /** * Name */ @@ -4933,7 +5171,7 @@ export type GetEntitlementByIdOpsV1EntitlementsIdGetResponses = { /** * ActorType */ - type: 'user' | 'system'; + type: 'user' | 'system' | 'app'; /** * Name */ @@ -5056,6 +5294,24 @@ export type TerminateEntitlementOpsV1EntitlementsIdTerminatePostResponses = { * An external identifier for the account */ external_id: string; + cloud_provider?: { + /** + * Name + */ + name: string; + /** + * Icon + */ + icon: string; + /** + * Datasource Id Format + */ + datasource_id_format: string; + /** + * Id + */ + id: string; + } | null; }; /** * EntitlementStatus @@ -5081,7 +5337,7 @@ export type TerminateEntitlementOpsV1EntitlementsIdTerminatePostResponses = { /** * ActorType */ - type: 'user' | 'system'; + type: 'user' | 'system' | 'app'; /** * Name */ @@ -5104,7 +5360,7 @@ export type TerminateEntitlementOpsV1EntitlementsIdTerminatePostResponses = { /** * ActorType */ - type: 'user' | 'system'; + type: 'user' | 'system' | 'app'; /** * Name */ @@ -5124,7 +5380,7 @@ export type TerminateEntitlementOpsV1EntitlementsIdTerminatePostResponses = { /** * ActorType */ - type: 'user' | 'system'; + type: 'user' | 'system' | 'app'; /** * Name */ @@ -5167,7 +5423,7 @@ export type TerminateEntitlementOpsV1EntitlementsIdTerminatePostResponses = { /** * ActorType */ - type: 'user' | 'system'; + type: 'user' | 'system' | 'app'; /** * Name */ @@ -5267,7 +5523,7 @@ export type GetOrganizationsOpsV1OrganizationsGetResponses = { /** * ActorType */ - type: 'user' | 'system'; + type: 'user' | 'system' | 'app'; /** * Name */ @@ -5290,7 +5546,7 @@ export type GetOrganizationsOpsV1OrganizationsGetResponses = { /** * ActorType */ - type: 'user' | 'system'; + type: 'user' | 'system' | 'app'; /** * Name */ @@ -5310,7 +5566,7 @@ export type GetOrganizationsOpsV1OrganizationsGetResponses = { /** * ActorType */ - type: 'user' | 'system'; + type: 'user' | 'system' | 'app'; /** * Name */ @@ -5330,7 +5586,7 @@ export type GetOrganizationsOpsV1OrganizationsGetResponses = { /** * ActorType */ - type: 'user' | 'system'; + type: 'user' | 'system' | 'app'; /** * Name */ @@ -5586,7 +5842,7 @@ export type GetOrganizationByIdOpsV1OrganizationsOrganizationIdGetResponses = { /** * ActorType */ - type: 'user' | 'system'; + type: 'user' | 'system' | 'app'; /** * Name */ @@ -5609,7 +5865,7 @@ export type GetOrganizationByIdOpsV1OrganizationsOrganizationIdGetResponses = { /** * ActorType */ - type: 'user' | 'system'; + type: 'user' | 'system' | 'app'; /** * Name */ @@ -5629,7 +5885,7 @@ export type GetOrganizationByIdOpsV1OrganizationsOrganizationIdGetResponses = { /** * ActorType */ - type: 'user' | 'system'; + type: 'user' | 'system' | 'app'; /** * Name */ @@ -5649,7 +5905,7 @@ export type GetOrganizationByIdOpsV1OrganizationsOrganizationIdGetResponses = { /** * ActorType */ - type: 'user' | 'system'; + type: 'user' | 'system' | 'app'; /** * Name */ @@ -5844,7 +6100,7 @@ export type UpdateOrganizationOpsV1OrganizationsOrganizationIdPutResponses = { /** * ActorType */ - type: 'user' | 'system'; + type: 'user' | 'system' | 'app'; /** * Name */ @@ -5867,7 +6123,7 @@ export type UpdateOrganizationOpsV1OrganizationsOrganizationIdPutResponses = { /** * ActorType */ - type: 'user' | 'system'; + type: 'user' | 'system' | 'app'; /** * Name */ @@ -5887,7 +6143,7 @@ export type UpdateOrganizationOpsV1OrganizationsOrganizationIdPutResponses = { /** * ActorType */ - type: 'user' | 'system'; + type: 'user' | 'system' | 'app'; /** * Name */ @@ -5907,7 +6163,7 @@ export type UpdateOrganizationOpsV1OrganizationsOrganizationIdPutResponses = { /** * ActorType */ - type: 'user' | 'system'; + type: 'user' | 'system' | 'app'; /** * Name */ @@ -6981,7 +7237,7 @@ export type GetAccountsOpsV1AccountsGetResponses = { /** * ActorType */ - type: 'user' | 'system'; + type: 'user' | 'system' | 'app'; /** * Name */ @@ -7004,7 +7260,7 @@ export type GetAccountsOpsV1AccountsGetResponses = { /** * ActorType */ - type: 'user' | 'system'; + type: 'user' | 'system' | 'app'; /** * Name */ @@ -7024,7 +7280,7 @@ export type GetAccountsOpsV1AccountsGetResponses = { /** * ActorType */ - type: 'user' | 'system'; + type: 'user' | 'system' | 'app'; /** * Name */ @@ -7084,6 +7340,24 @@ export type GetAccountsOpsV1AccountsGetResponses = { * AccountType */ type: 'admin' | 'operations' | 'affiliate'; + cloud_provider?: { + /** + * Name + */ + name: string; + /** + * Icon + */ + icon: string; + /** + * Datasource Id Format + */ + datasource_id_format: string; + /** + * Id + */ + id: string; + } | null; /** * AccountStats */ @@ -7230,7 +7504,7 @@ export type CreateAccountOpsV1AccountsPostResponses = { /** * ActorType */ - type: 'user' | 'system'; + type: 'user' | 'system' | 'app'; /** * Name */ @@ -7253,7 +7527,7 @@ export type CreateAccountOpsV1AccountsPostResponses = { /** * ActorType */ - type: 'user' | 'system'; + type: 'user' | 'system' | 'app'; /** * Name */ @@ -7273,7 +7547,7 @@ export type CreateAccountOpsV1AccountsPostResponses = { /** * ActorType */ - type: 'user' | 'system'; + type: 'user' | 'system' | 'app'; /** * Name */ @@ -7333,6 +7607,24 @@ export type CreateAccountOpsV1AccountsPostResponses = { * AccountType */ type: 'admin' | 'operations' | 'affiliate'; + cloud_provider?: { + /** + * Name + */ + name: string; + /** + * Icon + */ + icon: string; + /** + * Datasource Id Format + */ + datasource_id_format: string; + /** + * Id + */ + id: string; + } | null; /** * AccountStats */ @@ -7450,7 +7742,7 @@ export type GetAccountByIdOpsV1AccountsIdGetResponses = { /** * ActorType */ - type: 'user' | 'system'; + type: 'user' | 'system' | 'app'; /** * Name */ @@ -7473,7 +7765,7 @@ export type GetAccountByIdOpsV1AccountsIdGetResponses = { /** * ActorType */ - type: 'user' | 'system'; + type: 'user' | 'system' | 'app'; /** * Name */ @@ -7493,7 +7785,7 @@ export type GetAccountByIdOpsV1AccountsIdGetResponses = { /** * ActorType */ - type: 'user' | 'system'; + type: 'user' | 'system' | 'app'; /** * Name */ @@ -7553,6 +7845,24 @@ export type GetAccountByIdOpsV1AccountsIdGetResponses = { * AccountType */ type: 'admin' | 'operations' | 'affiliate'; + cloud_provider?: { + /** + * Name + */ + name: string; + /** + * Icon + */ + icon: string; + /** + * Datasource Id Format + */ + datasource_id_format: string; + /** + * Id + */ + id: string; + } | null; /** * AccountStats */ @@ -7683,7 +7993,7 @@ export type UpdateAccountOpsV1AccountsIdPutResponses = { /** * ActorType */ - type: 'user' | 'system'; + type: 'user' | 'system' | 'app'; /** * Name */ @@ -7706,7 +8016,7 @@ export type UpdateAccountOpsV1AccountsIdPutResponses = { /** * ActorType */ - type: 'user' | 'system'; + type: 'user' | 'system' | 'app'; /** * Name */ @@ -7726,7 +8036,7 @@ export type UpdateAccountOpsV1AccountsIdPutResponses = { /** * ActorType */ - type: 'user' | 'system'; + type: 'user' | 'system' | 'app'; /** * Name */ @@ -7786,6 +8096,24 @@ export type UpdateAccountOpsV1AccountsIdPutResponses = { * AccountType */ type: 'admin' | 'operations' | 'affiliate'; + cloud_provider?: { + /** + * Name + */ + name: string; + /** + * Icon + */ + icon: string; + /** + * Datasource Id Format + */ + datasource_id_format: string; + /** + * Id + */ + id: string; + } | null; /** * AccountStats */ @@ -7907,7 +8235,7 @@ export type ListAccountUsersOpsV1AccountsIdUsersGetResponses = { /** * ActorType */ - type: 'user' | 'system'; + type: 'user' | 'system' | 'app'; /** * Name */ @@ -7930,7 +8258,7 @@ export type ListAccountUsersOpsV1AccountsIdUsersGetResponses = { /** * ActorType */ - type: 'user' | 'system'; + type: 'user' | 'system' | 'app'; /** * Name */ @@ -7950,7 +8278,7 @@ export type ListAccountUsersOpsV1AccountsIdUsersGetResponses = { /** * ActorType */ - type: 'user' | 'system'; + type: 'user' | 'system' | 'app'; /** * Name */ @@ -8006,6 +8334,24 @@ export type ListAccountUsersOpsV1AccountsIdUsersGetResponses = { * An external identifier for the account */ external_id: string; + cloud_provider?: { + /** + * Name + */ + name: string; + /** + * Icon + */ + icon: string; + /** + * Datasource Id Format + */ + datasource_id_format: string; + /** + * Id + */ + id: string; + } | null; }; } | null; }>; @@ -8063,6 +8409,24 @@ export type MeOpsV1MeGetResponses = { * An external identifier for the account */ external_id: string; + cloud_provider?: { + /** + * Name + */ + name: string; + /** + * Icon + */ + icon: string; + /** + * Datasource Id Format + */ + datasource_id_format: string; + /** + * Id + */ + id: string; + } | null; }; system: { /** diff --git a/frontend/src/features/entitlements/create-entitlement-wizard/CreateEntitlement.Schema.spec.ts b/frontend/src/features/entitlements/create-entitlement-wizard/CreateEntitlement.Schema.spec.ts index af907e07..491486ef 100644 --- a/frontend/src/features/entitlements/create-entitlement-wizard/CreateEntitlement.Schema.spec.ts +++ b/frontend/src/features/entitlements/create-entitlement-wizard/CreateEntitlement.Schema.spec.ts @@ -1,4 +1,4 @@ -import { AddWizardFormSchema } from "./CreateEntitlement.Schema"; +import { AddWizardFormSchema, toDatasourceIdRegExp } from "./CreateEntitlement.Schema"; describe("AddWizardFormSchema", () => { const validInput = { @@ -52,4 +52,65 @@ describe("AddWizardFormSchema", () => { }); expect(result.success).toBe(false); }); + + describe("dataSource.id format from the affiliate cloud provider", () => { + const withFormat = (datasource_id_format: string, id: string) => ({ + ...validInput, + affiliate: { ...validInput.affiliate, cloud_provider: { datasource_id_format } }, + dataSource: { ...validInput.dataSource, id }, + }); + + it("accepts an id matching the cloud provider format", () => { + const result = AddWizardFormSchema.safeParse(withFormat("[0-9]{12}", "123456789012")); + expect(result.success).toBe(true); + }); + + it("rejects an id not matching the format and reports it on dataSource.id", () => { + const result = AddWizardFormSchema.safeParse(withFormat("[0-9]{12}", "not-an-aws-id")); + expect(result.success).toBe(false); + expect(result.error?.issues).toEqual([ + expect.objectContaining({ code: "custom", path: ["dataSource", "id"] }), + ]); + }); + + it("requires the whole id to match, not just a substring", () => { + const result = AddWizardFormSchema.safeParse(withFormat("[0-9]{12}", "x123456789012x")); + expect(result.success).toBe(false); + }); + + it("skips the format check when the affiliate has no cloud provider", () => { + const result = AddWizardFormSchema.safeParse({ + ...validInput, + affiliate: { ...validInput.affiliate, cloud_provider: null }, + }); + expect(result.success).toBe(true); + }); + + it("skips the format check when the pattern is malformed", () => { + const result = AddWizardFormSchema.safeParse(withFormat("([unclosed", "anything")); + expect(result.success).toBe(true); + }); + + it("keeps cloud_provider in the parsed output", () => { + const result = AddWizardFormSchema.parse(withFormat(".+", "ds-1")); + expect(result.affiliate.cloud_provider).toEqual({ datasource_id_format: ".+" }); + }); + }); + + describe("toDatasourceIdRegExp", () => { + it("returns null for an empty pattern", () => { + expect(toDatasourceIdRegExp(undefined)).toBeNull(); + expect(toDatasourceIdRegExp("")).toBeNull(); + }); + + it("anchors patterns that are already anchored without breaking them", () => { + expect(toDatasourceIdRegExp("^[a-z]+$")?.test("abc")).toBe(true); + }); + + it("full-matches alternations", () => { + const regex = toDatasourceIdRegExp("abc|def"); + expect(regex?.test("abc")).toBe(true); + expect(regex?.test("abcdef")).toBe(false); + }); + }); }); diff --git a/frontend/src/features/entitlements/create-entitlement-wizard/CreateEntitlement.Schema.tsx b/frontend/src/features/entitlements/create-entitlement-wizard/CreateEntitlement.Schema.tsx index e58113f0..f10140b6 100644 --- a/frontend/src/features/entitlements/create-entitlement-wizard/CreateEntitlement.Schema.tsx +++ b/frontend/src/features/entitlements/create-entitlement-wizard/CreateEntitlement.Schema.tsx @@ -4,23 +4,53 @@ import { i18n } from "~i18n/translations"; const tValidation = i18n.getFixedT(null, null, "entitlements:addWizard:validations"); -export const AddWizardFormSchema = z.object({ - id: z.string().nullish(), - name: z.string({ error: () => tValidation("name:required") }), - affiliate: z.object( - { - id: z.string({ error: () => tValidation("affiliate:required") }), - name: z.string(), - integration: z.string().nullish(), - }, - { error: () => tValidation("affiliate:required") }, - ), - dataSource: z.object({ - id: z.string({ error: () => tValidation("dataSource:id:required") }), - affiliate_external_id: z.string({ - error: () => tValidation("dataSource:affiliate_external_id:required"), +/** + * Builds a full-match RegExp from an API-provided pattern. + * Returns `null` for a missing or malformed pattern so that validation is skipped + * instead of crashing the form; the backend remains the source of truth. + */ +export const toDatasourceIdRegExp = (pattern: string | null | undefined): RegExp | null => { + if (!pattern) return null; + try { + return new RegExp(`^(?:${pattern})$`); + } catch { + return null; + } +}; + +export const AddWizardFormSchema = z + .object({ + id: z.string().nullish(), + name: z.string({ error: () => tValidation("name:required") }), + affiliate: z.object( + { + id: z.string({ error: () => tValidation("affiliate:required") }), + name: z.string(), + integration: z.string().nullish(), + // Declared so it survives parsing and is available to the refinement below. + cloud_provider: z.object({ datasource_id_format: z.string() }).nullish(), + }, + { error: () => tValidation("affiliate:required") }, + ), + dataSource: z.object({ + id: z.string({ error: () => tValidation("dataSource:id:required") }), + affiliate_external_id: z.string({ + error: () => tValidation("dataSource:affiliate_external_id:required"), + }), }), - }), -}); + }) + .superRefine(({ affiliate, dataSource }, ctx) => { + const regex = toDatasourceIdRegExp(affiliate.cloud_provider?.datasource_id_format); + + if (regex && !regex.test(dataSource.id)) { + ctx.addIssue({ + code: "custom", + path: ["dataSource", "id"], + message: tValidation("dataSource:id:invalid", { + format: affiliate.cloud_provider?.datasource_id_format, + }), + }); + } + }); export type AddWizardForm = z.output; diff --git a/frontend/src/features/entitlements/create-entitlement-wizard/CreateEntitlementWizard.spec.mocks.tsx b/frontend/src/features/entitlements/create-entitlement-wizard/CreateEntitlementWizard.spec.mocks.tsx index 9537691f..9a8dd229 100644 --- a/frontend/src/features/entitlements/create-entitlement-wizard/CreateEntitlementWizard.spec.mocks.tsx +++ b/frontend/src/features/entitlements/create-entitlement-wizard/CreateEntitlementWizard.spec.mocks.tsx @@ -4,10 +4,12 @@ import type { useEntitlementsApi } from "~entitlements/api/useEntitlementsApi"; import { mockErrorDetailsModule } from "~test-utils/mocks/errorDetails"; import { mockUserRoleModule } from "~test-utils/mocks/userRole"; +import type { AddWizardForm } from "./CreateEntitlement.Schema"; import type { ReviewStep } from "./steps/ReviewStep"; import type { SummaryStep } from "./steps/SummaryStep"; import type { useSteps } from "./useSteps"; +export { mockGetErrorMessage } from "~test-utils/mocks/errorDetails"; export { mockUseUserRole } from "~test-utils/mocks/userRole"; type EntitlementsApi = ReturnType; @@ -29,6 +31,14 @@ export const mockUseSteps = jest.fn(); // per render. Tests mutate `.activeStepIndex` before rendering. export const mockStepContext = { activeStepIndex: 0 }; +// Schema-valid values written into the real form by the "fill form" button, +// so `handleSubmit` passes zod validation and reaches the mutation. +export const mockValidForm: AddWizardForm = { + name: "My entitlement", + affiliate: { id: "acc-1", name: "Affiliate 1", integration: "aws", cloud_provider: null }, + dataSource: { id: "ds-1", affiliate_external_id: "ext-1" }, +}; + jest.mock("~entitlements/api/useEntitlementsApi", () => ({ useEntitlementsApi: () => ({ save: mockSave }), })); @@ -37,22 +47,58 @@ jest.mock("~shared/hooks/useErrorDetails", () => mockErrorDetailsModule); jest.mock("~shared/hooks/useUserRole", () => mockUserRoleModule); jest.mock("@swo/design-system/modal", () => ({ - Modal: ({ children }: ModalProps) =>
{children}
, + Modal: ({ children, onClose }: ModalProps) => ( +
+ + {children} +
+ ), })); jest.mock("@swo/design-system/wizard", () => { + const { useFormContext } = + jest.requireActual("react-hook-form"); + + // Stands in for the (mocked) steps editing the real form owned by the wizard. + const FormControls = () => { + const { setValue } = useFormContext(); + const fillForm = () => { + setValue("name", mockValidForm.name); + setValue("affiliate", mockValidForm.affiliate); + setValue("dataSource", mockValidForm.dataSource); + }; + + return ( + <> + + + + + ); + }; + const Wizard = (props: MockWizardProps) => { mockWizard(props); return ( -
+
+ + {props.children} -
+ ); }; Wizard.Header = ({ children }: WizardHeaderProps) => ( diff --git a/frontend/src/features/entitlements/create-entitlement-wizard/CreateEntitlementWizard.spec.tsx b/frontend/src/features/entitlements/create-entitlement-wizard/CreateEntitlementWizard.spec.tsx index 74028b05..a1ad4ee8 100644 --- a/frontend/src/features/entitlements/create-entitlement-wizard/CreateEntitlementWizard.spec.tsx +++ b/frontend/src/features/entitlements/create-entitlement-wizard/CreateEntitlementWizard.spec.tsx @@ -1,21 +1,55 @@ -import { screen } from "@testing-library/react"; +import { screen, waitFor } from "@testing-library/react"; import userEvent from "@testing-library/user-event"; +import { AxiosError, AxiosResponse } from "axios"; +import type { EntitlementRead } from "~api/ffc-api-model"; +import type { useUserRole } from "~shared/hooks/useUserRole"; import { renderWithQueryClient } from "~test-utils"; +import { makeEntitlement } from "~test-utils/factories"; import { + mockGetErrorMessage, + mockSave, mockStepContext, mockUseSteps, mockUseUserRole, + mockValidForm, mockWizard, } from "./CreateEntitlementWizard.spec.mocks"; import { CreateEntitlementWizard } from "./CreateEntitlementWizard"; +const REVIEW_STEP_INDEX = 2; +const SAVE_ERROR = "Saving failed"; + +type User = NonNullable["user"]>; + function lastWizardProps() { return mockWizard.mock.lastCall![0]; } +function renderWizard(onClose = jest.fn()) { + const user = userEvent.setup(); + renderWithQueryClient(); + return { user, onClose }; +} + +async function fillAndSubmit(user: ReturnType) { + await user.click(screen.getByRole("button", { name: "fill form" })); + await user.click(screen.getByRole("button", { name: "submit wizard" })); +} + +function primeSaveSuccess() { + mockSave.mockResolvedValue({ + data: makeEntitlement({ id: "ent-new" }), + } as unknown as AxiosResponse); +} + +function primeSaveFailure() { + mockSave.mockRejectedValue(new AxiosError("boom")); + mockGetErrorMessage.mockReturnValue(SAVE_ERROR); +} + describe("CreateEntitlementWizard", () => { beforeEach(() => { mockUseUserRole.mockReturnValue({ user: null, role: "operations" }); @@ -24,19 +58,19 @@ describe("CreateEntitlementWizard", () => { }); it("passes the initial saving state to useSteps", () => { - renderWithQueryClient(); + renderWizard(); expect(mockUseSteps).toHaveBeenLastCalledWith(false); }); it("passes the configured steps to the Wizard", () => { - renderWithQueryClient(); + renderWizard(); expect(lastWizardProps().stepsProps).toHaveLength(4); }); it("opens on the first step", () => { - renderWithQueryClient(); + renderWizard(); expect(lastWizardProps().activeStepIndex).toBe(0); }); @@ -49,28 +83,164 @@ describe("CreateEntitlementWizard", () => { ] as const)("renders the %s step when it is active", (_name, index, testId) => { mockStepContext.activeStepIndex = index; - renderWithQueryClient(); + renderWizard(); expect(screen.getByTestId(testId)).toBeInTheDocument(); }); - it("closing the wizard reports an unsuccessful result when nothing was created", () => { - const user = userEvent.setup(); - const onClose = jest.fn(); - renderWithQueryClient(); + it("closing the wizard reports an unsuccessful result when nothing was created", async () => { + const { user, onClose } = renderWizard(); - return user.click(screen.getByRole("button", { name: "close wizard" })).then(() => { - expect(onClose).toHaveBeenCalledWith({ success: false }); - }); + await user.click(screen.getByRole("button", { name: "close wizard" })); + + expect(onClose).toHaveBeenCalledWith({ success: false }); }); it("saving the wizard reports an unsuccessful result when nothing was created", async () => { - const user = userEvent.setup(); - const onClose = jest.fn(); - renderWithQueryClient(); + const { user, onClose } = renderWizard(); await user.click(screen.getByRole("button", { name: "save wizard" })); expect(onClose).toHaveBeenCalledWith({ success: false }); }); + + it("dismissing the modal closes without a result", async () => { + const { user, onClose } = renderWizard(); + + await user.click(screen.getByRole("button", { name: "dismiss modal" })); + + expect(onClose).toHaveBeenCalledWith(); + }); + + describe("submitting", () => { + it("does not save when the form is invalid", async () => { + const { user } = renderWizard(); + + await user.click(screen.getByRole("button", { name: "submit wizard" })); + + expect(mockSave).not.toHaveBeenCalled(); + }); + + it("saves the entitlement on behalf of the selected affiliate for operations users", async () => { + primeSaveSuccess(); + const { user } = renderWizard(); + + await fillAndSubmit(user); + + await waitFor(() => { + expect(mockSave).toHaveBeenCalledWith( + { + name: mockValidForm.name, + affiliate_external_id: mockValidForm.dataSource.affiliate_external_id, + datasource_id: mockValidForm.dataSource.id, + owner: { id: mockValidForm.affiliate.id }, + }, + expect.anything(), + ); + }); + }); + + it("saves the entitlement without an owner for affiliate users", async () => { + mockUseUserRole.mockReturnValue({ + user: { account: { id: "acc-1", name: "Affiliate 1" } } as User, + role: "affiliate", + }); + primeSaveSuccess(); + const { user } = renderWizard(); + + await fillAndSubmit(user); + + await waitFor(() => { + expect(mockSave).toHaveBeenCalledWith( + { + name: mockValidForm.name, + affiliate_external_id: mockValidForm.dataSource.affiliate_external_id, + datasource_id: mockValidForm.dataSource.id, + }, + expect.anything(), + ); + }); + }); + + it("advances to the next step after a successful save", async () => { + primeSaveSuccess(); + const { user } = renderWizard(); + + await fillAndSubmit(user); + + await waitFor(() => { + expect(lastWizardProps().activeStepIndex).toBe(1); + }); + }); + + it.each([ + ["closing", "close wizard"], + ["finishing", "save wizard"], + ])("%s the wizard reports a successful result after a save", async (_name, button) => { + primeSaveSuccess(); + const { user, onClose } = renderWizard(); + await fillAndSubmit(user); + await waitFor(() => { + expect(lastWizardProps().activeStepIndex).toBe(1); + }); + + await user.click(screen.getByRole("button", { name: button })); + + expect(onClose).toHaveBeenCalledWith({ success: true }); + }); + }); + + describe("save errors", () => { + beforeEach(() => { + mockStepContext.activeStepIndex = REVIEW_STEP_INDEX; + primeSaveFailure(); + }); + + async function renderWithSaveError() { + const { user, onClose } = renderWizard(); + await fillAndSubmit(user); + await waitFor(() => { + expect(screen.getByTestId("step-review")).toHaveAttribute("data-error", SAVE_ERROR); + }); + return { user, onClose }; + } + + it("shows the error message and stays on the current step", async () => { + await renderWithSaveError(); + + expect(lastWizardProps().activeStepIndex).toBe(0); + }); + + it("reports an unsuccessful result when closed after a failed save", async () => { + const { user, onClose } = await renderWithSaveError(); + + await user.click(screen.getByRole("button", { name: "close wizard" })); + + expect(onClose).toHaveBeenCalledWith({ success: false }); + }); + + it("clears the error once the user edits the form", async () => { + const { user } = await renderWithSaveError(); + + await user.click(screen.getByRole("button", { name: "change name" })); + + expect(screen.getByTestId("step-review")).toHaveAttribute("data-error", ""); + }); + + it("keeps the error when form values change without making it dirty", async () => { + const { user } = await renderWithSaveError(); + + await user.click(screen.getByRole("button", { name: "change name without dirtying" })); + + expect(screen.getByTestId("step-review")).toHaveAttribute("data-error", SAVE_ERROR); + }); + + it("clears the error when the wizard is closed", async () => { + const { user } = await renderWithSaveError(); + + await user.click(screen.getByRole("button", { name: "close wizard" })); + + expect(screen.getByTestId("step-review")).toHaveAttribute("data-error", ""); + }); + }); }); diff --git a/frontend/src/features/entitlements/create-entitlement-wizard/CreateEntitlementWizard.tsx b/frontend/src/features/entitlements/create-entitlement-wizard/CreateEntitlementWizard.tsx index fcce1ea0..f30e8e01 100644 --- a/frontend/src/features/entitlements/create-entitlement-wizard/CreateEntitlementWizard.tsx +++ b/frontend/src/features/entitlements/create-entitlement-wizard/CreateEntitlementWizard.tsx @@ -1,4 +1,4 @@ -import { useCallback, useState } from "react"; +import { useCallback, useEffect, useState } from "react"; import { zodResolver } from "@hookform/resolvers/zod/dist/zod.js"; import { useMutation } from "@tanstack/react-query"; @@ -45,6 +45,7 @@ export function CreateEntitlementWizard({ isOpen, onClose }: Readonly) { >({ mutationFn: save, onError: (err) => { + setEntitlementCreated(false); setError(getErrorMessage(err)); }, onSuccess: (res) => { @@ -67,7 +68,23 @@ export function CreateEntitlementWizard({ isOpen, onClose }: Readonly) { : null, }, }); - const { handleSubmit, reset, setValue } = methods; + const { handleSubmit, reset, setValue, subscribe } = methods; + + useEffect(() => { + const callback = subscribe({ + formState: { + values: true, + isDirty: true, + }, + callback: ({ isDirty }) => { + if (!entitlementCreated && isDirty && error) { + setError(""); + } + }, + }); + + return () => callback(); + }, [subscribe, entitlementCreated, error]); const closeWizard = useCallback(() => { reset(); diff --git a/frontend/src/features/entitlements/create-entitlement-wizard/steps/AffiliateStep.spec.tsx b/frontend/src/features/entitlements/create-entitlement-wizard/steps/AffiliateStep.spec.tsx index 6e62ffda..486ae260 100644 --- a/frontend/src/features/entitlements/create-entitlement-wizard/steps/AffiliateStep.spec.tsx +++ b/frontend/src/features/entitlements/create-entitlement-wizard/steps/AffiliateStep.spec.tsx @@ -37,8 +37,8 @@ jest.mock("~shared/components/SelectAffiliateList", () => ({ jest.mock("~shared/components/wizard/WizardStep", () => mockSharedWizardStep); -function renderStep(triggerResolvedValue = true) { - return renderWizardStep(, { triggerResolvedValue }); +function renderStep(triggerResolvedValue = true, dirtyFields: string[] = []) { + return renderWizardStep(, { triggerResolvedValue, dirtyFields }); } function getRegisteredOnNextCallback() { @@ -57,6 +57,24 @@ describe("AffiliateStep", () => { expect(setValue).toHaveBeenCalledWith("affiliate", selectedAffiliate, { shouldValidate: true }); }); + it("re-validates an entered datasource id when the affiliate changes", async () => { + const user = userEvent.setup(); + const { trigger } = renderStep(true, ["dataSource.id"]); + + await user.click(screen.getByRole("button", { name: "select affiliate" })); + + expect(trigger).toHaveBeenCalledWith("dataSource.id"); + }); + + it("does not validate the datasource id when it has not been entered yet", async () => { + const user = userEvent.setup(); + const { trigger } = renderStep(); + + await user.click(screen.getByRole("button", { name: "select affiliate" })); + + expect(trigger).not.toHaveBeenCalledWith("dataSource.id"); + }); + it("validates the affiliate selection before advancing", async () => { const { trigger } = renderStep(); diff --git a/frontend/src/features/entitlements/create-entitlement-wizard/steps/AffiliateStep.tsx b/frontend/src/features/entitlements/create-entitlement-wizard/steps/AffiliateStep.tsx index e63782ff..0024ff61 100644 --- a/frontend/src/features/entitlements/create-entitlement-wizard/steps/AffiliateStep.tsx +++ b/frontend/src/features/entitlements/create-entitlement-wizard/steps/AffiliateStep.tsx @@ -14,7 +14,7 @@ import { AddWizardForm } from "../CreateEntitlement.Schema"; export function AffiliateStep() { const tStep = useFixedT("entitlements:addWizard:steps:affiliate"); - const { trigger, setValue } = useFormContext(); + const { trigger, setValue, getFieldState } = useFormContext(); const { registerOnNextCallback } = useStepActions(); const [affiliate] = useWatch, ["affiliate"]>({ name: ["affiliate"] }); const formState = useFormState({ name: "affiliate" }); @@ -39,8 +39,14 @@ export function AffiliateStep() { setValue("affiliate", entity as Required, { shouldValidate: true, }); + + // The datasource id format depends on the affiliate's cloud provider, + // so re-check an already entered id against the new format. + if (getFieldState("dataSource.id").isDirty) { + void trigger("dataSource.id"); + } }, - [setValue], + [setValue, getFieldState, trigger], ); return ( diff --git a/frontend/src/features/entitlements/create-entitlement-wizard/steps/wizardStepTestUtils.tsx b/frontend/src/features/entitlements/create-entitlement-wizard/steps/wizardStepTestUtils.tsx index dca44aed..4319c67f 100644 --- a/frontend/src/features/entitlements/create-entitlement-wizard/steps/wizardStepTestUtils.tsx +++ b/frontend/src/features/entitlements/create-entitlement-wizard/steps/wizardStepTestUtils.tsx @@ -8,6 +8,8 @@ import type { AddWizardForm } from "../CreateEntitlement.Schema"; export type WizardStepRenderOptions = { defaultValues?: Partial; triggerResolvedValue?: boolean; + /** Field names reported as dirty by `getFieldState`. */ + dirtyFields?: string[]; }; export type WizardStepRenderResult = { @@ -17,15 +19,20 @@ export type WizardStepRenderResult = { export function renderWizardStep( element: ReactNode, - { defaultValues, triggerResolvedValue = true }: WizardStepRenderOptions = {}, + { defaultValues, triggerResolvedValue = true, dirtyFields = [] }: WizardStepRenderOptions = {}, ): WizardStepRenderResult { const trigger = jest.fn().mockResolvedValue(triggerResolvedValue); const setValue = jest.fn(); function Wrapper() { const methods = useForm({ defaultValues: defaultValues as AddWizardForm }); + const { getFieldState } = methods; methods.trigger = trigger as unknown as typeof methods.trigger; methods.setValue = setValue as unknown as typeof methods.setValue; + methods.getFieldState = ((name, formState) => ({ + ...getFieldState(name, formState), + isDirty: dirtyFields.includes(name), + })) as typeof methods.getFieldState; return {element}; } diff --git a/frontend/src/i18n/en.json b/frontend/src/i18n/en.json index 62af9fde..b1052197 100644 --- a/frontend/src/i18n/en.json +++ b/frontend/src/i18n/en.json @@ -106,7 +106,10 @@ "affiliate": { "required": "Affiliate is required" }, "name": { "required": "Entitlement name is required" }, "dataSource": { - "id": { "required": "Data Source ID is required" }, + "id": { + "invalid": "Data Source ID does not match the format expected by the selected cloud provider: {{format}}", + "required": "Data Source ID is required" + }, "affiliate_external_id": { "required": "Affiliate external ID is required" } } },