Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

CHI-2311: POC - Do not merge - persist redux locally #1936

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions .github/actions/main-action/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,15 @@ runs:
TWILIO_ACCOUNT_SID: ${{ inputs.account-sid }}
with:
filename: ./plugin-hrm-form/public/appConfig.js
# Get latest tag for this version
- name: Get latest tag
uses: oprypin/find-latest-tag@v1
with:
repository: ${{ inputs.repository }}
# releases-only: false
regex: "^v\d+\.\d+\.\d+.*$"
id: latest_matching_tag
continue-on-error: true
- name: Create secret.js
run: |
touch ./src/private/secret.js
Expand All @@ -95,6 +104,8 @@ runs:
export const datadogAccessToken = '$DATADOG_ACCESS_TOKEN';
export const datadogApplicationID = '$DATADOG_APP_ID';
export const fullStoryId = '$FULLSTORY_ID';
export const versionId = '${{ steps.latest_matching_tag.outputs.tag }}'
export const githubSha = '{{ github.sha }}';
EOT
working-directory: ./plugin-hrm-form
shell: bash
Expand Down
4 changes: 3 additions & 1 deletion plugin-hrm-form/src/HrmFormPlugin.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -43,10 +43,11 @@ import { setUpReferrableResources } from './components/resources/setUpReferrable
import { subscribeNewMessageAlertOnPluginInit } from './notifications/newMessage';
import { subscribeReservedTaskAlert } from './notifications/reservedTask';
import { setUpCounselorToolkits } from './components/toolkits/setUpCounselorToolkits';
import { setupConferenceComponents, setUpConferenceActions } from './conference';
import { setUpConferenceActions, setupConferenceComponents } from './conference';
import { setUpTransferActions } from './transfer/setUpTransferActions';
import { playNotification } from './notifications/playNotification';
import { namespace } from './states/storeNamespaces';
import { activateStatePersistence } from './states/persistState';

const PLUGIN_NAME = 'HrmFormPlugin';

Expand Down Expand Up @@ -245,6 +246,7 @@ export default class HrmFormPlugin extends FlexPlugin {
* This is a workaround until we deprecate 'getConfig' in it's current form after we migrate to Flex 2.0
*/
subscribeToConfigUpdates(manager);
activateStatePersistence();
}
}

Expand Down

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions plugin-hrm-form/src/___tests__/utils/sharedState.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ const metadata = {} as ContactMetadata;
const form: ContactState = {
savedContact: contact,
metadata,
references: new Set(),
references: {},
};
const task = createTask();

