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

[OldDot Rules Migration] Tag rules #48325

Merged
merged 36 commits into from
Sep 19, 2024
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
53828c7
Add WorkspaceMembersSelectionList
WojtekBoman Aug 28, 2024
374f298
add interface for tag edit
BrtqKr Aug 28, 2024
299856e
wire up the tags
BrtqKr Aug 29, 2024
4a3bfcc
wire up action
BrtqKr Aug 30, 2024
d64e7b6
Merge remote-tracking branch 'origin/main' into brtqkr/47016-tag-rules
BrtqKr Sep 9, 2024
10dbc8e
cleanup, modify action
BrtqKr Sep 9, 2024
e9dbc5c
change translations
BrtqKr Sep 9, 2024
99d973f
Merge remote-tracking branch 'origin/main' into brtqkr/47016-tag-rules
BrtqKr Sep 10, 2024
78df971
add remove approver option
BrtqKr Sep 10, 2024
7f792c8
disable when workflows are disabled
BrtqKr Sep 10, 2024
6163fe5
add approver conditon
BrtqKr Sep 10, 2024
bd3fdba
add approver conditon to category settings
BrtqKr Sep 10, 2024
21d8f3d
replace values with const
BrtqKr Sep 11, 2024
d7cb964
change conditon for find category approver
BrtqKr Sep 11, 2024
5c0b778
ts fix
BrtqKr Sep 11, 2024
804c601
use getCategoryApproverRule in the actions
BrtqKr Sep 12, 2024
c43913b
change offline pattern for tags
BrtqKr Sep 12, 2024
4770792
ts fix
BrtqKr Sep 12, 2024
e0e1d5b
make pending keys unique
BrtqKr Sep 12, 2024
57a13f2
review fixes
BrtqKr Sep 13, 2024
9fe0ff3
Merge remote-tracking branch 'origin/main' into brtqkr/47016-tag-rules
BrtqKr Sep 13, 2024
d7e902a
Merge remote-tracking branch 'origin/main' into brtqkr/47016-tag-rules
BrtqKr Sep 17, 2024
fe5f31c
update category rule when renaming category
BrtqKr Sep 17, 2024
bc4eed8
update tag rule when renaming tag
BrtqKr Sep 17, 2024
6fc0879
remove pending action pattern for rules
BrtqKr Sep 17, 2024
69d919a
Merge remote-tracking branch 'origin/main' into brtqkr/47016-tag-rules
BrtqKr Sep 17, 2024
08cf2a2
fix onyx check
BrtqKr Sep 18, 2024
b32ed64
remove pending pattern from type
BrtqKr Sep 18, 2024
71c8ef8
run prettier
BrtqKr Sep 18, 2024
669e692
cleanup
BrtqKr Sep 18, 2024
4fccbb1
cleanup
BrtqKr Sep 18, 2024
2fdea82
remove withOnyx
BrtqKr Sep 18, 2024
d91e3f3
Merge remote-tracking branch 'origin/main' into brtqkr/47016-tag-rules
BrtqKr Sep 18, 2024
4810e33
run prettier
BrtqKr Sep 18, 2024
2ba3191
cleanup
BrtqKr Sep 18, 2024
e06551c
rename connection
BrtqKr Sep 19, 2024
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
4 changes: 4 additions & 0 deletions src/ROUTES.ts
Original file line number Diff line number Diff line change
Expand Up @@ -814,6 +814,10 @@ const ROUTES = {
route: 'settings/workspaces/:policyID/tag/:orderWeight/:tagName',
getRoute: (policyID: string, orderWeight: number, tagName: string) => `settings/workspaces/${policyID}/tag/${orderWeight}/${encodeURIComponent(tagName)}` as const,
},
WORKSPACE_TAG_APPROVER: {
route: 'settings/workspaces/:policyID/tag/:orderWeight/:tagName/approver',
getRoute: (policyID: string, orderWeight: number, tagName: string) => `settings/workspaces/${policyID}/tag/${orderWeight}/${tagName}/approver` as const,
},
WORKSPACE_TAG_LIST_VIEW: {
route: 'settings/workspaces/:policyID/tag-list/:orderWeight',
getRoute: (policyID: string, orderWeight: number) => `settings/workspaces/${policyID}/tag-list/${orderWeight}` as const,
Expand Down
1 change: 1 addition & 0 deletions src/SCREENS.ts
Original file line number Diff line number Diff line change
Expand Up @@ -416,6 +416,7 @@ const SCREENS = {
TAX_CREATE: 'Workspace_Tax_Create',
TAG_CREATE: 'Tag_Create',
TAG_SETTINGS: 'Tag_Settings',
TAG_APPROVER: 'Tag_Approver',
TAG_LIST_VIEW: 'Tag_List_View',
TAG_GL_CODE: 'Tag_GL_Code',
CURRENCY: 'Workspace_Profile_Currency',
Expand Down
115 changes: 115 additions & 0 deletions src/components/WorkspaceMembersSelectionList.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
import React, {useMemo} from 'react';
import type {SectionListData} from 'react-native';
import useDebouncedState from '@hooks/useDebouncedState';
import useLocalize from '@hooks/useLocalize';
import usePolicy from '@hooks/usePolicy';
import useScreenWrapperTranstionStatus from '@hooks/useScreenWrapperTransitionStatus';
import * as DeviceCapabilities from '@libs/DeviceCapabilities';
import * as OptionsListUtils from '@libs/OptionsListUtils';
import * as PolicyUtils from '@libs/PolicyUtils';
import CONST from '@src/CONST';
import type {Icon} from '@src/types/onyx/OnyxCommon';
import Badge from './Badge';
import {FallbackAvatar} from './Icon/Expensicons';
import {usePersonalDetails} from './OnyxProvider';
import SelectionList from './SelectionList';
import InviteMemberListItem from './SelectionList/InviteMemberListItem';
import type {Section} from './SelectionList/types';

type WorkspaceMembersSelectionListProps = {
policyID: string;
selectedApprover: string;
setApprover: (email: string) => void;
};

type SelectionListApprover = {
text: string;
alternateText: string;
keyForList: string;
isSelected: boolean;
login: string;
rightElement?: React.ReactNode;
icons: Icon[];
};
type ApproverSection = SectionListData<SelectionListApprover, Section<SelectionListApprover>>;

function WorkspaceMembersSelectionList({policyID, selectedApprover, setApprover}: WorkspaceMembersSelectionListProps) {
const {translate} = useLocalize();
const {didScreenTransitionEnd} = useScreenWrapperTranstionStatus();
const [searchTerm, debouncedSearchTerm, setSearchTerm] = useDebouncedState('');
const personalDetails = usePersonalDetails();
const policy = usePolicy(policyID);

const sections: ApproverSection[] = useMemo(() => {
const approvers: SelectionListApprover[] = [];

if (policy?.employeeList) {
const availableApprovers = Object.values(policy.employeeList)
.map((employee): SelectionListApprover | null => {
const isAdmin = employee?.role === CONST.REPORT.ROLE.ADMIN;
const email = employee.email;

if (!email) {
return null;
}

const policyMemberEmailsToAccountIDs = PolicyUtils.getMemberAccountIDsForWorkspace(policy?.employeeList);
const accountID = Number(policyMemberEmailsToAccountIDs[email] ?? '');
const {avatar, displayName = email} = personalDetails?.[accountID] ?? {};

return {
text: displayName,
alternateText: email,
keyForList: email,
isSelected: selectedApprover === email,
login: email,
icons: [{source: avatar ?? FallbackAvatar, type: CONST.ICON_TYPE_AVATAR, name: displayName, id: accountID}],
rightElement: isAdmin ? <Badge text={translate('common.admin')} /> : undefined,
};
})
.filter((approver): approver is SelectionListApprover => !!approver);

approvers.push(...availableApprovers);
}

const filteredApprovers =
debouncedSearchTerm !== ''
? approvers.filter((option) => {
const searchValue = OptionsListUtils.getSearchValueForPhoneOrEmail(debouncedSearchTerm);
const isPartOfSearchTerm = !!option.text?.toLowerCase().includes(searchValue) || !!option.login?.toLowerCase().includes(searchValue);
return isPartOfSearchTerm;
})
: approvers;

return [
{
title: undefined,
data: filteredApprovers,
shouldShow: true,
},
];
}, [debouncedSearchTerm, personalDetails, policy?.employeeList, selectedApprover, translate]);

const handleOnSelectRow = (approver: SelectionListApprover) => {
setApprover(approver.login);
};

const headerMessage = useMemo(() => (searchTerm && !sections[0].data.length ? translate('common.noResultsFound') : ''), [searchTerm, sections, translate]);

return (
<SelectionList
sections={sections}
ListItem={InviteMemberListItem}
textInputLabel={translate('selectionList.findMember')}
textInputValue={searchTerm}
onChangeText={setSearchTerm}
headerMessage={headerMessage}
onSelectRow={handleOnSelectRow}
showScrollIndicator
showLoadingPlaceholder={!didScreenTransitionEnd}
shouldPreventDefaultFocusOnSelectRow={!DeviceCapabilities.canUseTouchScreen()}
/>
);
}

export default WorkspaceMembersSelectionList;
2 changes: 2 additions & 0 deletions src/languages/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3110,6 +3110,8 @@ export default {
importedFromAccountingSoftware: 'The tags below are imported from your',
glCode: 'GL code',
updateGLCodeFailureMessage: 'An error occurred while updating the GL code, please try again.',
tagRules: 'Tag rules',
approverDescription: 'Approver',
},
taxes: {
subtitle: 'Add tax names, rates, and set defaults.',
Expand Down
2 changes: 2 additions & 0 deletions src/languages/es.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3159,6 +3159,8 @@ export default {
importedFromAccountingSoftware: 'Etiquetas importadas desde',
glCode: 'Código de Libro Mayor',
updateGLCodeFailureMessage: 'Se produjo un error al actualizar el código de Libro Mayor. Por favor, inténtelo nuevamente.',
tagRules: 'Reglas de etiquetas',
approverDescription: 'Aprobador',
Copy link
Contributor

Choose a reason for hiding this comment

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

NAB we could probably move this to the general since it is used twice.

},
taxes: {
subtitle: 'Añade nombres, tasas y establezca valores por defecto para los impuestos.',
Expand Down
7 changes: 7 additions & 0 deletions src/libs/API/parameters/SetPolicyTagApproverParams.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
type SetPolicyTagApproverParams = {
policyID: string;
tagName: string;
approver: string;
};

export default SetPolicyTagApproverParams;
1 change: 1 addition & 0 deletions src/libs/API/parameters/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -299,6 +299,7 @@ export type {default as EnablePolicyCompanyCardsParams} from './EnablePolicyComp
export type {default as ToggleCardContinuousReconciliationParams} from './ToggleCardContinuousReconciliationParams';
export type {default as CardDeactivateParams} from './CardDeactivateParams';
export type {default as UpdateExpensifyCardLimitTypeParams} from './UpdateExpensifyCardLimitTypeParams';
export type {default as SetPolicyTagApproverParams} from './SetPolicyTagApproverParams';
export type {default as UpdateQuickbooksOnlineAutoCreateVendorParams} from './UpdateQuickbooksOnlineAutoCreateVendorParams';
export type {default as ImportCategoriesSpreadsheetParams} from './ImportCategoriesSpreadsheet';
export type {default as UpdateXeroGenericTypeParams} from './UpdateXeroGenericTypeParams';
Expand Down
2 changes: 2 additions & 0 deletions src/libs/API/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -360,6 +360,7 @@ const WRITE_COMMANDS = {
CREATE_EXPENSIFY_CARD: 'CreateExpensifyCard',
CREATE_ADMIN_ISSUED_VIRTUAL_CARD: 'CreateAdminIssuedVirtualCard',
TOGGLE_CARD_CONTINUOUS_RECONCILIATION: 'ToggleCardContinuousReconciliation',
SET_POLICY_TAG_APPROVER: 'SetPolicyTagApprover',
UPDATE_CARD_SETTLEMENT_FREQUENCY: 'UpdateCardSettlementFrequency',
UPDATE_CARD_SETTLEMENT_ACCOUNT: 'UpdateCardSettlementAccount',
UPDATE_XERO_IMPORT_TRACKING_CATEGORIES: 'UpdateXeroImportTrackingCategories',
Expand Down Expand Up @@ -588,6 +589,7 @@ type WriteCommandParameters = {
[WRITE_COMMANDS.DECLINE_JOIN_REQUEST]: Parameters.DeclineJoinRequestParams;
[WRITE_COMMANDS.SET_POLICY_TAXES_CURRENCY_DEFAULT]: Parameters.SetPolicyCurrencyDefaultParams;
[WRITE_COMMANDS.SET_POLICY_CUSTOM_TAX_NAME]: Parameters.SetPolicyCustomTaxNameParams;
[WRITE_COMMANDS.SET_POLICY_TAG_APPROVER]: Parameters.SetPolicyTagApproverParams;
[WRITE_COMMANDS.SET_POLICY_TAXES_FOREIGN_CURRENCY_DEFAULT]: Parameters.SetPolicyForeignCurrencyDefaultParams;
[WRITE_COMMANDS.CREATE_POLICY_TAX]: Parameters.CreatePolicyTaxParams;
[WRITE_COMMANDS.SET_POLICY_TAXES_ENABLED]: Parameters.SetPolicyTaxesEnabledParams;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -263,6 +263,7 @@ const SettingsModalStackNavigator = createModalStackNavigator<SettingsNavigatorP
[SCREENS.WORKSPACE.TAGS_EDIT]: () => require<ReactComponentModule>('../../../../pages/workspace/tags/WorkspaceEditTagsPage').default,
[SCREENS.WORKSPACE.TAG_CREATE]: () => require<ReactComponentModule>('../../../../pages/workspace/tags/WorkspaceCreateTagPage').default,
[SCREENS.WORKSPACE.TAG_EDIT]: () => require<ReactComponentModule>('../../../../pages/workspace/tags/EditTagPage').default,
[SCREENS.WORKSPACE.TAG_APPROVER]: () => require<ReactComponentModule>('../../../../pages/workspace/tags/TagApproverPage').default,
[SCREENS.WORKSPACE.TAG_GL_CODE]: () => require<ReactComponentModule>('../../../../pages/workspace/tags/TagGLCodePage').default,
[SCREENS.WORKSPACE.TAXES_SETTINGS]: () => require<ReactComponentModule>('../../../../pages/workspace/taxes/WorkspaceTaxesSettingsPage').default,
[SCREENS.WORKSPACE.TAXES_SETTINGS_CUSTOM_TAX_NAME]: () => require<ReactComponentModule>('../../../../pages/workspace/taxes/WorkspaceTaxesSettingsCustomTaxName').default,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,7 @@ const FULL_SCREEN_TO_RHP_MAPPING: Partial<Record<FullScreenName, string[]>> = {
SCREENS.WORKSPACE.TAG_EDIT,
SCREENS.WORKSPACE.TAG_LIST_VIEW,
SCREENS.WORKSPACE.TAG_GL_CODE,
SCREENS.WORKSPACE.TAG_APPROVER,
],
[SCREENS.WORKSPACE.CATEGORIES]: [
SCREENS.WORKSPACE.CATEGORY_CREATE,
Expand Down
7 changes: 7 additions & 0 deletions src/libs/Navigation/linkingConfig/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -645,6 +645,13 @@ const config: LinkingOptions<RootStackParamList>['config'] = {
tagName: (tagName: string) => decodeURIComponent(tagName),
},
},
[SCREENS.WORKSPACE.TAG_APPROVER]: {
path: ROUTES.WORKSPACE_TAG_APPROVER.route,
parse: {
orderWeight: Number,
tagName: (tagName: string) => decodeURIComponent(tagName),
},
},
[SCREENS.WORKSPACE.TAG_GL_CODE]: {
path: ROUTES.WORKSPACE_TAG_GL_CODE.route,
parse: {
Expand Down
5 changes: 5 additions & 0 deletions src/libs/Navigation/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -273,6 +273,11 @@ type SettingsNavigatorParamList = {
orderWeight: number;
tagName: string;
};
[SCREENS.WORKSPACE.TAG_APPROVER]: {
policyID: string;
orderWeight: number;
tagName: string;
};
[SCREENS.WORKSPACE.TAG_GL_CODE]: {
policyID: string;
orderWeight: number;
Expand Down
10 changes: 10 additions & 0 deletions src/libs/PolicyUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -978,6 +978,15 @@ function getWorkspaceAccountID(policyID: string) {
return policy.workspaceAccountID ?? 0;
}

function getTagApproverRule(policyID: string, tagName: string) {
const policy = getPolicy(policyID);

const approvalRules = policy?.rules?.approvalRules ?? [];
const approverRule = approvalRules.find((rule) => rule.applyWhen.find(({condition, field, value}) => condition === 'matches' && field === 'tag' && value === tagName));
Copy link
Contributor

Choose a reason for hiding this comment

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

Same as mentioned before. Let's use tag and matches from CONST values.


return approverRule;
}

function getDomainNameForPolicy(policyID?: string): string {
if (!policyID) {
return '';
Expand Down Expand Up @@ -1096,6 +1105,7 @@ export {
getWorkspaceAccountID,
getAllTaxRatesNamesAndKeys as getAllTaxRates,
getTagNamesFromTagsLists,
getTagApproverRule,
getDomainNameForPolicy,
getWorkflowApprovalsUnavailable,
};
Expand Down
73 changes: 73 additions & 0 deletions src/libs/actions/Policy/Tag.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import type {
OpenPolicyTagsPageParams,
RenamePolicyTaglistParams,
RenamePolicyTagsParams,
SetPolicyTagApproverParams,
SetPolicyTagsEnabled,
SetPolicyTagsRequired,
UpdatePolicyTagGLCodeParams,
Expand Down Expand Up @@ -846,6 +847,77 @@ function setPolicyTagGLCode(policyID: string, tagName: string, tagListIndex: num
API.write(WRITE_COMMANDS.UPDATE_POLICY_TAG_GL_CODE, parameters, onyxData);
}

function setPolicyTagApprover(policyID: string, tag: string, approver: string) {
const policy = PolicyUtils.getPolicy(policyID);
const prevApprovalRules = policy?.rules?.approvalRules ?? [];
const approverRuleToUpdate = PolicyUtils.getTagApproverRule(policyID, tag);
const filteredApprovalRules = approverRuleToUpdate ? prevApprovalRules.filter((rule) => rule.id !== approverRuleToUpdate.id) : prevApprovalRules;

const updatedApproverRule = approverRuleToUpdate
? {...approverRuleToUpdate, approver}
: {
applyWhen: [
{
condition: 'matches',
Copy link
Contributor

Choose a reason for hiding this comment

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

NAB but we can add/use the keywords tag and matches using CONST values

field: 'tag',
value: tag,
},
],
approver,
id: '-1',
};

const onyxData: OnyxData = {
optimisticData: [
{
onyxMethod: Onyx.METHOD.MERGE,
key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`,
value: {
rules: {
approvalRules: [...filteredApprovalRules, updatedApproverRule],
},
pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE,
pendingFields: {rules: CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE},
},
},
],
successData: [
{
onyxMethod: Onyx.METHOD.MERGE,
key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`,
value: {
rules: {
approvalRules: [...filteredApprovalRules, updatedApproverRule],
},
pendingAction: null,
pendingFields: {rules: null},
},
},
],
failureData: [
{
onyxMethod: Onyx.METHOD.MERGE,
key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`,
value: {
rules: {
approvalRules: prevApprovalRules,
},
pendingAction: null,
pendingFields: {rules: null},
},
},
],
};

const parameters: SetPolicyTagApproverParams = {
policyID,
tagName: tag,
approver,
};

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

export {
buildOptimisticPolicyRecentlyUsedTags,
setPolicyRequiresTag,
Expand All @@ -861,6 +933,7 @@ export {
renamePolicyTaglist,
setWorkspaceTagEnabled,
setPolicyTagGLCode,
setPolicyTagApprover,
};

export type {NewCustomUnit};
Loading
Loading