Skip to content

Commit

Permalink
Merge pull request #48876 from software-mansion-labs/Guccio163/import…
Browse files Browse the repository at this point in the history
…-members-csv

Add import and export CSV flow for Members
  • Loading branch information
madmax330 authored Sep 20, 2024
2 parents 4c9a3fb + 233388d commit d009d8e
Show file tree
Hide file tree
Showing 20 changed files with 348 additions and 39 deletions.
1 change: 1 addition & 0 deletions src/CONST.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5764,6 +5764,7 @@ const CONST = {
ICON_HEIGHT: 160,

CATEGORIES_ARTICLE_LINK: 'https://help.expensify.com/articles/expensify-classic/workspaces/Create-categories#import-custom-categories',
MEMBERS_ARTICLE_LINK: 'https://help.expensify.com/articles/expensify-classic/workspaces/Invite-members-and-assign-roles#import-a-group-of-members',
TAGS_ARTICLE_LINK: 'https://help.expensify.com/articles/expensify-classic/workspaces/Create-tags#import-a-spreadsheet-1',
},

Expand Down
8 changes: 8 additions & 0 deletions src/ROUTES.ts
Original file line number Diff line number Diff line change
Expand Up @@ -728,6 +728,14 @@ const ROUTES = {
route: 'settings/workspaces/:policyID/members',
getRoute: (policyID: string) => `settings/workspaces/${policyID}/members` as const,
},
WORKSPACE_MEMBERS_IMPORT: {
route: 'settings/workspaces/:policyID/members/import',
getRoute: (policyID: string) => `settings/workspaces/${policyID}/members/import` as const,
},
WORKSPACE_MEMBERS_IMPORTED: {
route: 'settings/workspaces/:policyID/members/imported',
getRoute: (policyID: string) => `settings/workspaces/${policyID}/members/imported` as const,
},
POLICY_ACCOUNTING: {
route: 'settings/workspaces/:policyID/accounting',
getRoute: (policyID: string, newConnectionName?: ConnectionName, integrationToDisconnect?: ConnectionName, shouldDisconnectIntegrationBeforeConnecting?: boolean) => {
Expand Down
2 changes: 2 additions & 0 deletions src/SCREENS.ts
Original file line number Diff line number Diff line change
Expand Up @@ -408,6 +408,8 @@ const SCREENS = {
INVOICES_COMPANY_WEBSITE: 'Workspace_Invoices_Company_Website',
TRAVEL: 'Workspace_Travel',
MEMBERS: 'Workspace_Members',
MEMBERS_IMPORT: 'Members_Import',
MEMBERS_IMPORTED: 'Members_Imported',
INVITE: 'Workspace_Invite',
INVITE_MESSAGE: 'Workspace_Invite_Message',
CATEGORIES: 'Workspace_Categories',
Expand Down
6 changes: 3 additions & 3 deletions src/components/ImportSpreadsheetColumns.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ type ImportSpreadsheetColumnsProps = {
learnMoreLink?: string;
};

function ImportSpreeadsheetColumns({spreadsheetColumns, columnNames, columnRoles, errors, importFunction, isButtonLoading, learnMoreLink}: ImportSpreadsheetColumnsProps) {
function ImportSpreadsheetColumns({spreadsheetColumns, columnNames, columnRoles, errors, importFunction, isButtonLoading, learnMoreLink}: ImportSpreadsheetColumnsProps) {
const styles = useThemeStyles();
const {translate} = useLocalize();
const {isOffline} = useNetwork();
Expand Down Expand Up @@ -101,6 +101,6 @@ function ImportSpreeadsheetColumns({spreadsheetColumns, columnNames, columnRoles
);
}

ImportSpreeadsheetColumns.displayName = 'ImportSpreeadsheetColumns';
ImportSpreadsheetColumns.displayName = 'ImportSpreadsheetColumns';

export default ImportSpreeadsheetColumns;
export default ImportSpreadsheetColumns;
2 changes: 2 additions & 0 deletions src/languages/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -687,6 +687,7 @@ export default {
singleFieldMultipleColumns: (fieldName: string) => `Oops! You've mapped a single field ("${fieldName}") to multiple columns. Please review and try again.`,
importSuccessfullTitle: 'Import successful',
importCategoriesSuccessfullDescription: (categories: number) => (categories > 1 ? `${categories} categories have been added.` : '1 category has been added.'),
importMembersSuccessfullDescription: (members: number) => (members > 1 ? `${members} members have been added.` : '1 member has been added.'),
importTagsSuccessfullDescription: (tags: number) => (tags > 1 ? `${tags} tags have been added.` : '1 tag has been added.'),
importFailedTitle: 'Import failed',
importFailedDescription: 'Please ensure all fields are filled out correctly and try again. If the problem persists, please reach out to Concierge.',
Expand Down Expand Up @@ -3269,6 +3270,7 @@ export default {
addedWithPrimary: 'Some members were added with their primary logins.',
invitedBySecondaryLogin: ({secondaryLogin}) => `Added by secondary login ${secondaryLogin}.`,
membersListTitle: 'Directory of all workspace members.',
importMembers: 'Import members',
},
card: {
header: 'Unlock free Expensify Cards',
Expand Down
2 changes: 2 additions & 0 deletions src/languages/es.ts
Original file line number Diff line number Diff line change
Expand Up @@ -681,6 +681,7 @@ export default {
importFailedTitle: 'Fallo en la importación',
importFailedDescription: 'Por favor, asegúrate de que todos los campos estén llenos correctamente e inténtalo de nuevo. Si el problema persiste, por favor contacta a Concierge.',
importCategoriesSuccessfullDescription: (categories: number) => (categories > 1 ? `Se han agregado ${categories} categorías.` : 'Se ha agregado 1 categoría.'),
importMembersSuccessfullDescription: (members: number) => (members > 1 ? `Se han agregado ${members} miembros.` : 'Se ha agregado 1 miembro.'),
importTagsSuccessfullDescription: (tags: number) => (tags > 1 ? `Se han agregado ${tags} etiquetas.` : 'Se ha agregado 1 etiqueta.'),
importSuccessfullTitle: 'Importar categorías',
importDescription: 'Elige qué campos mapear desde tu hoja de cálculo haciendo clic en el menú desplegable junto a cada columna importada a continuación.',
Expand Down Expand Up @@ -3318,6 +3319,7 @@ export default {
addedWithPrimary: 'Se agregaron algunos miembros con sus nombres de usuario principales.',
invitedBySecondaryLogin: ({secondaryLogin}) => `Agregado por nombre de usuario secundario ${secondaryLogin}.`,
membersListTitle: 'Directorio de todos los miembros del espacio de trabajo.',
importMembers: 'Importar miembros',
},
accounting: {
settings: 'configuración',
Expand Down
6 changes: 6 additions & 0 deletions src/libs/API/parameters/ExportMembersSpreadsheetParams.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
type ExportMembersSpreadsheetParams = {
/** ID of the policy */
policyID: string;
};

export default ExportMembersSpreadsheetParams;
10 changes: 10 additions & 0 deletions src/libs/API/parameters/ImportMembersSpreadsheet.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
type ImportMembersSpreadsheetParams = {
policyID: string;
/**
* Stringified JSON object with type of following structure:
* Array<{email: string, role: string}>
*/
employees: string;
};

export default ImportMembersSpreadsheetParams;
2 changes: 2 additions & 0 deletions src/libs/API/parameters/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -312,6 +312,8 @@ export type {default as SetPolicyCategoryReceiptsRequiredParams} from './SetPoli
export type {default as RemovePolicyCategoryReceiptsRequiredParams} from './RemovePolicyCategoryReceiptsRequiredParams';
export type {default as UpdateQuickbooksOnlineAutoCreateVendorParams} from './UpdateQuickbooksOnlineAutoCreateVendorParams';
export type {default as ImportCategoriesSpreadsheetParams} from './ImportCategoriesSpreadsheet';
export type {default as ImportMembersSpreadsheetParams} from './ImportMembersSpreadsheet';
export type {default as ExportMembersSpreadsheetParams} from './ExportCategoriesSpreadsheet';
export type {default as ImportTagsSpreadsheetParams} from './ImportTagsSpreadsheet';
export type {default as ExportCategoriesSpreadsheetParams} from './ExportCategoriesSpreadsheet';
export type {default as ExportTagsSpreadsheetParams} from './ExportTagsSpreadsheet';
Expand Down
8 changes: 6 additions & 2 deletions src/libs/API/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -142,9 +142,11 @@ const WRITE_COMMANDS = {
SET_WORKSPACE_CATEGORIES_ENABLED: 'SetWorkspaceCategoriesEnabled',
SET_POLICY_TAGS_ENABLED: 'SetPolicyTagsEnabled',
CREATE_WORKSPACE_CATEGORIES: 'CreateWorkspaceCategories',
IMPORT_TAGS_SREADSHEET: 'ImportTagsSpreadsheet',
IMPORT_CATEGORIES_SPREADSHEET: 'ImportCategoriesSpreadsheet',
IMPORT_MEMBERS_SPREADSHEET: 'ImportMembersSpreadsheet',
IMPORT_TAGS_SPREADSHEET: 'ImportTagsSpreadsheet',
EXPORT_CATEGORIES_CSV: 'ExportCategoriesCSV',
EXPORT_MEMBERS_CSV: 'ExportMembersCSV',
EXPORT_TAGS_CSV: 'ExportTagsCSV',
RENAME_WORKSPACE_CATEGORY: 'RenameWorkspaceCategory',
CREATE_POLICY_TAG: 'CreatePolicyTag',
Expand Down Expand Up @@ -530,9 +532,11 @@ type WriteCommandParameters = {
[WRITE_COMMANDS.CREATE_WORKSPACE_FROM_IOU_PAYMENT]: Parameters.CreateWorkspaceFromIOUPaymentParams;
[WRITE_COMMANDS.SET_WORKSPACE_CATEGORIES_ENABLED]: Parameters.SetWorkspaceCategoriesEnabledParams;
[WRITE_COMMANDS.CREATE_WORKSPACE_CATEGORIES]: Parameters.CreateWorkspaceCategoriesParams;
[WRITE_COMMANDS.IMPORT_TAGS_SREADSHEET]: Parameters.ImportTagsSpreadsheetParams;
[WRITE_COMMANDS.IMPORT_CATEGORIES_SPREADSHEET]: Parameters.ImportCategoriesSpreadsheetParams;
[WRITE_COMMANDS.IMPORT_MEMBERS_SPREADSHEET]: Parameters.ImportMembersSpreadsheetParams;
[WRITE_COMMANDS.IMPORT_TAGS_SPREADSHEET]: Parameters.ImportTagsSpreadsheetParams;
[WRITE_COMMANDS.EXPORT_CATEGORIES_CSV]: Parameters.ExportCategoriesSpreadsheetParams;
[WRITE_COMMANDS.EXPORT_MEMBERS_CSV]: Parameters.ExportMembersSpreadsheetParams;
[WRITE_COMMANDS.EXPORT_TAGS_CSV]: Parameters.ExportTagsSpreadsheetParams;
[WRITE_COMMANDS.RENAME_WORKSPACE_CATEGORY]: Parameters.RenameWorkspaceCategoriesParams;
[WRITE_COMMANDS.SET_WORKSPACE_REQUIRES_CATEGORY]: Parameters.SetWorkspaceRequiresCategoryParams;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,8 @@ const SettingsModalStackNavigator = createModalStackNavigator<SettingsNavigatorP
[SCREENS.WORKSPACE.RATE_AND_UNIT_RATE]: () => require<ReactComponentModule>('../../../../pages/workspace/reimburse/WorkspaceRateAndUnitPage/RatePage').default,
[SCREENS.WORKSPACE.RATE_AND_UNIT_UNIT]: () => require<ReactComponentModule>('../../../../pages/workspace/reimburse/WorkspaceRateAndUnitPage/UnitPage').default,
[SCREENS.WORKSPACE.INVITE]: () => require<ReactComponentModule>('../../../../pages/workspace/WorkspaceInvitePage').default,
[SCREENS.WORKSPACE.MEMBERS_IMPORT]: () => require<ReactComponentModule>('../../../../pages/workspace/members/ImportMembersPage').default,
[SCREENS.WORKSPACE.MEMBERS_IMPORTED]: () => require<ReactComponentModule>('../../../../pages/workspace/members/ImportedMembersPage').default,
[SCREENS.WORKSPACE.WORKFLOWS_APPROVALS_NEW]: () => require<ReactComponentModule>('../../../../pages/workspace/workflows/approvals/WorkspaceWorkflowsApprovalsCreatePage').default,
[SCREENS.WORKSPACE.WORKFLOWS_APPROVALS_EDIT]: () => require<ReactComponentModule>('../../../../pages/workspace/workflows/approvals/WorkspaceWorkflowsApprovalsEditPage').default,
[SCREENS.WORKSPACE.WORKFLOWS_APPROVALS_EXPENSES_FROM]: () =>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ const FULL_SCREEN_TO_RHP_MAPPING: Partial<Record<FullScreenName, string[]>> = {
SCREENS.WORKSPACE.OWNER_CHANGE_SUCCESS,
SCREENS.WORKSPACE.OWNER_CHANGE_ERROR,
SCREENS.WORKSPACE.OWNER_CHANGE_ERROR,
SCREENS.WORKSPACE.MEMBERS_IMPORT,
SCREENS.WORKSPACE.MEMBERS_IMPORTED,
],
[SCREENS.WORKSPACE.WORKFLOWS]: [
SCREENS.WORKSPACE.WORKFLOWS_APPROVALS_NEW,
Expand Down
6 changes: 6 additions & 0 deletions src/libs/Navigation/linkingConfig/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -569,6 +569,12 @@ const config: LinkingOptions<RootStackParamList>['config'] = {
[SCREENS.WORKSPACE.INVITE]: {
path: ROUTES.WORKSPACE_INVITE.route,
},
[SCREENS.WORKSPACE.MEMBERS_IMPORT]: {
path: ROUTES.WORKSPACE_MEMBERS_IMPORT.route,
},
[SCREENS.WORKSPACE.MEMBERS_IMPORTED]: {
path: ROUTES.WORKSPACE_MEMBERS_IMPORTED.route,
},
[SCREENS.WORKSPACE.WORKFLOWS_APPROVALS_NEW]: {
path: ROUTES.WORKSPACE_WORKFLOWS_APPROVALS_NEW.route,
},
Expand Down
6 changes: 6 additions & 0 deletions src/libs/Navigation/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,12 @@ type SettingsNavigatorParamList = {
[SCREENS.WORKSPACE.INVITE]: {
policyID: string;
};
[SCREENS.WORKSPACE.MEMBERS_IMPORT]: {
policyID: string;
};
[SCREENS.WORKSPACE.MEMBERS_IMPORTED]: {
policyID: string;
};
[SCREENS.WORKSPACE.INVITE_MESSAGE]: {
policyID: string;
};
Expand Down
68 changes: 68 additions & 0 deletions src/libs/actions/Policy/Member.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,12 @@ import type {
UpdateWorkspaceMembersRoleParams,
} from '@libs/API/parameters';
import {READ_COMMANDS, WRITE_COMMANDS} from '@libs/API/types';
import * as ApiUtils from '@libs/ApiUtils';
import * as ErrorUtils from '@libs/ErrorUtils';
import fileDownload from '@libs/fileDownload';
import {translateLocal} from '@libs/Localize';
import Log from '@libs/Log';
import enhanceParameters from '@libs/Network/enhanceParameters';
import Parser from '@libs/Parser';
import * as PersonalDetailsUtils from '@libs/PersonalDetailsUtils';
import * as PhoneNumber from '@libs/PhoneNumber';
Expand All @@ -23,6 +27,7 @@ import type {InvitedEmailsToAccountIDs, PersonalDetailsList, Policy, PolicyEmplo
import type {PendingAction} from '@src/types/onyx/OnyxCommon';
import type {JoinWorkspaceResolution} from '@src/types/onyx/OriginalMessage';
import type {Attributes, Rate} from '@src/types/onyx/Policy';
import type {OnyxData} from '@src/types/onyx/Request';
import {isEmptyObject} from '@src/types/utils/EmptyObject';
import {createPolicyExpenseChats} from './Policy';

Expand Down Expand Up @@ -167,6 +172,36 @@ function buildAnnounceRoomMembersOnyxData(policyID: string, accountIDs: number[]
});
return announceRoomMembers;
}
/**
* Updates the import spreadsheet data according to the result of the import
*/
function updateImportSpreadsheetData(membersLength: number): OnyxData {
const onyxData: OnyxData = {
successData: [
{
onyxMethod: Onyx.METHOD.MERGE,
key: ONYXKEYS.IMPORTED_SPREADSHEET,
value: {
shouldFinalModalBeOpened: true,
importFinalModal: {title: translateLocal('spreadsheet.importSuccessfullTitle'), prompt: translateLocal('spreadsheet.importMembersSuccessfullDescription', membersLength)},
},
},
],

failureData: [
{
onyxMethod: Onyx.METHOD.MERGE,
key: ONYXKEYS.IMPORTED_SPREADSHEET,
value: {
shouldFinalModalBeOpened: true,
importFinalModal: {title: translateLocal('spreadsheet.importFailedTitle'), prompt: translateLocal('spreadsheet.importFailedDescription')},
},
},
],
};

return onyxData;
}

/**
* Build optimistic data for removing users from the announcement room
Expand Down Expand Up @@ -640,6 +675,22 @@ function addMembersToWorkspace(invitedEmailsToAccountIDs: InvitedEmailsToAccount
API.write(WRITE_COMMANDS.ADD_MEMBERS_TO_WORKSPACE, params, {optimisticData, successData, failureData});
}

type PolicyMember = {
email: string;
role: string;
};

function importPolicyMembers(policyID: string, members: PolicyMember[]) {
const onyxData = updateImportSpreadsheetData(members.length);

const parameters = {
policyID,
employees: JSON.stringify(members.map((member) => ({email: member.email, role: member.role}))),
};

API.write(WRITE_COMMANDS.IMPORT_MEMBERS_SPREADSHEET, parameters, onyxData);
}

/**
* Invite member to the specified policyID
* Please see https://github.com/Expensify/App/blob/main/README.md#Security for more details
Expand Down Expand Up @@ -836,6 +887,21 @@ function declineJoinRequest(reportID: string, reportAction: OnyxEntry<ReportActi
API.write(WRITE_COMMANDS.DECLINE_JOIN_REQUEST, parameters, {optimisticData, failureData, successData});
}

function downloadMembersCSV(policyID: string) {
const finalParameters = enhanceParameters(WRITE_COMMANDS.EXPORT_MEMBERS_CSV, {
policyID,
});

const fileName = 'Members.csv';

const formData = new FormData();
Object.entries(finalParameters).forEach(([key, value]) => {
formData.append(key, String(value));
});

fileDownload(ApiUtils.getCommandURL({command: WRITE_COMMANDS.EXPORT_MEMBERS_CSV}), fileName, '', false, formData, CONST.NETWORK.METHOD.POST);
}

export {
removeMembers,
updateWorkspaceMembersRole,
Expand All @@ -850,6 +916,8 @@ export {
acceptJoinRequest,
declineJoinRequest,
isApprover,
importPolicyMembers,
downloadMembersCSV,
};

export type {NewCustomUnit};
2 changes: 1 addition & 1 deletion src/libs/actions/Policy/Tag.ts
Original file line number Diff line number Diff line change
Expand Up @@ -230,7 +230,7 @@ function importPolicyTags(policyID: string, tags: PolicyTag[]) {
tags: JSON.stringify(tags.map((tag) => ({name: tag.name, enabled: tag.enabled, 'GL Code': tag['GL Code']}))),
};

API.write(WRITE_COMMANDS.IMPORT_TAGS_SREADSHEET, parameters, onyxData);
API.write(WRITE_COMMANDS.IMPORT_TAGS_SPREADSHEET, parameters, onyxData);
}

function setWorkspaceTagEnabled(policyID: string, tagsToUpdate: Record<string, {name: string; enabled: boolean}>, tagListIndex: number) {
Expand Down
Loading

0 comments on commit d009d8e

Please sign in to comment.