Expand Down Expand Up @@ -126,7 +126,7 @@ beforeEach(async () => {
metadata: {
draft: undefined,
} as ContactMetadata,
references: new Set<string>(),
references: {},
};

mockFlexManager = {
Expand Down
3 changes: 3 additions & 0 deletions plugin-hrm-form/src/hrmConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import { buildFormDefinitionsBaseUrlGetter, inferConfiguredFormDefinitionsBaseUr
import { ConfigFlags, FeatureFlags } from './types/types';
import type { RootState } from './states';
import { namespace } from './states/storeNamespaces';
import { githubSha, versionId } from './private/secret';

const featureFlagEnvVarPrefix = 'REACT_APP_FF_';
type ContactSaveFrequency = 'onTabChange' | 'onFinalSaveAndTransfer';
Expand Down Expand Up @@ -167,3 +168,5 @@ export const getAseloFeatureFlags = (): FeatureFlags => cachedConfig.featureFlag
export const getDefinitionVersions = () => {
return (Flex.Manager.getInstance().store.getState() as RootState)[namespace].configuration;
};

export const pluginVersionDescription = `${versionId}${githubSha ? `#${githubSha}` : ''}`;
4 changes: 2 additions & 2 deletions plugin-hrm-form/src/states/contacts/contactState.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import { ITask, TaskHelper } from '@twilio/flex-ui';
import type { ContactMetadata } from './types';
import { ReferralLookupStatus } from './resourceReferral';
import type { ContactState } from './existingContacts';
import { Contact, ContactRawJson, OfflineContactTask, isOfflineContactTask } from '../../types/types';
import { Contact, ContactRawJson, isOfflineContactTask, OfflineContactTask } from '../../types/types';
import { createStateItem, getInitialValue } from '../../components/common/forms/formGenerators';
import { createContactlessTaskTabDefinition } from '../../components/tabbedForms/ContactlessTaskTabDefinition';
import { getHrmConfig } from '../../hrmConfig';
Expand Down Expand Up @@ -111,5 +111,5 @@ export const newContactState = (definitions: DefinitionVersion, task?: ITask | O
savedContact: newContact(definitions, task),
metadata: newContactMetaData(recreated),
draftContact: {},
references: new Set(),
references: {},
});
17 changes: 8 additions & 9 deletions plugin-hrm-form/src/states/contacts/existingContacts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import { ConfigurationState } from '../configuration/reducer';
import { transformValuesForContactForm } from './contactDetailsAdapter';
import { ContactMetadata } from './types';
import { newContactMetaData } from './contactState';
import { add, has, remove, SerializableSet, size } from '../serializableSet';

export enum ContactDetailsRoute {
EDIT_CALLER_INFORMATION = 'editCallerInformation',
Expand Down Expand Up @@ -85,7 +86,7 @@ export type TranscriptResult = {
};

export type ContactState = {
references: Set<string>;
references: SerializableSet;
savedContact: Contact;
draftContact?: ContactDraftChanges;
metadata: ContactMetadata;
Expand Down Expand Up @@ -130,12 +131,10 @@ export const refreshContact = (contact: any) => loadContact(contact, undefined,
export const loadContactReducer = (state = initialState, action: LoadContactAction) => {
const updateEntries = action.contacts
.filter(c => {
return (
(action.reference && !(state[c.id]?.references ?? new Set()).has(action.reference)) || action.replaceExisting
);
return (action.reference && !has(state[c.id]?.references ?? {}, action.reference)) || action.replaceExisting;
})
.map(c => {
const current = state[c.id] ?? { references: new Set() };
const current = state[c.id] ?? { references: {} };
const { draftContact, ...currentContact } = state[c.id] ?? {
categories: {
expanded: {},
Expand All @@ -147,8 +146,8 @@ export const loadContactReducer = (state = initialState, action: LoadContactActi
{
metadata: newContactMetaData(true),
...currentContact,
savedContact: action.replaceExisting || !current.references.size ? c : state[c.id].savedContact,
references: action.reference ? current.references.add(action.reference) : current.references,
savedContact: action.replaceExisting || !size(current.references) ? c : state[c.id].savedContact,
references: action.reference ? add(current.references, action.reference) : current.references,
draftContact: action.replaceExisting ? undefined : draftContact,
},
];
Expand Down Expand Up @@ -189,10 +188,10 @@ export const releaseContactReducer = (state: ExistingContactsState, action: Rele
);
return [id, undefined];
}
current.references.delete(action.reference);
remove(current.references, action.reference);
return [id, current];
})
.filter(([, ecs]) => typeof ecs === 'object' && ecs.references.size > 0);
.filter(([, ecs]) => typeof ecs === 'object' && size(ecs.references) > 0);
return {
...omit(state, ...action.ids),
...Object.fromEntries(updateKvps),
Expand Down
6 changes: 3 additions & 3 deletions plugin-hrm-form/src/states/contacts/reducer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@ import {
ContactsState,
CREATE_CONTACT_ACTION,
LOAD_CONTACT_FROM_HRM_BY_TASK_ID_ACTION,
SET_SAVED_CONTACT, UPDATE_CONTACT_ACTION,
SET_SAVED_CONTACT,
UPDATE_CONTACT_ACTION,
} from './types';
import { REMOVE_CONTACT_STATE, RemoveContactStateAction } from '../types';
import {
Expand All @@ -34,7 +35,6 @@ import {
EXISTING_CONTACT_TOGGLE_CATEGORY_EXPANDED_ACTION,
EXISTING_CONTACT_UPDATE_DRAFT_ACTION,
ExistingContactAction,
initialState as existingContactInitialState,
LOAD_CONTACT_ACTION,
loadContactReducer,
loadTranscriptReducer,
Expand Down Expand Up @@ -66,7 +66,7 @@ export const emptyCategories = [];
// exposed for testing
export const initialState: ContactsState = {
existingContacts: {},
contactsBeingCreated: new Set<string>(),
contactsBeingCreated: {},
contactDetails: {
[DetailsContext.CASE_DETAILS]: { detailsExpanded: {} },
[DetailsContext.CONTACT_SEARCH]: { detailsExpanded: {} },
Expand Down
26 changes: 13 additions & 13 deletions plugin-hrm-form/src/states/contacts/saveContact.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,28 +20,29 @@ import { format } from 'date-fns';
import { submitContactForm } from '../../services/formSubmissionHelpers';
import {
connectToCase,
removeFromCase,
createContact,
getContactById,
getContactByTaskSid,
removeFromCase,
updateContactInHrm,
} from '../../services/ContactService';
import { Case, CustomITask, Contact } from '../../types/types';
import { Case, Contact, CustomITask } from '../../types/types';
import {
CONNECT_TO_CASE,
REMOVE_FROM_CASE,
ContactMetadata,
ContactsState,
CREATE_CONTACT_ACTION,
LOAD_CONTACT_FROM_HRM_BY_ID_ACTION,
LOAD_CONTACT_FROM_HRM_BY_TASK_ID_ACTION,
REMOVE_FROM_CASE,
SET_SAVED_CONTACT,
UPDATE_CONTACT_ACTION,
} from './types';
import { ContactDraftChanges } from './existingContacts';
import { newContactMetaData } from './contactState';
import { cancelCase, getCase } from '../../services/CaseService';
import { getCase } from '../../services/CaseService';
import { getUnsavedContact } from './getUnsavedContact';
import { add, remove } from '../serializableSet';

export const createContactAsyncAction = createAsyncAction(
CREATE_CONTACT_ACTION,
Expand Down Expand Up @@ -197,13 +198,13 @@ const loadContactIntoRedux = (
newMetadata?: ContactMetadata,
): ContactsState => {
const { existingContacts } = state;
const references = existingContacts[contact.id]?.references ?? new Set();
const references = existingContacts[contact.id]?.references ?? {};
if (reference) {
references.add(reference);
add(references, reference);
}
const metadata = newMetadata ?? existingContacts[contact.id]?.metadata;
const contactsBeingCreated = new Set(state.contactsBeingCreated);
contactsBeingCreated.delete(contact.taskId);
const contactsBeingCreated = { ...state.contactsBeingCreated };
remove(contactsBeingCreated, contact.taskId);
return {
...state,
contactsBeingCreated,
Expand Down Expand Up @@ -288,11 +289,10 @@ export const saveContactReducer = (initialState: ContactsState) =>
handleAction(
createContactAsyncAction.pending as typeof createContactAsyncAction,
(state, { meta: { taskSid } }): ContactsState => {
const contactsBeingCreated = new Set(state.contactsBeingCreated);
contactsBeingCreated.add(taskSid);
const contactsBeingCreated = { ...state.contactsBeingCreated };
return {
...state,
contactsBeingCreated,
contactsBeingCreated: add(contactsBeingCreated, taskSid),
};
},
),
Expand All @@ -310,8 +310,8 @@ export const saveContactReducer = (initialState: ContactsState) =>
} = action as typeof action & {
meta: { taskSid: string };
};
const contactsBeingCreated = new Set(state.contactsBeingCreated);
contactsBeingCreated.delete(taskSid);
const contactsBeingCreated = { ...state.contactsBeingCreated };
remove(contactsBeingCreated, taskSid);
return {
...state,
contactsBeingCreated,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

import { RootState } from '..';
import { namespace } from '../storeNamespaces';
import { has, size } from '../serializableSet';

export const selectIsContactCreating = (
{
Expand All @@ -24,12 +25,12 @@ export const selectIsContactCreating = (
},
}: RootState,
taskSid: string,
) => contactsBeingCreated.has(taskSid);
) => has(contactsBeingCreated, taskSid);

export const selectAnyContactIsSaving = ({
[namespace]: {
activeContacts: { contactsBeingCreated, existingContacts },
},
}: RootState) =>
contactsBeingCreated.size > 0 ||
size(contactsBeingCreated) > 0 ||
Object.values(existingContacts).some(({ metadata }) => metadata?.saveStatus === 'saving');
3 changes: 2 additions & 1 deletion plugin-hrm-form/src/states/contacts/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import { Case, Contact } from '../../types/types';
import { DraftResourceReferralState } from './resourceReferral';
import { ContactState, ExistingContactsState } from './existingContacts';
import { ContactDetailsState } from './contactDetails';
import { SerializableSet } from '../serializableSet';

// Action types
export const SAVE_END_MILLIS = 'SAVE_END_MILLIS';
Expand Down Expand Up @@ -63,7 +64,7 @@ export type ContactMetadata = {

export type ContactsState = {
existingContacts: ExistingContactsState;
contactsBeingCreated: Set<string>;
contactsBeingCreated: SerializableSet;
contactDetails: ContactDetailsState;
isCallTypeCaller: boolean;
};
Expand Down
8 changes: 5 additions & 3 deletions plugin-hrm-form/src/states/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ import { CaseState } from './case/types';
import { ContactsState } from './contacts/types';
import {
caseListBase,
caseMergingBannersBase,
conferencingBase,
configurationBase,
connectedCaseBase,
Expand All @@ -42,14 +43,14 @@ import {
csamReportBase,
dualWriteBase,
namespace,
profileBase,
queuesStatusBase,
referrableResourcesBase,
routingBase,
searchContactsBase,
caseMergingBannersBase,
profileBase,
} from './storeNamespaces';
import { reduce as CaseMergingBannersReducer } from './case/caseBanners';
import { readPersistedState } from './persistState';

const reducers = {
[searchContactsBase]: SearchFormReducer,
Expand Down Expand Up @@ -78,7 +79,8 @@ export type RootState = FlexState & { [namespace]: HrmState };
const combinedReducers = combineReducers(reducers);

// Combine the reducers
const reducer = (state: HrmState, action): HrmState => {
const reducer = (currentState: HrmState, action): HrmState => {
const state = currentState ?? readPersistedState();
return {
...combinedReducers(state, action),
/*
Expand Down
50 changes: 50 additions & 0 deletions plugin-hrm-form/src/states/persistState.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/**
* Copyright (C) 2021-2023 Technology Matters
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published
* by the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see https://www.gnu.org/licenses/.
*/

import { Manager } from '@twilio/flex-ui';
import _ from 'lodash';

import { RootState } from '.';
import { namespace } from './storeNamespaces';
import { getAseloFeatureFlags, pluginVersionDescription } from '../hrmConfig';

// Quick & dirty module to persist redux state to localStorage via subscriptions since we can't add middleware like redux-persist to do it for us
export const activateStatePersistence = () => {
if (getAseloFeatureFlags().enable_local_redux_persist) {
const debouncedWrite = _.debounce(() => {
// Exclude configuration from persisted state, since it contains non serializable elements, and is read only in the client anyway
const {
[namespace]: { configuration, ...persistableState },
} = Manager.getInstance().store.getState() as RootState;
sessionStorage.setItem(
'redux-state/plugin-hrm-form',
JSON.stringify({ [pluginVersionDescription]: persistableState }),
);
}, 1000);
Manager.getInstance().store.subscribe(debouncedWrite);
}
};

export const readPersistedState = (): RootState[typeof namespace] | null => {
if (getAseloFeatureFlags().enable_local_redux_persist) {
const persistedStateJson = sessionStorage.getItem('redux-state/plugin-hrm-form');
if (persistedStateJson) {
const persistedState = JSON.parse(persistedStateJson);
return persistedState[pluginVersionDescription];
}
}
return undefined;
};
Loading
Loading