feat: universal search permissions access control.

This commit is contained in:
a.bouhuolia
2021-11-27 17:22:29 +02:00
parent 56ab0a68e2
commit 3db00f6f70
14 changed files with 142 additions and 39 deletions

View File

@@ -23,6 +23,7 @@ import withDashboard from 'containers/Dashboard/withDashboard';
import QuickNewDropdown from 'containers/QuickNewDropdown/QuickNewDropdown'; import QuickNewDropdown from 'containers/QuickNewDropdown/QuickNewDropdown';
import { compose } from 'utils'; import { compose } from 'utils';
import withSubscriptions from '../../containers/Subscriptions/withSubscriptions'; import withSubscriptions from '../../containers/Subscriptions/withSubscriptions';
import { useGetUniversalSearchTypeOptions } from '../../containers/UniversalSearch/utils';
function DashboardTopbarSubscriptionMessage() { function DashboardTopbarSubscriptionMessage() {
return ( return (
@@ -142,11 +143,8 @@ function DashboardTopbar({
<Navbar class="dashboard__topbar-navbar"> <Navbar class="dashboard__topbar-navbar">
<NavbarGroup> <NavbarGroup>
<If condition={isSubscriptionActive}> <If condition={isSubscriptionActive}>
<Button <DashboardQuickSearchButton
onClick={() => openGlobalSearch(true)} onClick={() => openGlobalSearch(true)}
className={Classes.MINIMAL}
icon={<Icon icon={'search-24'} iconSize={20} />}
text={<T id={'quick_find'} />}
/> />
<QuickNewDropdown /> <QuickNewDropdown />
@@ -195,3 +193,23 @@ export default compose(
'main', 'main',
), ),
)(DashboardTopbar); )(DashboardTopbar);
/**
* Dashboard quick search button.
*/
function DashboardQuickSearchButton({ ...rest }) {
const searchTypeOptions = useGetUniversalSearchTypeOptions();
// Can't continue if there is no any search type option.
if (searchTypeOptions.length <= 0) {
return null;
}
return (
<Button
className={Classes.MINIMAL}
icon={<Icon icon={'search-24'} iconSize={20} />}
text={<T id={'quick_find'} />}
{...rest}
/>
);
}

View File

@@ -1,6 +1,10 @@
import intl from 'react-intl-universal'; import intl from 'react-intl-universal';
import { RESOURCES_TYPES } from 'common/resourcesTypes'; import { RESOURCES_TYPES } from 'common/resourcesTypes';
import withDrawerActions from '../Drawer/withDrawerActions'; import withDrawerActions from '../Drawer/withDrawerActions';
import {
AbilitySubject,
ManualJournalAction,
} from '../../common/abilityOption';
/** /**
* Universal search manual journal item select action. * Universal search manual journal item select action.
@@ -44,4 +48,8 @@ export const universalSearchJournalBind = () => ({
optionItemLabel: intl.get('manual_journals'), optionItemLabel: intl.get('manual_journals'),
selectItemAction: JournalUniversalSearchSelectAction, selectItemAction: JournalUniversalSearchSelectAction,
itemSelect: manualJournalsToSearch, itemSelect: manualJournalsToSearch,
permission: {
ability: ManualJournalAction.View,
subject: AbilitySubject.ManualJournal,
},
}); });

View File

@@ -1,7 +1,10 @@
import intl from 'react-intl-universal'; import intl from 'react-intl-universal';
import { RESOURCES_TYPES } from '../../common/resourcesTypes';
import withDrawerActions from '../Drawer/withDrawerActions'; import withDrawerActions from '../Drawer/withDrawerActions';
import { AbilitySubject, AccountAction } from '../../common/abilityOption';
import { RESOURCES_TYPES } from '../../common/resourcesTypes';
function AccountUniversalSearchItemSelectComponent({ function AccountUniversalSearchItemSelectComponent({
// #ownProps // #ownProps
resourceType, resourceType,
@@ -42,4 +45,8 @@ export const universalSearchAccountBind = () => ({
optionItemLabel: intl.get('accounts'), optionItemLabel: intl.get('accounts'),
selectItemAction: AccountUniversalSearchItemSelect, selectItemAction: AccountUniversalSearchItemSelect,
itemSelect: accountToSearch, itemSelect: accountToSearch,
permission: {
ability: AccountAction.View,
subject: AbilitySubject.Account,
},
}); });

View File

@@ -1,4 +1,5 @@
import intl from 'react-intl-universal'; import intl from 'react-intl-universal';
import { AbilitySubject, CustomerAction } from '../../common/abilityOption';
import { RESOURCES_TYPES } from '../../common/resourcesTypes'; import { RESOURCES_TYPES } from '../../common/resourcesTypes';
import withDrawerActions from '../Drawer/withDrawerActions'; import withDrawerActions from '../Drawer/withDrawerActions';
@@ -42,4 +43,8 @@ export const universalSearchCustomerBind = () => ({
optionItemLabel: intl.get('customers'), optionItemLabel: intl.get('customers'),
selectItemAction: CustomerUniversalSearchSelectAction, selectItemAction: CustomerUniversalSearchSelectAction,
itemSelect: customersToSearch, itemSelect: customersToSearch,
permission: {
ability: CustomerAction.View,
subject: AbilitySubject.Customer,
},
}); });

View File

@@ -1,7 +1,10 @@
import intl from 'react-intl-universal'; import intl from 'react-intl-universal';
import { RESOURCES_TYPES } from '../../common/resourcesTypes';
import withDrawerActions from '../Drawer/withDrawerActions'; import withDrawerActions from '../Drawer/withDrawerActions';
import { RESOURCES_TYPES } from '../../common/resourcesTypes';
import { AbilitySubject, ItemAction } from '../../common/abilityOption';
/** /**
* Item univrsal search item select action. * Item univrsal search item select action.
*/ */
@@ -46,4 +49,8 @@ export const universalSearchItemBind = () => ({
optionItemLabel: intl.get('items'), optionItemLabel: intl.get('items'),
selectItemAction: ItemUniversalSearchSelectAction, selectItemAction: ItemUniversalSearchSelectAction,
itemSelect: transfromItemsToSearch, itemSelect: transfromItemsToSearch,
permission: {
ability: ItemAction.View,
subject: AbilitySubject.Item,
},
}); });

View File

@@ -7,6 +7,7 @@ import { T, Icon, Choose, If } from 'components';
import { RESOURCES_TYPES } from 'common/resourcesTypes'; import { RESOURCES_TYPES } from 'common/resourcesTypes';
import withDrawerActions from '../../Drawer/withDrawerActions'; import withDrawerActions from '../../Drawer/withDrawerActions';
import { AbilitySubject, BillAction } from '../../../common/abilityOption';
/** /**
* Universal search bill item select action. * Universal search bill item select action.
@@ -116,4 +117,8 @@ export const universalSearchBillBind = () => ({
selectItemAction: BillUniversalSearchSelect, selectItemAction: BillUniversalSearchSelect,
itemRenderer: BillUniversalSearchItem, itemRenderer: BillUniversalSearchItem,
itemSelect: billsToSearch, itemSelect: billsToSearch,
permission: {
ability: BillAction.View,
subject: AbilitySubject.Bill,
},
}); });

View File

@@ -1,14 +1,14 @@
import React from 'react'; import React from 'react';
import { MenuItem } from '@blueprintjs/core'; import { MenuItem } from '@blueprintjs/core';
import intl from 'react-intl-universal'; import intl from 'react-intl-universal';
import { isEmpty } from 'lodash';
import { Icon, If } from 'components'; import { Icon } from 'components';
import { RESOURCES_TYPES } from 'common/resourcesTypes'; import { RESOURCES_TYPES } from 'common/resourcesTypes';
import withDrawerActions from '../../Drawer/withDrawerActions'; import withDrawerActions from '../../Drawer/withDrawerActions';
import { highlightText } from 'utils'; import { highlightText } from 'utils';
import { AbilitySubject, PaymentMadeAction } from '../../../common/abilityOption';
/** /**
* Universal search bill item select action. * Universal search bill item select action.
@@ -82,4 +82,8 @@ export const universalSearchPaymentMadeBind = () => ({
selectItemAction: PaymentMadeUniversalSearchSelect, selectItemAction: PaymentMadeUniversalSearchSelect,
itemRenderer: PaymentMadeUniversalSearchItem, itemRenderer: PaymentMadeUniversalSearchItem,
itemSelect: paymentMadeToSearch, itemSelect: paymentMadeToSearch,
permission: {
ability: PaymentMadeAction.View,
subject: AbilitySubject.PaymentMade,
},
}); });

View File

@@ -5,6 +5,8 @@ import intl from 'react-intl-universal';
import { Choose, T, Icon } from 'components'; import { Choose, T, Icon } from 'components';
import { RESOURCES_TYPES } from "common/resourcesTypes"; import { RESOURCES_TYPES } from "common/resourcesTypes";
import { AbilitySubject, SaleEstimateAction } from '../../../../common/abilityOption';
import withDrawerActions from "../../../Drawer/withDrawerActions"; import withDrawerActions from "../../../Drawer/withDrawerActions";
/** /**
@@ -110,5 +112,9 @@ export const universalSearchEstimateBind = () => ({
optionItemLabel: intl.get('estimates'), optionItemLabel: intl.get('estimates'),
selectItemAction: EstimateUniversalSearchSelect, selectItemAction: EstimateUniversalSearchSelect,
itemRenderer: EstimateUniversalSearchItem, itemRenderer: EstimateUniversalSearchItem,
itemSelect: transformEstimatesToSearch itemSelect: transformEstimatesToSearch,
permission: {
ability: SaleEstimateAction.View,
subject: AbilitySubject.Estimate,
},
}); });

View File

@@ -3,10 +3,13 @@ import intl from 'react-intl-universal';
import { MenuItem } from '@blueprintjs/core'; import { MenuItem } from '@blueprintjs/core';
import { T, Choose, Icon } from 'components'; import { T, Choose, Icon } from 'components';
import { highlightText } from 'utils'; import { highlightText } from 'utils';
import { RESOURCES_TYPES } from 'common/resourcesTypes'; import { RESOURCES_TYPES } from 'common/resourcesTypes';
import {
AbilitySubject,
SaleInvoiceAction,
} from '../../../common/abilityOption';
import withDrawerActions from '../../Drawer/withDrawerActions'; import withDrawerActions from '../../Drawer/withDrawerActions';
/** /**
@@ -118,4 +121,8 @@ export const universalSearchInvoiceBind = () => ({
selectItemAction: InvoiceUniversalSearchSelect, selectItemAction: InvoiceUniversalSearchSelect,
itemRenderer: InvoiceUniversalSearchItem, itemRenderer: InvoiceUniversalSearchItem,
itemSelect: transformInvoicesToSearch, itemSelect: transformInvoicesToSearch,
permission: {
ability: SaleInvoiceAction.View,
subject: AbilitySubject.Invoice,
},
}); });

View File

@@ -2,10 +2,14 @@ import React from 'react';
import { MenuItem } from '@blueprintjs/core'; import { MenuItem } from '@blueprintjs/core';
import intl from 'react-intl-universal'; import intl from 'react-intl-universal';
import { RESOURCES_TYPES } from "../../../common/resourcesTypes"; import { RESOURCES_TYPES } from '../../../common/resourcesTypes';
import withDrawerActions from "../../Drawer/withDrawerActions"; import withDrawerActions from '../../Drawer/withDrawerActions';
import { highlightText } from 'utils'; import { highlightText } from 'utils';
import { Icon } from 'components'; import { Icon } from 'components';
import {
AbilitySubject,
PaymentReceiveAction,
} from '../../../common/abilityOption';
/** /**
* Payment receive universal search item select action. * Payment receive universal search item select action.
@@ -19,7 +23,9 @@ function PaymentReceiveUniversalSearchSelectComponent({
openDrawer, openDrawer,
}) { }) {
if (resourceType === RESOURCES_TYPES.PAYMENT_RECEIVE) { if (resourceType === RESOURCES_TYPES.PAYMENT_RECEIVE) {
openDrawer('payment-receive-detail-drawer', { paymentReceiveId: resourceId }); openDrawer('payment-receive-detail-drawer', {
paymentReceiveId: resourceId,
});
} }
return null; return null;
} }
@@ -59,7 +65,7 @@ export function PaymentReceiveUniversalSearchItem(
/** /**
* Transformes payment receives to search. * Transformes payment receives to search.
*/ */
const paymentReceivesToSearch = (payment) => ({ const paymentReceivesToSearch = (payment) => ({
id: payment.id, id: payment.id,
text: payment.customer.display_name, text: payment.customer.display_name,
subText: payment.formatted_payment_date, subText: payment.formatted_payment_date,
@@ -70,10 +76,14 @@ export function PaymentReceiveUniversalSearchItem(
/** /**
* Binds universal search payment receive configure. * Binds universal search payment receive configure.
*/ */
export const universalSearchPaymentReceiveBind = () => ({ export const universalSearchPaymentReceiveBind = () => ({
resourceType: RESOURCES_TYPES.PAYMENT_RECEIVE, resourceType: RESOURCES_TYPES.PAYMENT_RECEIVE,
optionItemLabel: intl.get('payment_receives'), optionItemLabel: intl.get('payment_receives'),
selectItemAction: PaymentReceiveUniversalSearchSelect, selectItemAction: PaymentReceiveUniversalSearchSelect,
itemRenderer: PaymentReceiveUniversalSearchItem, itemRenderer: PaymentReceiveUniversalSearchItem,
itemSelect: paymentReceivesToSearch, itemSelect: paymentReceivesToSearch,
permission: {
ability: PaymentReceiveAction.View,
subject: AbilitySubject.PaymentReceive,
},
}); });

View File

@@ -1,13 +1,15 @@
import React from 'react'; import React from 'react';
import intl from 'react-intl-universal'; import intl from 'react-intl-universal';
import { MenuItem } from '@blueprintjs/core'; import { MenuItem } from '@blueprintjs/core';
import { Icon, Choose, T } from 'components'; import { Icon, Choose, T } from 'components';
import { RESOURCES_TYPES } from "../../../common/resourcesTypes"; import { RESOURCES_TYPES } from '../../../common/resourcesTypes';
import withDrawerActions from "../../Drawer/withDrawerActions"; import withDrawerActions from '../../Drawer/withDrawerActions';
import {
AbilitySubject,
SaleReceiptAction,
} from '../../../common/abilityOption';
/** /**
* Receipt universal search item select action. * Receipt universal search item select action.
@@ -39,11 +41,15 @@ function ReceiptStatus({ receipt }) {
return ( return (
<Choose> <Choose>
<Choose.When condition={receipt.is_closed}> <Choose.When condition={receipt.is_closed}>
<span class="closed"><T id={'closed'} /></span> <span class="closed">
<T id={'closed'} />
</span>
</Choose.When> </Choose.When>
<Choose.Otherwise> <Choose.Otherwise>
<span class="draft"><T id={'draft'} /></span> <span class="draft">
<T id={'draft'} />
</span>
</Choose.Otherwise> </Choose.Otherwise>
</Choose> </Choose>
); );
@@ -100,4 +106,8 @@ export const universalSearchReceiptBind = () => ({
selectItemAction: ReceiptUniversalSearchSelect, selectItemAction: ReceiptUniversalSearchSelect,
itemRenderer: ReceiptUniversalSearchItem, itemRenderer: ReceiptUniversalSearchItem,
itemSelect: transformReceiptsToSearch, itemSelect: transformReceiptsToSearch,
permission: {
ability: SaleReceiptAction.View,
subject: AbilitySubject.Receipt,
},
}); });

View File

@@ -14,7 +14,7 @@ import DashboardUniversalSearchItemActions from './DashboardUniversalSearchItemA
import { DashboardUniversalSearchItem } from './components'; import { DashboardUniversalSearchItem } from './components';
import DashboardUniversalSearchHotkeys from './DashboardUniversalSearchHotkeys'; import DashboardUniversalSearchHotkeys from './DashboardUniversalSearchHotkeys';
import { getUniversalSearchTypeOptions } from './utils'; import { useGetUniversalSearchTypeOptions } from './utils';
/** /**
* Dashboard universal search. * Dashboard universal search.
@@ -28,6 +28,8 @@ function DashboardUniversalSearch({
closeGlobalSearch, closeGlobalSearch,
defaultUniversalResourceType, defaultUniversalResourceType,
}) { }) {
const searchTypeOptions = useGetUniversalSearchTypeOptions();
// Search keyword. // Search keyword.
const [searchKeyword, setSearchKeyword] = React.useState(''); const [searchKeyword, setSearchKeyword] = React.useState('');
@@ -97,10 +99,9 @@ function DashboardUniversalSearch({
setSearchKeyword(''); setSearchKeyword('');
}; };
const searchTypeOptions = React.useMemo( if (searchTypeOptions.length === 0) {
() => getUniversalSearchTypeOptions(), return null;
[], }
);
return ( return (
<div class="dashboard__universal-search"> <div class="dashboard__universal-search">

View File

@@ -1,5 +1,9 @@
import { get } from 'lodash'; import { get } from 'lodash';
import * as R from 'ramda';
import React from 'react';
import { universalSearchBinds } from './DashboardUniversalSearchBinds'; import { universalSearchBinds } from './DashboardUniversalSearchBinds';
import { useAbilitiesFilter } from '../../hooks/utils';
/** /**
* *
@@ -23,22 +27,28 @@ export const getUniversalSearchBind = (resourceType, key) => {
}; };
/** /**
* * Retrieve universal search type options.
* @returns
*/ */
export const getUniversalSearchTypeOptions = () => { export const useGetUniversalSearchTypeOptions = () => {
return getUniversalSearchBinds().map((bind) => ({ const abilityFilter = useAbilitiesFilter();
const momerizedBinds = React.useMemo(() => {
const filteredBinds = R.compose(abilityFilter, getUniversalSearchBinds)();
return filteredBinds.map((bind) => ({
key: bind.resourceType, key: bind.resourceType,
label: bind.optionItemLabel, label: bind.optionItemLabel,
})) }));
} }, [abilityFilter]);
return momerizedBinds;
};
/** /**
* * Retrieve universal search types actions.
* @returns
*/ */
export const getUniversalSearchItemsActions = () => { export const getUniversalSearchItemsActions = () => {
return getUniversalSearchBinds() return getUniversalSearchBinds()
.filter((bind) => bind.selectItemAction) .filter((bind) => bind.selectItemAction)
.map((bind) => bind.selectItemAction); .map((bind) => bind.selectItemAction);
} };

View File

@@ -1,6 +1,7 @@
import intl from 'react-intl-universal'; import intl from 'react-intl-universal';
import { RESOURCES_TYPES } from '../../common/resourcesTypes'; import { RESOURCES_TYPES } from '../../common/resourcesTypes';
import { AbilitySubject, VendorAction } from '../../common/abilityOption';
import withDrawerActions from '../Drawer/withDrawerActions'; import withDrawerActions from '../Drawer/withDrawerActions';
/** /**
@@ -43,4 +44,8 @@ export const universalSearchVendorBind = () => ({
optionItemLabel: intl.get('vendors'), optionItemLabel: intl.get('vendors'),
selectItemAction: VendorUniversalSearchSelectAction, selectItemAction: VendorUniversalSearchSelectAction,
itemSelect: vendorToSearch, itemSelect: vendorToSearch,
permission: {
ability: VendorAction.View,
subject: AbilitySubject.Vendor,
},
}); });