Skip to content

Commit

Permalink
ID-1150 Hide GitHub If Not Available (#4717)
Browse files Browse the repository at this point in the history
  • Loading branch information
tlangs authored Mar 15, 2024
1 parent 9b5d671 commit 4f5ff7b
Show file tree
Hide file tree
Showing 8 changed files with 152 additions and 8 deletions.
5 changes: 4 additions & 1 deletion src/auth/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -572,17 +572,20 @@ export const loadTerraUser = async (): Promise<void> => {
const getAllowances = Ajax().User.getUserAllowances();
const getAttributes = Ajax().User.getUserAttributes();
const getTermsOfService = Ajax().TermsOfService.getUserTermsOfServiceDetails();
const [profile, terraUserAllowances, terraUserAttributes, termsOfService] = await Promise.all([
const getEnterpriseFeatures = Ajax().User.getEnterpriseFeatures();
const [profile, terraUserAllowances, terraUserAttributes, termsOfService, enterpriseFeatures] = await Promise.all([
getProfile,
getAllowances,
getAttributes,
getTermsOfService,
getEnterpriseFeatures,
]);
clearNotification(sessionTimeoutProps.id);
userStore.update((state: TerraUserState) => ({
...state,
profile,
terraUserAttributes,
enterpriseFeatures,
}));
authStore.update((state: AuthState) => ({
...state,
Expand Down
46 changes: 46 additions & 0 deletions src/enterprise-features/features.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { act } from '@testing-library/react';
import { userHasAccessToEnterpriseFeature } from 'src/enterprise-features/features';
import { TerraUserState, userStore } from 'src/libs/state';

describe('features', () => {
describe('when the user has access to an enterprise feature', () => {
it('says the user has access to that feature', async () => {
// Arrange
const userEnterpriseFeatures = ['github-account-linking'];
await act(async () => {
userStore.update((state: TerraUserState) => ({ ...state, enterpriseFeatures: userEnterpriseFeatures }));
});

// Act
const hasAccess = userHasAccessToEnterpriseFeature('github-account-linking');
// Assert
expect(hasAccess).toBe(true);
});
});
describe('when the user does not have access to an enterprise feature', () => {
it('says the user does not have access to that feature', async () => {
// Arrange
const userEnterpriseFeatures = [];
await act(async () => {
userStore.update((state: TerraUserState) => ({ ...state, enterpriseFeatures: userEnterpriseFeatures }));
});

// Act
const hasAccess = userHasAccessToEnterpriseFeature('github-account-linking');
// Assert
expect(hasAccess).toBe(false);
});
});
it('it raises an error if the feature is not found', async () => {
// Arrange
const userEnterpriseFeatures = [];
await act(async () => {
userStore.update((state: TerraUserState) => ({ ...state, enterpriseFeatures: userEnterpriseFeatures }));
});

// Act
const throws = () => userHasAccessToEnterpriseFeature('feature-not-found');
// Assert
expect(throws).toThrow(Error);
});
});
17 changes: 17 additions & 0 deletions src/enterprise-features/features.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { userStore } from 'src/libs/state';

type EnterpriseFeature = {
id: string;
name: string;
};

const allFeatures: EnterpriseFeature[] = [{ id: 'github-account-linking', name: 'GitHub Account Linking' }];

export const userHasAccessToEnterpriseFeature = (feature: string): boolean => {
const matchingFeature = allFeatures.find((f) => f.id === feature);
if (!matchingFeature) {
throw new Error(`Feature ${feature} not found`);
}
const userEnterpriseFeatures = userStore.get().enterpriseFeatures;
return userEnterpriseFeatures?.includes(matchingFeature.id) ?? false;
};
9 changes: 9 additions & 0 deletions src/libs/ajax/User.ts
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,15 @@ export const User = (signal?: AbortSignal) => {
return res.json();
},

getEnterpriseFeatures: async (): Promise<string[]> => {
const res = await fetchSam(
'/api/resources/v2?format=flat&resourceTypes=enterprise-feature&roles=user',
_.mergeAll([authOpts(), { signal }])
);
const json = await res.json();
return json.resources.map((resource) => resource.resourceId);
},

registerWithProfile: async (
acceptsTermsOfService: boolean,
profile: CreateTerraUserProfileRequest
Expand Down
2 changes: 2 additions & 0 deletions src/libs/state.ts
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,7 @@ export interface TerraUserState {
profile: TerraUserProfile;
terraUser: TerraUser;
terraUserAttributes: SamUserAttributes;
enterpriseFeatures: string[];
}

/**
Expand Down Expand Up @@ -165,6 +166,7 @@ export const userStore: Atom<TerraUserState> = atom<TerraUserState>({
terraUserAttributes: {
marketingConsent: true,
},
enterpriseFeatures: [],
});

export const getTerraUser = (): TerraUser => userStore.get().terraUser;
Expand Down
65 changes: 65 additions & 0 deletions src/profile/external-identities/ExternalIdentities.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import { asMockedFn } from '@terra-ui-packages/test-utils';
import { act, screen } from '@testing-library/react';
import React from 'react';
import { getConfig } from 'src/libs/config';
import { TerraUserState, userStore } from 'src/libs/state';
import { ExternalIdentities } from 'src/profile/external-identities/ExternalIdentities';
import { renderWithAppContexts as render } from 'src/testing/test-utils';

jest.mock('src/libs/config', () => ({
...jest.requireActual('src/libs/config'),
getConfig: jest.fn().mockReturnValue({}),
}));

jest.mock('src/profile/external-identities/OAuth2Link', () => ({
...jest.requireActual('src/profile/external-identities/OAuth2Link'),
OAuth2Link: jest.fn((props) => <div>{props.provider.name}</div>),
}));

jest.mock('src/profile/external-identities/OAuth2Link', () => ({
...jest.requireActual('src/profile/external-identities/OAuth2Link'),
OAuth2Link: jest.fn((props) => <div>{props.provider.name}</div>),
}));

jest.mock('src/profile/external-identities/FenceAccount', () => ({
...jest.requireActual('src/profile/external-identities/FenceAccount'),
FenceAccount: jest.fn((props) => <div>{props.provider.name}</div>),
}));

jest.mock('src/profile/external-identities/NihAccount', () => ({
...jest.requireActual('src/profile/external-identities/NihAccount'),
NihAccount: jest.fn(() => <div>Nih Account</div>),
}));
describe('ExternalIdentities', () => {
beforeEach(() =>
asMockedFn(getConfig).mockReturnValue({ externalCreds: { providers: ['github'], urlRoot: 'https/foo.bar.com' } })
);
describe('when the user has access to GitHub Account Linking', () => {
it('shows the GitHub Account Linking card', async () => {
// Arrange
await act(async () => {
userStore.update((state: TerraUserState) => ({ ...state, enterpriseFeatures: ['github-account-linking'] }));
});

// Act
render(<ExternalIdentities queryParams={{}} />);

// Assert
screen.getByText('GitHub');
});
});
describe('when the user does not have access to GitHub Account Linking', () => {
it('hides the GitHub Account Linking card', async () => {
// Arrange
await act(async () => {
userStore.update((state: TerraUserState) => ({ ...state, enterpriseFeatures: [] }));
});

// Act
render(<ExternalIdentities queryParams={{}} />);

// Assert
expect(screen.queryByText('GitHub')).toBeNull();
});
});
});
15 changes: 8 additions & 7 deletions src/profile/external-identities/ExternalIdentities.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import _ from 'lodash/fp';
import React, { ReactNode } from 'react';
import { PageBox, PageBoxVariants } from 'src/components/PageBox';
import { userHasAccessToEnterpriseFeature } from 'src/enterprise-features/features';
import { getConfig } from 'src/libs/config';
import allProviders from 'src/libs/providers';
import { FenceAccount } from 'src/profile/external-identities/FenceAccount';
Expand All @@ -24,13 +25,13 @@ export const ExternalIdentities = (props: ExternalIdentitiesProps): ReactNode =>
),
allProviders
)}
{getConfig().externalCreds?.providers.map((providerKey) => (
<OAuth2Link
key={`oauth2link-${providerKey}`}
queryParams={queryParams}
provider={oauth2Provider(providerKey)}
/>
))}
{getConfig().externalCreds?.providers.includes('ras') && (
<OAuth2Link key="oauth2link-ras}" queryParams={queryParams} provider={oauth2Provider('ras')} />
)}
{getConfig().externalCreds?.providers.includes('github') &&
userHasAccessToEnterpriseFeature('github-account-linking') && (
<OAuth2Link key="oauth2link-github}" queryParams={queryParams} provider={oauth2Provider('github')} />
)}
</PageBox>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ const setupMockAjax = async (
User: {
getUserAttributes: jest.fn().mockResolvedValue({ marketingConsent: true }),
getUserAllowances: jest.fn().mockResolvedValue(terraUserAllowances),
getEnterpriseFeatures: jest.fn().mockResolvedValue([]),
profile: {
get: jest.fn().mockResolvedValue({ keyValuePairs: [] }),
update: jest.fn().mockResolvedValue({ keyValuePairs: [] }),
Expand Down

0 comments on commit 4f5ff7b

Please sign in to comment.