From b49555e111ae98d31be41e805e3db0c46b7c4f06 Mon Sep 17 00:00:00 2001 From: Patrick Fic <> Date: Wed, 23 Jun 2021 14:15:41 -0700 Subject: [PATCH] IO-1211 Feature Restrictions --- bodyshop_translations.babel | 21 +++++ .../feature-wrapper.component.jsx | 50 +++++++++++ ...bs-detail-header-actions.csi.component.jsx | 3 + client/src/graphql/bodyshop.queries.js | 1 + .../pages/dashboard/dashboard.container.jsx | 9 +- .../production-board.container.jsx | 9 +- .../scoreboard/scoreboard.page.container.jsx | 9 +- client/src/pages/tech/tech.page.component.jsx | 78 +++++++++-------- client/src/pages/tech/tech.page.container.jsx | 12 ++- client/src/translations/en_us/common.json | 1 + client/src/translations/es/common.json | 1 + client/src/translations/fr/common.json | 1 + .../down.yaml | 12 +++ .../up.yaml | 20 +++++ .../down.yaml | 5 ++ .../up.yaml | 6 ++ .../down.yaml | 86 ++++++++++++++++++ .../up.yaml | 87 +++++++++++++++++++ hasura/migrations/metadata.yaml | 11 +++ 19 files changed, 373 insertions(+), 49 deletions(-) create mode 100644 client/src/components/feature-wrapper/feature-wrapper.component.jsx create mode 100644 hasura/migrations/1624480412524_track_all_relationships/down.yaml create mode 100644 hasura/migrations/1624480412524_track_all_relationships/up.yaml create mode 100644 hasura/migrations/1624480467391_alter_table_public_bodyshops_add_column_features/down.yaml create mode 100644 hasura/migrations/1624480467391_alter_table_public_bodyshops_add_column_features/up.yaml create mode 100644 hasura/migrations/1624480496210_update_permission_user_public_table_bodyshops/down.yaml create mode 100644 hasura/migrations/1624480496210_update_permission_user_public_table_bodyshops/up.yaml diff --git a/bodyshop_translations.babel b/bodyshop_translations.babel index 73f7047a6..7ca06523b 100644 --- a/bodyshop_translations.babel +++ b/bodyshop_translations.babel @@ -14310,6 +14310,27 @@ + + nofeatureaccess + false + + + + + + en-US + false + + + es-MX + false + + + fr-CA + false + + + noshop false diff --git a/client/src/components/feature-wrapper/feature-wrapper.component.jsx b/client/src/components/feature-wrapper/feature-wrapper.component.jsx new file mode 100644 index 000000000..af609ac1f --- /dev/null +++ b/client/src/components/feature-wrapper/feature-wrapper.component.jsx @@ -0,0 +1,50 @@ +import moment from "moment"; +import React from "react"; +import { useTranslation } from "react-i18next"; +import { connect } from "react-redux"; +import { createStructuredSelector } from "reselect"; +import { selectBodyshop } from "../../redux/user/user.selectors"; +import AlertComponent from "../alert/alert.component"; + +const mapStateToProps = createStructuredSelector({ + bodyshop: selectBodyshop, +}); + +function FeatureWrapper({ + bodyshop, + featureName, + noauth, + children, + ...restProps +}) { + const { t } = useTranslation(); + + if (HasFeatureAccess({ featureName, bodyshop })) return children; + + return ( + noauth || ( + + ) + ); +} + +export function HasFeatureAccess({ featureName, bodyshop }) { + return ( + bodyshop.features.allAccess || + moment(bodyshop.features[featureName]).isAfter(moment()) + ); +} + +export default connect(mapStateToProps, null)(FeatureWrapper); + +/* +dashboard +production-board +scoreboard +csi +tech-console +mobile-imaging +*/ diff --git a/client/src/components/jobs-detail-header-actions/jobs-detail-header-actions.csi.component.jsx b/client/src/components/jobs-detail-header-actions/jobs-detail-header-actions.csi.component.jsx index 9cecb5d22..c205e3088 100644 --- a/client/src/components/jobs-detail-header-actions/jobs-detail-header-actions.csi.component.jsx +++ b/client/src/components/jobs-detail-header-actions/jobs-detail-header-actions.csi.component.jsx @@ -19,6 +19,7 @@ import { import { selectBodyshop } from "../../redux/user/user.selectors"; import { DateTimeFormatter } from "../../utils/DateFormatter"; import { TemplateList } from "../../utils/TemplateConstants"; +import { HasFeatureAccess } from "../feature-wrapper/feature-wrapper.component"; const mapStateToProps = createStructuredSelector({ //currentUser: selectCurrentUser' @@ -179,6 +180,8 @@ export function JobsDetailHeaderCsi({ } }; + if (!HasFeatureAccess({ featureName: "csi", bodyshop })) return <>; + return ( ({ setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)), @@ -28,9 +29,11 @@ export function ExportsLogPageContainer({ setBreadcrumbs, setSelectedHeader }) { }, [setBreadcrumbs, t, setSelectedHeader]); return ( - - - + + + + + ); } export default connect(null, mapDispatchToProps)(ExportsLogPageContainer); diff --git a/client/src/pages/production-board/production-board.container.jsx b/client/src/pages/production-board/production-board.container.jsx index c99a9f26a..20fc35d51 100644 --- a/client/src/pages/production-board/production-board.container.jsx +++ b/client/src/pages/production-board/production-board.container.jsx @@ -9,6 +9,7 @@ import { } from "../../redux/application/application.actions"; import { selectBodyshop } from "../../redux/user/user.selectors"; import ProductionBoardComponent from "./production-board.component"; +import FeatureWrapper from "../../components/feature-wrapper/feature-wrapper.component"; const mapStateToProps = createStructuredSelector({ bodyshop: selectBodyshop, @@ -38,9 +39,11 @@ export function ProductionBoardContainer({ }, [t, setBreadcrumbs, setSelectedHeader]); return ( - - - + + + + + ); } export default connect( diff --git a/client/src/pages/scoreboard/scoreboard.page.container.jsx b/client/src/pages/scoreboard/scoreboard.page.container.jsx index 3f17416aa..2ded23396 100644 --- a/client/src/pages/scoreboard/scoreboard.page.container.jsx +++ b/client/src/pages/scoreboard/scoreboard.page.container.jsx @@ -12,6 +12,7 @@ import { useSubscription } from "@apollo/client"; import { SUBSCRIPTION_SCOREBOARD } from "../../graphql/scoreboard.queries"; import moment from "moment"; import RbacWrapper from "../../components/rbac-wrapper/rbac-wrapper.component"; +import FeatureWrapper from "../../components/feature-wrapper/feature-wrapper.component"; const mapStateToProps = createStructuredSelector({ bodyshop: selectBodyshop, @@ -44,9 +45,11 @@ export function ScoreboardContainer({ setBreadcrumbs, setSelectedHeader }) { }, [t, setBreadcrumbs, setSelectedHeader]); return ( - - - + + + + + ); } export default connect( diff --git a/client/src/pages/tech/tech.page.component.jsx b/client/src/pages/tech/tech.page.component.jsx index 95c5c8138..ef00da57e 100644 --- a/client/src/pages/tech/tech.page.component.jsx +++ b/client/src/pages/tech/tech.page.component.jsx @@ -10,6 +10,7 @@ import LoadingSpinner from "../../components/loading-spinner/loading-spinner.com import TechHeader from "../../components/tech-header/tech-header.component"; import TechSider from "../../components/tech-sider/tech-sider.component"; import { selectTechnician } from "../../redux/tech/tech.selectors"; +import FeatureWrapper from "../../components/feature-wrapper/feature-wrapper.component"; import "./tech.page.styles.scss"; const TimeTicketModalContainer = lazy(() => import("../../components/time-ticket-modal/time-ticket-modal.container") @@ -51,52 +52,55 @@ export function TechPage({ technician, match }) { }, [t]); return ( - + {technician ? null : } - + - }> - - - - - - - - - - + } + > + + + + + + + + + + + + diff --git a/client/src/pages/tech/tech.page.container.jsx b/client/src/pages/tech/tech.page.container.jsx index de48e041d..5fb547cd4 100644 --- a/client/src/pages/tech/tech.page.container.jsx +++ b/client/src/pages/tech/tech.page.container.jsx @@ -7,12 +7,18 @@ import { setBodyshop } from "../../redux/user/user.actions"; import TechPage from "./tech.page.component"; import LoadingSpinner from "../../components/loading-spinner/loading-spinner.component"; import { useTranslation } from "react-i18next"; +import { selectBodyshop } from "../../redux/user/user.selectors"; +import { createStructuredSelector } from "reselect"; + +const mapStateToProps = createStructuredSelector({ + bodyshop: selectBodyshop, +}); const mapDispatchToProps = (dispatch) => ({ setBodyshop: (bs) => dispatch(setBodyshop(bs)), }); -export function TechPageContainer({ setBodyshop, match }) { +export function TechPageContainer({ bodyshop, setBodyshop, match }) { const { loading, error, data } = useQuery(QUERY_BODYSHOP, { fetchPolicy: "network-only", }); @@ -21,10 +27,10 @@ export function TechPageContainer({ setBodyshop, match }) { if (data) setBodyshop(data.bodyshops[0]); }, [data, setBodyshop]); - if (loading) + if (loading || !bodyshop) return ; if (error) return ; return ; } -export default connect(null, mapDispatchToProps)(TechPageContainer); +export default connect(mapStateToProps, mapDispatchToProps)(TechPageContainer); diff --git a/client/src/translations/en_us/common.json b/client/src/translations/en_us/common.json index d0cd83a08..2c4ad3f22 100644 --- a/client/src/translations/en_us/common.json +++ b/client/src/translations/en_us/common.json @@ -899,6 +899,7 @@ "newversionmessage": "Click refresh below to update to the latest available version of ImEX Online. Please make sure all other tabs and windows are closed.", "newversiontitle": "New version of ImEX Online Available", "noacctfilepath": "There is no accounting file path set. You will not be able to export any items.", + "nofeatureaccess": "You do not have access to this feature of ImEX Online. Please contact support to request a license for this feature.", "noshop": "You do not have access to any shops. Please reach out to your shop manager or technical support. ", "notfoundsub": "Please make sure that you have access to the data or that the link is correct.", "notfoundtitle": "We couldn't find what you're looking for...", diff --git a/client/src/translations/es/common.json b/client/src/translations/es/common.json index 223b5772f..d2004c724 100644 --- a/client/src/translations/es/common.json +++ b/client/src/translations/es/common.json @@ -899,6 +899,7 @@ "newversionmessage": "", "newversiontitle": "", "noacctfilepath": "", + "nofeatureaccess": "", "noshop": "", "notfoundsub": "", "notfoundtitle": "", diff --git a/client/src/translations/fr/common.json b/client/src/translations/fr/common.json index 08efbf140..16733c621 100644 --- a/client/src/translations/fr/common.json +++ b/client/src/translations/fr/common.json @@ -899,6 +899,7 @@ "newversionmessage": "", "newversiontitle": "", "noacctfilepath": "", + "nofeatureaccess": "", "noshop": "", "notfoundsub": "", "notfoundtitle": "", diff --git a/hasura/migrations/1624480412524_track_all_relationships/down.yaml b/hasura/migrations/1624480412524_track_all_relationships/down.yaml new file mode 100644 index 000000000..af1251f44 --- /dev/null +++ b/hasura/migrations/1624480412524_track_all_relationships/down.yaml @@ -0,0 +1,12 @@ +- args: + relationship: partsOrdersByOrderedby + table: + name: users + schema: public + type: drop_relationship +- args: + relationship: userByOrderedby + table: + name: parts_orders + schema: public + type: drop_relationship diff --git a/hasura/migrations/1624480412524_track_all_relationships/up.yaml b/hasura/migrations/1624480412524_track_all_relationships/up.yaml new file mode 100644 index 000000000..b03f180ad --- /dev/null +++ b/hasura/migrations/1624480412524_track_all_relationships/up.yaml @@ -0,0 +1,20 @@ +- args: + name: partsOrdersByOrderedby + table: + name: users + schema: public + using: + foreign_key_constraint_on: + column: orderedby + table: + name: parts_orders + schema: public + type: create_array_relationship +- args: + name: userByOrderedby + table: + name: parts_orders + schema: public + using: + foreign_key_constraint_on: orderedby + type: create_object_relationship diff --git a/hasura/migrations/1624480467391_alter_table_public_bodyshops_add_column_features/down.yaml b/hasura/migrations/1624480467391_alter_table_public_bodyshops_add_column_features/down.yaml new file mode 100644 index 000000000..d53fc1675 --- /dev/null +++ b/hasura/migrations/1624480467391_alter_table_public_bodyshops_add_column_features/down.yaml @@ -0,0 +1,5 @@ +- args: + cascade: false + read_only: false + sql: ALTER TABLE "public"."bodyshops" DROP COLUMN "features"; + type: run_sql diff --git a/hasura/migrations/1624480467391_alter_table_public_bodyshops_add_column_features/up.yaml b/hasura/migrations/1624480467391_alter_table_public_bodyshops_add_column_features/up.yaml new file mode 100644 index 000000000..68869f6a8 --- /dev/null +++ b/hasura/migrations/1624480467391_alter_table_public_bodyshops_add_column_features/up.yaml @@ -0,0 +1,6 @@ +- args: + cascade: false + read_only: false + sql: ALTER TABLE "public"."bodyshops" ADD COLUMN "features" jsonb NULL DEFAULT + jsonb_build_object(); + type: run_sql diff --git a/hasura/migrations/1624480496210_update_permission_user_public_table_bodyshops/down.yaml b/hasura/migrations/1624480496210_update_permission_user_public_table_bodyshops/down.yaml new file mode 100644 index 000000000..330d7d1af --- /dev/null +++ b/hasura/migrations/1624480496210_update_permission_user_public_table_bodyshops/down.yaml @@ -0,0 +1,86 @@ +- args: + role: user + table: + name: bodyshops + schema: public + type: drop_select_permission +- args: + permission: + allow_aggregations: false + columns: + - accountingconfig + - address1 + - address2 + - appt_alt_transport + - appt_colors + - appt_length + - bill_tax_rates + - cdk_dealerid + - city + - country + - created_at + - default_adjustment_rate + - deliverchecklist + - email + - enforce_class + - enforce_referral + - federal_tax_id + - id + - imexshopid + - inhousevendorid + - insurance_vendor_id + - intakechecklist + - jc_hourly_rates + - jobsizelimit + - logo_img_path + - md_categories + - md_ccc_rates + - md_classes + - md_hour_split + - md_ins_cos + - md_jobline_presets + - md_labor_rates + - md_messaging_presets + - md_notes_presets + - md_order_statuses + - md_parts_locations + - md_payment_types + - md_rbac + - md_referral_sources + - md_responsibility_centers + - md_ro_statuses + - messagingservicesid + - phone + - prodtargethrs + - production_config + - region_config + - schedule_end_time + - schedule_start_time + - scoreboard_target + - shopname + - shoprates + - speedprint + - ssbuckets + - state + - state_tax_id + - stripe_acct_id + - sub_status + - target_touchtime + - template_header + - textid + - updated_at + - use_fippa + - website + - workingdays + - zip_post + computed_fields: [] + filter: + associations: + user: + authid: + _eq: X-Hasura-User-Id + role: user + table: + name: bodyshops + schema: public + type: create_select_permission diff --git a/hasura/migrations/1624480496210_update_permission_user_public_table_bodyshops/up.yaml b/hasura/migrations/1624480496210_update_permission_user_public_table_bodyshops/up.yaml new file mode 100644 index 000000000..24984ef56 --- /dev/null +++ b/hasura/migrations/1624480496210_update_permission_user_public_table_bodyshops/up.yaml @@ -0,0 +1,87 @@ +- args: + role: user + table: + name: bodyshops + schema: public + type: drop_select_permission +- args: + permission: + allow_aggregations: false + columns: + - accountingconfig + - address1 + - address2 + - appt_alt_transport + - appt_colors + - appt_length + - bill_tax_rates + - cdk_dealerid + - city + - country + - created_at + - default_adjustment_rate + - deliverchecklist + - email + - enforce_class + - enforce_referral + - features + - federal_tax_id + - id + - imexshopid + - inhousevendorid + - insurance_vendor_id + - intakechecklist + - jc_hourly_rates + - jobsizelimit + - logo_img_path + - md_categories + - md_ccc_rates + - md_classes + - md_hour_split + - md_ins_cos + - md_jobline_presets + - md_labor_rates + - md_messaging_presets + - md_notes_presets + - md_order_statuses + - md_parts_locations + - md_payment_types + - md_rbac + - md_referral_sources + - md_responsibility_centers + - md_ro_statuses + - messagingservicesid + - phone + - prodtargethrs + - production_config + - region_config + - schedule_end_time + - schedule_start_time + - scoreboard_target + - shopname + - shoprates + - speedprint + - ssbuckets + - state + - state_tax_id + - stripe_acct_id + - sub_status + - target_touchtime + - template_header + - textid + - updated_at + - use_fippa + - website + - workingdays + - zip_post + computed_fields: [] + filter: + associations: + user: + authid: + _eq: X-Hasura-User-Id + role: user + table: + name: bodyshops + schema: public + type: create_select_permission diff --git a/hasura/migrations/metadata.yaml b/hasura/migrations/metadata.yaml index 3a4f43614..3481eec42 100644 --- a/hasura/migrations/metadata.yaml +++ b/hasura/migrations/metadata.yaml @@ -766,6 +766,7 @@ tables: - email - enforce_class - enforce_referral + - features - federal_tax_id - id - imexshopid @@ -3596,6 +3597,9 @@ tables: - name: user using: foreign_key_constraint_on: user_email + - name: userByOrderedby + using: + foreign_key_constraint_on: orderedby - name: vendor using: foreign_key_constraint_on: vendorid @@ -4172,6 +4176,13 @@ tables: table: schema: public name: parts_orders + - name: partsOrdersByOrderedby + using: + foreign_key_constraint_on: + column: orderedby + table: + schema: public + name: parts_orders insert_permissions: - role: user permission: