refactor(react): split sso-provider-edit hook layers#316
refactor(react): split sso-provider-edit hook layers#316harishsundar-okta wants to merge 3 commits into
Conversation
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #316 +/- ##
==========================================
+ Coverage 89.47% 89.58% +0.10%
==========================================
Files 156 156
Lines 13070 13072 +2
Branches 1419 1343 -76
==========================================
+ Hits 11694 11710 +16
+ Misses 1376 1362 -14 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
Add tests for fetchProvider, fetchOrganizationDetails, and fetchProvisioning callbacks. Add early return tests for createScimToken, deleteScimToken, syncSsoAttributes, and syncProvisioningAttributes when provider/coreClient is null. Coverage improved from 94.51% to 96.7%.
| export const ssoProviderEditQueryKeys = { | ||
| all: ['sso-providers'] as const, | ||
| list: () => [...ssoProviderEditQueryKeys.all, 'list'] as const, | ||
| detail: (idpId: IdpId) => [...ssoProviderEditQueryKeys.all, 'detail', idpId] as const, | ||
| organization: () => ['organization', 'details'] as const, | ||
| provisioning: (idpId: IdpId) => [...ssoProviderEditQueryKeys.all, 'provisioning', idpId] as const, | ||
| scimTokens: (idpId: IdpId) => [...ssoProviderEditQueryKeys.all, 'scim-tokens', idpId] as const, | ||
| }; |
There was a problem hiding this comment.
we should not have queries in service, should be there in core
| /** | ||
| * List SCIM tokens mutation - fetches SCIM tokens for provisioning. | ||
| * Note: This uses imperative fetching rather than a query because tokens | ||
| * are typically fetched on-demand and the response includes sensitive data | ||
| * that shouldn't be automatically cached. | ||
| */ |
There was a problem hiding this comment.
its existing but probably we can optimize this
| provisioning: (idpId: IdpId) => [...ssoProviderEditQueryKeys.all, 'provisioning', idpId] as const, | ||
| scimTokens: (idpId: IdpId) => [...ssoProviderEditQueryKeys.all, 'scim-tokens', idpId] as const, | ||
| }; | ||
| export { ssoProviderEditQueryKeys }; |
There was a problem hiding this comment.
we should not follow this reexport anti pattern
| useEffect(() => { | ||
| if (providerQuery.isError && !hasShownProviderError.current) { | ||
| handleError(providerQuery.error, { fallbackMessage: t('general_error') }); | ||
| hasShownProviderError.current = true; | ||
| } | ||
|
|
||
| if (!providerQuery.isError) { | ||
| hasShownProviderError.current = false; | ||
| } | ||
| }, [providerQuery.isError, providerQuery.error, t, handleError]); | ||
|
|
||
| useEffect(() => { | ||
| if (organizationQuery.isError && !hasShownOrganizationError.current) { | ||
| handleError(organizationQuery.error, { fallbackMessage: t('general_error') }); | ||
| hasShownOrganizationError.current = true; | ||
| } | ||
|
|
||
| if (!organizationQuery.isError) { | ||
| hasShownOrganizationError.current = false; | ||
| } | ||
| }, [organizationQuery.error, organizationQuery.isError, t, handleError]); | ||
|
|
||
| useEffect(() => { | ||
| if (provisioningQuery.isError && !hasShownProvisioningError.current) { | ||
| handleError(provisioningQuery.error, { fallbackMessage: t('general_error') }); | ||
| hasShownProvisioningError.current = true; | ||
| } | ||
|
|
||
| if (!provisioningQuery.isError) { | ||
| hasShownProvisioningError.current = false; | ||
| } | ||
| }, [provisioningQuery.isError, provisioningQuery.error, t, handleError]); |
There was a problem hiding this comment.
looks quite a bit identical pattern here can we see if we can reuse? probably from a utility if you see can be used across the services hooks of other modules
function useQueryErrorToast(query: UseQueryResult, fallbackMessage: string) {
const hasShown = useRef(false);
const handleError = useErrorHandler();
useEffect(() => {
if (query.isError && !hasShown.current) {
handleError(query.error, { fallbackMessage });
hasShown.current = true;
}
if (!query.isError) hasShown.current = false;
}, [query.isError, query.error, handleError, fallbackMessage]);
}
useQueryErrorToast(providerQuery, t('general_error'));
useQueryErrorToast(organizationQuery, t('general_error'));
useQueryErrorToast(provisioningQuery, t('general_error'));
|
|
||
| showToast({ | ||
| type: 'success', | ||
| message: t('scim_token_delete_sucess'), |
There was a problem hiding this comment.
its existing, but probably we should correct this typo of key as we are a public repo
| mockOrgClient.organization.identityProviders.get = mockGet; | ||
| mockOrgClient.organization.identityProviders.update = mockUpdate; | ||
| mockOrgClient.organization.identityProviders.delete = mockDelete; | ||
| mockOrgClient.organization.identityProviders.detach = mockDetach; | ||
| ( | ||
| mockOrgClient.organization.identityProviders as unknown as Record<string, unknown> | ||
| ).provisioning = { | ||
| ...mockOrgClient.organization.identityProviders.provisioning, | ||
| get: mockProvisioningGet, | ||
| create: mockProvisioningCreate, | ||
| delete: mockProvisioningDelete, | ||
| scimTokens: { | ||
| list: mockScimTokensList, | ||
| create: mockScimTokensCreate, | ||
| delete: mockScimTokensDelete, | ||
| }, | ||
| }; | ||
| mockOrgClient.organizationDetails.get = mockGetOrgDetails; |
There was a problem hiding this comment.
I have seen this in quite a few test cases can we improvise this by using existing mocks createMockCoreClient, or if needed we can just extent it to use overrides
| it('should return early from syncSsoAttributes if idpId is empty', async () => { | ||
| const { result } = renderUseSsoProviderEdit(''); | ||
|
|
||
| await result.current.syncSsoAttributes(); | ||
|
|
||
| // Should not throw or call any API | ||
| }); | ||
|
|
||
| it('should return early from syncProvisioningAttributes if coreClient is null', async () => { | ||
| setupMockUseCoreClientNull(useCoreClientModule); | ||
|
|
||
| const { result } = renderUseSsoProviderEdit(mockIdpId); | ||
|
|
||
| await result.current.syncProvisioningAttributes(); | ||
|
|
||
| // Should not throw or call any API | ||
| }); |
There was a problem hiding this comment.
we should have some expect on it statements
| it('should return early from fetchOrganizationDetails if coreClient is null', async () => { | ||
| setupMockUseCoreClientNull(useCoreClientModule); | ||
|
|
||
| const { result } = renderUseSsoProviderEdit(mockIdpId); | ||
|
|
||
| await result.current.fetchOrganizationDetails(); | ||
|
|
||
| // Should not throw - early return when coreClient is null |
| updateProvider: mockUpdateProvider, | ||
| }); | ||
|
|
||
| describe('useSsoProviderEdit - logic behavior', () => { |
There was a problem hiding this comment.
we can remove this? because we dont have edit-logic hook any more?
Summary
Refactors the use-sso-provider-edit hook to follow the two-layer hook architecture (service & logic separation).
Why
The use-sso-provider-edit hook combined data fetching (TanStack Query operations, API calls, cache management) with UI orchestration (modals, selections, toasts, event handlers) in a single file. Splitting into two layers improves testability, maintainability, and consistency with the codebase convention.
What
Packages
packages/corepackages/reactexamplesReferences
Testing
How can this be verified? Note anything intentionally not covered by tests and why.
Checklist
Contributing