Skip to content

refactor(react): split sso-provider-edit hook layers#316

Open
harishsundar-okta wants to merge 3 commits into
mainfrom
refactor/hook-architecture-sso-provider-edit
Open

refactor(react): split sso-provider-edit hook layers#316
harishsundar-okta wants to merge 3 commits into
mainfrom
refactor/hook-architecture-sso-provider-edit

Conversation

@harishsundar-okta
Copy link
Copy Markdown
Contributor

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

  • Extracted data-fetching logic into internal use-sso-provider-edit-service.ts under shared/services/
  • Refactored use-sso-provider-edit.ts to be the public hook handling UI state and event handlers
  • Removed the old use-sso-provider-edit-logic.ts file (merged into the public hook)
  • Moved and renamed service-level tests to shared/tests/use-sso-provider-edit-service.test.ts
  • Updated component and type imports accordingly

Packages

  • packages/core
  • packages/react
  • examples

References

Testing

How can this be verified? Note anything intentionally not covered by tests and why.

  • This change adds unit test coverage
  • Tested for both SPA and RWA flows, all example apps working
  • All existing and new tests complete without errors

Checklist

  • Breaking change
  • Requires docs update
  • Backward compatible

Contributing

@codecov-commenter
Copy link
Copy Markdown

codecov-commenter commented May 29, 2026

Codecov Report

❌ Patch coverage is 96.86099% with 21 lines in your changes missing coverage. Please review.
✅ Project coverage is 89.58%. Comparing base (aecf241) to head (33e6dde).

Files with missing lines Patch % Lines
...n/shared/services/use-sso-provider-edit-service.ts 96.70% 21 Missing ⚠️
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.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

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%.
@harishsundar-okta harishsundar-okta marked this pull request as ready for review June 1, 2026 12:40
Comment on lines +39 to +46
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,
};
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we should not have queries in service, should be there in core

Comment on lines +593 to +598
/**
* 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.
*/
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 };
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we should not follow this reexport anti pattern

Comment on lines +119 to +150
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]);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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'),
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

its existing, but probably we should correct this typo of key as we are a public repo

Comment on lines +89 to +106
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;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Comment on lines +1356 to +1372
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
});
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we should have some expect on it statements

Comment on lines +1415 to +1422
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
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

similar for this

updateProvider: mockUpdateProvider,
});

describe('useSsoProviderEdit - logic behavior', () => {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we can remove this? because we dont have edit-logic hook any more?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants