From e2bc2f2067fcde27cee0540276521f4c9e360700 Mon Sep 17 00:00:00 2001 From: Tom Moor Date: Sat, 13 May 2023 12:30:24 -0400 Subject: [PATCH] Transfer changes from enterprise codebase --- app/hooks/useSettingsConfig.ts | 97 ++++++++++------------ app/menus/CollectionMenu.tsx | 2 +- app/stores/CollectionsStore.ts | 19 ++++- app/utils/PluginLoader.ts | 1 + server/policies/collection.ts | 2 +- server/routes/api/collections.ts | 2 +- shared/i18n/locales/en_US/translation.json | 2 +- 7 files changed, 65 insertions(+), 60 deletions(-) diff --git a/app/hooks/useSettingsConfig.ts b/app/hooks/useSettingsConfig.ts index b1553ef25..c82e47369 100644 --- a/app/hooks/useSettingsConfig.ts +++ b/app/hooks/useSettingsConfig.ts @@ -1,4 +1,3 @@ -import { mapValues } from "lodash"; import { EmailIcon, ProfileIcon, @@ -40,19 +39,13 @@ import { accountPreferencesPath } from "~/utils/routeHelpers"; import useCurrentTeam from "./useCurrentTeam"; import usePolicy from "./usePolicy"; -type SettingsGroups = "Account" | "Workspace" | "Integrations"; - export type ConfigItem = { name: string; path: string; icon: React.FC; - component: () => JSX.Element; + component: React.ComponentType; enabled: boolean; - group: SettingsGroups; -}; - -type ConfigType = { - [key in string]: ConfigItem; + group: string; }; const useSettingsConfig = () => { @@ -60,9 +53,9 @@ const useSettingsConfig = () => { const can = usePolicy(team); const { t } = useTranslation(); - const config: ConfigType = React.useMemo( - () => ({ - Profile: { + const config = React.useMemo(() => { + const items: ConfigItem[] = [ + { name: t("Profile"), path: "/settings", component: Profile, @@ -70,7 +63,7 @@ const useSettingsConfig = () => { group: t("Account"), icon: ProfileIcon, }, - Preferences: { + { name: t("Preferences"), path: accountPreferencesPath(), component: Preferences, @@ -78,7 +71,7 @@ const useSettingsConfig = () => { group: t("Account"), icon: SettingsIcon, }, - Notifications: { + { name: t("Notifications"), path: "/settings/notifications", component: Notifications, @@ -86,7 +79,7 @@ const useSettingsConfig = () => { group: t("Account"), icon: EmailIcon, }, - Api: { + { name: t("API Tokens"), path: "/settings/tokens", component: ApiKeys, @@ -95,7 +88,7 @@ const useSettingsConfig = () => { icon: CodeIcon, }, // Team group - Details: { + { name: t("Details"), path: "/settings/details", component: Details, @@ -103,7 +96,7 @@ const useSettingsConfig = () => { group: t("Workspace"), icon: TeamIcon, }, - Security: { + { name: t("Security"), path: "/settings/security", component: Security, @@ -111,7 +104,7 @@ const useSettingsConfig = () => { group: t("Workspace"), icon: PadlockIcon, }, - Features: { + { name: t("Features"), path: "/settings/features", component: Features, @@ -119,7 +112,7 @@ const useSettingsConfig = () => { group: t("Workspace"), icon: BeakerIcon, }, - Members: { + { name: t("Members"), path: "/settings/members", component: Members, @@ -127,7 +120,7 @@ const useSettingsConfig = () => { group: t("Workspace"), icon: UserIcon, }, - Groups: { + { name: t("Groups"), path: "/settings/groups", component: Groups, @@ -135,7 +128,7 @@ const useSettingsConfig = () => { group: t("Workspace"), icon: GroupIcon, }, - Shares: { + { name: t("Shared Links"), path: "/settings/shares", component: Shares, @@ -143,7 +136,7 @@ const useSettingsConfig = () => { group: t("Workspace"), icon: LinkIcon, }, - Import: { + { name: t("Import"), path: "/settings/import", component: Import, @@ -151,7 +144,7 @@ const useSettingsConfig = () => { group: t("Workspace"), icon: ImportIcon, }, - Export: { + { name: t("Export"), path: "/settings/export", component: Export, @@ -159,20 +152,7 @@ const useSettingsConfig = () => { group: t("Workspace"), icon: ExportIcon, }, - // Integrations - ...mapValues( - PluginLoader.plugins, - (plugin) => - ({ - name: plugin.config.name, - path: integrationSettingsPath(plugin.id), - group: t("Integrations"), - component: plugin.settings, - enabled: !!plugin.settings && can.update, - icon: plugin.icon, - } as ConfigItem) - ), - SelfHosted: { + { name: t("Self Hosted"), path: integrationSettingsPath("self-hosted"), component: SelfHosted, @@ -180,7 +160,7 @@ const useSettingsConfig = () => { group: t("Integrations"), icon: BuildingBlocksIcon, }, - GoogleAnalytics: { + { name: t("Google Analytics"), path: integrationSettingsPath("google-analytics"), component: GoogleAnalytics, @@ -188,7 +168,7 @@ const useSettingsConfig = () => { group: t("Integrations"), icon: GoogleIcon, }, - Zapier: { + { name: "Zapier", path: integrationSettingsPath("zapier"), component: Zapier, @@ -196,21 +176,34 @@ const useSettingsConfig = () => { group: t("Integrations"), icon: ZapierIcon, }, - }), - [t, can.createApiKey, can.update, can.createImport, can.createExport] - ); + ]; - const enabledConfigs = React.useMemo( - () => - Object.keys(config).reduce( - (acc, key: string) => - config[key].enabled ? [...acc, config[key]] : acc, - [] - ), - [config] - ); + // Plugins + Object.values(PluginLoader.plugins).map((plugin) => { + const hasSettings = !!plugin.settings; + const enabledInDeployment = + !plugin.config.deployments || + plugin.config.deployments.length === 0 || + (plugin.config.deployments.includes("cloud") && isCloudHosted) || + (plugin.config.deployments.includes("enterprise") && !isCloudHosted); - return enabledConfigs; + const item = { + name: t(plugin.config.name), + path: integrationSettingsPath(plugin.id), + group: plugin.id === "collections" ? t("Workspace") : t("Integrations"), + component: plugin.settings, + enabled: enabledInDeployment && hasSettings && can.update, + icon: plugin.icon, + } as ConfigItem; + + const insertIndex = items.findIndex((i) => i.group === t("Integrations")); + items.splice(insertIndex, 0, item); + }); + + return items; + }, [t, can.createApiKey, can.update, can.createImport, can.createExport]); + + return config.filter((item) => item.enabled); }; export default useSettingsConfig; diff --git a/app/menus/CollectionMenu.tsx b/app/menus/CollectionMenu.tsx index 18ff7be33..023e90717 100644 --- a/app/menus/CollectionMenu.tsx +++ b/app/menus/CollectionMenu.tsx @@ -239,7 +239,7 @@ function CollectionMenu({ { type: "button", title: `${t("Export")}…`, - visible: !!(collection && canUserInTeam.createExport), + visible: !!(collection && canUserInTeam.createExport && can.export), onClick: handleExport, icon: , }, diff --git a/app/stores/CollectionsStore.ts b/app/stores/CollectionsStore.ts index 305228fbc..553c274ba 100644 --- a/app/stores/CollectionsStore.ts +++ b/app/stores/CollectionsStore.ts @@ -1,5 +1,5 @@ import invariant from "invariant"; -import { concat, find, last } from "lodash"; +import { concat, find, last, sortBy } from "lodash"; import { computed, action } from "mobx"; import { CollectionPermission, @@ -50,9 +50,12 @@ export default class CollectionsStore extends BaseStore { @computed get orderedData(): Collection[] { let collections = Array.from(this.data.values()); - collections = collections.filter((collection) => - collection.deletedAt ? false : true - ); + collections = collections + .filter((collection) => !collection.deletedAt) + .filter( + (collection) => + this.rootStore.policies.abilities(collection.id).readDocument + ); return collections.sort((a, b) => { if (a.index === b.index) { return a.updatedAt > b.updatedAt ? -1 : 1; @@ -62,6 +65,14 @@ export default class CollectionsStore extends BaseStore { }); } + @computed + get all(): Collection[] { + return sortBy( + Array.from(this.data.values()), + (collection) => collection.name + ); + } + /** * List of paths to each of the documents, where paths are composed of id and title/name pairs */ diff --git a/app/utils/PluginLoader.ts b/app/utils/PluginLoader.ts index 4f230a234..05bebb2ad 100644 --- a/app/utils/PluginLoader.ts +++ b/app/utils/PluginLoader.ts @@ -6,6 +6,7 @@ interface Plugin { name: string; description: string; requiredEnvVars?: string[]; + deployments?: string[]; }; settings: React.FC; icon: React.FC<{ size?: number; fill?: string }>; diff --git a/server/policies/collection.ts b/server/policies/collection.ts index e7f56e866..70420ab44 100644 --- a/server/policies/collection.ts +++ b/server/policies/collection.ts @@ -92,7 +92,7 @@ allow(User, "share", Collection, (user, collection) => { return true; }); -allow(User, "readDocument", Collection, (user, collection) => { +allow(User, ["readDocument", "export"], Collection, (user, collection) => { if (!collection || user.teamId !== collection.teamId) { return false; } diff --git a/server/routes/api/collections.ts b/server/routes/api/collections.ts index 56baf0ecb..5ffe88669 100644 --- a/server/routes/api/collections.ts +++ b/server/routes/api/collections.ts @@ -576,7 +576,7 @@ router.post( const collection = await Collection.scope({ method: ["withMembership", user.id], }).findByPk(id); - authorize(user, "read", collection); + authorize(user, "export", collection); const fileOperation = await sequelize.transaction(async (transaction) => collectionExporter({ diff --git a/shared/i18n/locales/en_US/translation.json b/shared/i18n/locales/en_US/translation.json index db1427e06..b45852345 100644 --- a/shared/i18n/locales/en_US/translation.json +++ b/shared/i18n/locales/en_US/translation.json @@ -320,8 +320,8 @@ "Groups": "Groups", "Shared Links": "Shared Links", "Import": "Import", - "Integrations": "Integrations", "Self Hosted": "Self Hosted", + "Integrations": "Integrations", "Google Analytics": "Google Analytics", "Revoke token": "Revoke token", "Revoke": "Revoke",