diff --git a/CHANGELOG.md b/CHANGELOG.md index e2d06079de..bfe9ee2889 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## v.0.9.3 + +### Added + +- Adding a UCP specific variable to the connection success event + ## v.0.9.2 ### Added diff --git a/package.json b/package.json index aa671e75e0..bf17017cf4 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "@mxenabled/connect-widget", "description": "A simple ui library for React", - "version": "0.9.2", + "version": "0.9.3", "module": "dist/index.es.js", "types": "dist/index.d.ts", "type": "module", diff --git a/src/components/RenderConnectStep.js b/src/components/RenderConnectStep.js index de3415e6d6..473048c820 100644 --- a/src/components/RenderConnectStep.js +++ b/src/components/RenderConnectStep.js @@ -10,7 +10,7 @@ import { useTokens } from '@kyper/tokenprovider' import * as connectActions from 'src/redux/actions/Connect' import { getSize } from 'src/redux/selectors/Browser' -import { getCurrentMember, getMembers } from 'src/redux/selectors/Connect' +import { getCurrentMember, getMembers, getSelectedInstitution } from 'src/redux/selectors/Connect' import { selectConnectConfig, selectIsMobileWebView, @@ -57,7 +57,7 @@ const RenderConnectStep = (props) => { ) const connectedMembers = useSelector(getMembers) const currentMember = useSelector(getCurrentMember) - const selectedInstitution = useSelector((state) => state.connect.selectedInstitution) + const selectedInstitution = useSelector(getSelectedInstitution) const updateCredentials = useSelector((state) => state.connect.updateCredentials) const verifyMemberError = useSelector((state) => state.connect.error) diff --git a/src/hooks/__tests__/useSelectInstitution-test.tsx b/src/hooks/__tests__/useSelectInstitution-test.tsx index 3c4f1bfd7e..a5e7c72056 100644 --- a/src/hooks/__tests__/useSelectInstitution-test.tsx +++ b/src/hooks/__tests__/useSelectInstitution-test.tsx @@ -1,12 +1,12 @@ import React from 'react' import { useSelector } from 'react-redux' -import type { RootState } from 'src/redux/Store' import { screen, render } from 'src/utilities/testingLibrary' import useSelectInstitution from 'src/hooks/useSelectInstitution' import { institutionData } from 'src/services/mockedData' +import { getSelectedInstitution } from 'src/redux/selectors/Connect' const TestSelectInstitutionComponent = () => { - const selectedInstitution = useSelector((state: RootState) => state.connect.selectedInstitution) + const selectedInstitution = useSelector(getSelectedInstitution) const { handleSelectInstitution } = useSelectInstitution() return (
diff --git a/src/redux/selectors/Connect.js b/src/redux/selectors/Connect.js index 0ed27c0a0b..653961a29b 100644 --- a/src/redux/selectors/Connect.js +++ b/src/redux/selectors/Connect.js @@ -15,13 +15,34 @@ const getMemberByGuid = (members, guid) => { /** * Selectors */ + +const getConnectSlice = createSelector( + (state) => state, + (state) => state.connect, +) + +const getMembersRaw = createSelector(getConnectSlice, (slice) => slice.members) + export const getCurrentMember = createSelector( - (state) => state.connect.members, + getMembersRaw, (state) => state.connect.currentMemberGuid, getMemberByGuid, ) + export const getMembers = createSelector( - (state) => state.connect.members, + getMembersRaw, (members) => members?.filter((member) => !(member.connection_status === ReadableStatuses.PENDING)) ?? [], ) + +export const getSelectedInstitution = createSelector( + getConnectSlice, + (slice) => slice.selectedInstitution, +) + +export const getSelectedInstitutionUcpInstitutionId = createSelector( + getSelectedInstitution, + (selectedInstitution) => { + return selectedInstitution?.ucpInstitutionId + }, +) diff --git a/src/redux/selectors/__tests__/Connect-test.js b/src/redux/selectors/__tests__/Connect-test.js index 72f3151bff..182b8def09 100644 --- a/src/redux/selectors/__tests__/Connect-test.js +++ b/src/redux/selectors/__tests__/Connect-test.js @@ -1,5 +1,21 @@ -import * as ConnectSelectors from 'src/redux/selectors/Connect' import { genMember } from 'src/utilities/generators/Members' +import { + getCurrentMember, + getMembers, + getSelectedInstitution, + getSelectedInstitutionUcpInstitutionId, +} from '../Connect' +import { ReadableStatuses } from 'src/const/Statuses' + +const ucpInstitutionId = 'testUcpInstitutionId' + +const stateWithASelectedInstitution = { + connect: { + selectedInstitution: { + ucpInstitutionId, + }, + }, +} describe('Connect Selectors', () => { describe('getCurrentMember', () => { @@ -10,9 +26,52 @@ describe('Connect Selectors', () => { members: [genMember({ guid: 'MBR-1' }), genMember({ guid: 'MBR-2' })], }, } - const currentMember = ConnectSelectors.getCurrentMember(state) + const currentMember = getCurrentMember(state) expect(currentMember.guid).toEqual('MBR-1') }) }) + + describe('getMembers', () => { + it("returns an empty array if members doesn't exist", () => { + const state = { + connect: {}, + } + + expect(getMembers(state)).toEqual([]) + }) + + it("returns any members that aren't pending", () => { + const memberThatIsPending = genMember({ + connection_status: ReadableStatuses.PENDING, + }) + const memberThatIsntPending = genMember({ + connection_status: ReadableStatuses.CHALLENGED, + }) + + const state = { + connect: { + members: [memberThatIsPending, memberThatIsntPending], + }, + } + + expect(getMembers(state)).toEqual([memberThatIsntPending]) + }) + }) + + describe('getSelectedInstitution', () => { + it('returns the selected institution', () => { + expect(getSelectedInstitution(stateWithASelectedInstitution)).toEqual( + stateWithASelectedInstitution.connect.selectedInstitution, + ) + }) + }) + + describe('getSelectedInstitutionUcpInstitutionId', () => { + it("returns the selected institution's ucpId", () => { + expect(getSelectedInstitutionUcpInstitutionId(stateWithASelectedInstitution)).toEqual( + ucpInstitutionId, + ) + }) + }) }) diff --git a/src/views/connecting/Connecting.js b/src/views/connecting/Connecting.js index 30c67f7809..b084d745ce 100644 --- a/src/views/connecting/Connecting.js +++ b/src/views/connecting/Connecting.js @@ -27,7 +27,10 @@ import * as JobSchedule from 'src/utilities/JobSchedule' import { AriaLive } from 'src/components/AriaLive' import useAnalyticsPath from 'src/hooks/useAnalyticsPath' import { useApi } from 'src/context/ApiContext' -import { getCurrentMember } from 'src/redux/selectors/Connect' +import { + getCurrentMember, + getSelectedInstitutionUcpInstitutionId, +} from 'src/redux/selectors/Connect' import { isConnectComboJobsEnabled } from 'src/redux/reducers/userFeaturesSlice' import { ErrorStatuses, ReadableStatuses } from 'src/const/Statuses' @@ -69,6 +72,9 @@ export const Connecting = (props) => { const styles = getStyles(tokens) const getNextDelay = getDelay() const dispatch = useDispatch() + + const ucpInstitutionId = useSelector(getSelectedInstitutionUcpInstitutionId) + const analyticFunctions = useContext(AnalyticContext) const postMessageFunctions = useContext(PostMessageContext) const connectingRef = useRef(null) @@ -125,6 +131,10 @@ export const Connecting = (props) => { event.aggregator = currentMember.aggregator } + if (ucpInstitutionId) { + event.ucpInstitutionId = ucpInstitutionId + } + postMessageFunctions.onPostMessage(POST_MESSAGES.MEMBER_CONNECTED, event) analyticFunctions.onAnalyticEvent(`connect_${POST_MESSAGES.MEMBER_CONNECTED}`, { type: connectConfig.is_mobile_webview ? 'url' : 'message', diff --git a/src/views/credentials/CreateMemberForm.js b/src/views/credentials/CreateMemberForm.js index 5e183d704d..a734988ca1 100644 --- a/src/views/credentials/CreateMemberForm.js +++ b/src/views/credentials/CreateMemberForm.js @@ -15,6 +15,7 @@ import { LoadingSpinner } from 'src/components/LoadingSpinner' import { ReadableStatuses } from 'src/const/Statuses' import { PostMessageContext } from 'src/ConnectWidget' +import { getSelectedInstitution } from 'src/redux/selectors/Connect' /** * Responsibilities: @@ -23,7 +24,7 @@ import { PostMessageContext } from 'src/ConnectWidget' * - Performs the CREATE */ export const CreateMemberForm = (props) => { - const institution = useSelector((state) => state.connect.selectedInstitution) + const institution = useSelector(getSelectedInstitution) useAnalyticsPath(...PageviewInfo.CONNECT_CREATE_CREDENTIALS, { institution_guid: institution.guid, institution_name: institution.name, diff --git a/src/views/credentials/Credentials.js b/src/views/credentials/Credentials.js index 8a40d5f67e..f694010184 100644 --- a/src/views/credentials/Credentials.js +++ b/src/views/credentials/Credentials.js @@ -35,7 +35,7 @@ import { InstitutionBlock } from 'src/components/InstitutionBlock' import { InstructionalText } from 'src/components/InstructionalText' import { InstructionList } from 'src/components/InstructionList' import { Support, VIEWS as SUPPORT_VIEWS } from 'src/components/support/Support' -import { getCurrentMember } from 'src/redux/selectors/Connect' +import { getCurrentMember, getSelectedInstitution } from 'src/redux/selectors/Connect' import { buildInitialValues, buildFormSchema } from 'src/views/credentials/utils' import { CREDENTIAL_FIELD_TYPES } from 'src/views/credentials/consts' @@ -77,7 +77,7 @@ export const Credentials = React.forwardRef( // Redux Selectors/Dispatch const connectConfig = useSelector(selectConnectConfig) const isSmall = useSelector((state) => state.browser.size) === 'small' - const institution = useSelector((state) => state.connect.selectedInstitution) + const institution = useSelector(getSelectedInstitution) const showExternalLinkPopup = useSelector( (state) => state.profiles.clientProfile.show_external_link_popup, ) diff --git a/src/views/credentials/UpdateMemberForm.js b/src/views/credentials/UpdateMemberForm.js index f704c7d135..c333faf94e 100644 --- a/src/views/credentials/UpdateMemberForm.js +++ b/src/views/credentials/UpdateMemberForm.js @@ -6,7 +6,7 @@ import { useDispatch, useSelector } from 'react-redux' import { useApi } from 'src/context/ApiContext' import useAnalyticsPath from 'src/hooks/useAnalyticsPath' import { PageviewInfo } from 'src/const/Analytics' -import { getCurrentMember } from 'src/redux/selectors/Connect' +import { getCurrentMember, getSelectedInstitution } from 'src/redux/selectors/Connect' import { ActionTypes } from 'src/redux/actions/Connect' import { selectConfig } from 'src/redux/reducers/configSlice' @@ -23,7 +23,7 @@ import { PostMessageContext } from 'src/ConnectWidget' */ export const UpdateMemberForm = (props) => { useAnalyticsPath(...PageviewInfo.CONNECT_UPDATE_CREDENTIALS) - const institution = useSelector((state) => state.connect.selectedInstitution) + const institution = useSelector(getSelectedInstitution) const currentMember = useSelector(getCurrentMember) const config = useSelector(selectConfig) const isHuman = useSelector((state) => state.app.humanEvent) diff --git a/src/views/disclosure/Interstitial.js b/src/views/disclosure/Interstitial.js index f4d2fc14eb..ac8fc26d27 100644 --- a/src/views/disclosure/Interstitial.js +++ b/src/views/disclosure/Interstitial.js @@ -21,6 +21,7 @@ import { ConnectLogoHeader } from 'src/components/ConnectLogoHeader' import { PrivacyPolicy } from 'src/views/disclosure/PrivacyPolicy' import { DataRequested } from 'src/views/disclosure/DataRequested' import { DataAvailable } from 'src/views/disclosure/DataAvailable' +import { getSelectedInstitution } from 'src/redux/selectors/Connect' export const VIEWS = { AVAILABLE_DATA: 'available_data', @@ -35,7 +36,7 @@ export const DisclosureInterstitial = React.forwardRef((props, interstitialNavRe const tokens = useTokens() const styles = getStyles(tokens) const getNextDelay = getDelay() - const institution = useSelector((state) => state.connect.selectedInstitution) + const institution = useSelector(getSelectedInstitution) const appName = useSelector((state) => state.profiles.client.oauth_app_name || null) const [currentView, setCurrentView] = useState(VIEWS.INTERSTITIAL_DISCLOSURE) diff --git a/src/views/loginError/NoEligibleAccountsError.js b/src/views/loginError/NoEligibleAccountsError.js index e43c5471ae..41ba4e7617 100644 --- a/src/views/loginError/NoEligibleAccountsError.js +++ b/src/views/loginError/NoEligibleAccountsError.js @@ -9,7 +9,7 @@ import { Button } from '@mui/material' import { __ } from 'src/utilities/Intl' import { ActionTypes } from 'src/redux/actions/Connect' -import { getCurrentMember } from 'src/redux/selectors/Connect' +import { getCurrentMember, getSelectedInstitution } from 'src/redux/selectors/Connect' import { AriaLive } from 'src/components/AriaLive' import { SlideDown } from 'src/components/SlideDown' @@ -27,7 +27,7 @@ export const NoEligibleAccounts = () => { const dispatch = useDispatch() const currentMember = useSelector(getCurrentMember) - const selectedInstitution = useSelector((state) => state.connect.selectedInstitution) + const selectedInstitution = useSelector(getSelectedInstitution) const postHogEventMetadata = { authentication_method: currentMember.is_oauth diff --git a/src/views/oauth/OAuthError.js b/src/views/oauth/OAuthError.js index b6dde6c853..2c2b36d7a6 100644 --- a/src/views/oauth/OAuthError.js +++ b/src/views/oauth/OAuthError.js @@ -15,6 +15,7 @@ import { InstitutionBlock } from 'src/components/InstitutionBlock' import { SlideDown } from 'src/components/SlideDown' import { getDelay } from 'src/utilities/getDelay' import { PostMessageContext } from 'src/ConnectWidget' +import { getSelectedInstitution } from 'src/redux/selectors/Connect' export const OAuthError = React.forwardRef((props, navigationRef) => { useAnalyticsPath(...PageviewInfo.CONNECT_OAUTH_ERROR) @@ -22,7 +23,7 @@ export const OAuthError = React.forwardRef((props, navigationRef) => { const postMessageFunctions = useContext(PostMessageContext) const errorReason = useSelector((state) => state.connect.oauthErrorReason) - const selectedInstitution = useSelector((state) => state.connect.selectedInstitution) + const selectedInstitution = useSelector(getSelectedInstitution) const tokens = useTokens() const styles = getStyles(tokens) const getNextDelay = getDelay()