Some checks failed
Create issues from TODOs / todos (push) Failing after 3s
Close inactive issues / close-issues (push) Has been cancelled
Deploy GitHub Pages / deploy-preview (push) Has been cancelled
CI / ⚙️ Variables Setup (push) Has been cancelled
CI / ⚙️ Test Guard (push) Has been cancelled
CI / 🚀 Notify external services - draft (push) Has been cancelled
CI / 📦 Build Packages (push) Has been cancelled
CI / 📦 Meteor Build (coverage) (push) Has been cancelled
CI / 📦 Meteor Build (production) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [account-service presence-service omnichannel-transcript-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [account-service presence-service omnichannel-transcript-service], fips) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [authorization-service queue-worker-service ddp-streamer-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [authorization-service queue-worker-service ddp-streamer-service], fips) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [rocketchat], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [rocketchat], coverage) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [rocketchat], fips) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [account-service presence-service omnichannel-transcript-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [authorization-service queue-worker-service ddp-streamer-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [rocketchat], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [rocketchat], coverage) (push) Has been cancelled
CI / 🚢 Publish Docker Images (ghcr.io) (push) Has been cancelled
CI / 📦 Track Image Sizes (push) Has been cancelled
CI / 🔎 Code Check - Action Lint (push) Has been cancelled
CI / 🔎 Code Check (push) Has been cancelled
CI / 🔨 Test Storybook (push) Has been cancelled
CI / 🔨 Test Unit (push) Has been cancelled
CI / 🔨 Test API (CE) (push) Has been cancelled
CI / 🔨 Test API Livechat (CE) (push) Has been cancelled
CI / 🔨 Test UI (CE) (push) Has been cancelled
CI / 🔨 Test API (EE) (push) Has been cancelled
CI / 🔨 Test API Livechat (EE) (push) Has been cancelled
CI / 🔨 Test API Apps (deno-runtime - EE) (push) Has been cancelled
CI / 🔨 Test UI (EE) (push) Has been cancelled
CI / 🔨 Test API (FIPS) (push) Has been cancelled
CI / 🔨 Test API Livechat (FIPS) (push) Has been cancelled
CI / 🔨 Test UI (FIPS) (push) Has been cancelled
CI / 🔨 Test Federation Matrix (push) Has been cancelled
CI / 📊 Report Coverage (push) Has been cancelled
CI / ✅ Tests Done (push) Has been cancelled
CI / 🚀 Publish build assets (push) Has been cancelled
CI / 🚀 Publish Docker Images (DockerHub) (push) Has been cancelled
CI / 🚀 Notify external services (push) Has been cancelled
CI / Update Version Durability (push) Has been cancelled
Release / Release (push) Has been cancelled
Auto-close duplicate issues / auto-close-duplicates (push) Failing after 1s
Code scanning - action / CodeQL-Build (push) Failing after 2s
160 lines
5.3 KiB
TypeScript
160 lines
5.3 KiB
TypeScript
import type { ISetting } from '@rocket.chat/core-typings';
|
|
import { isSettingCode } from '@rocket.chat/core-typings';
|
|
import { createPredicateFromFilter } from '@rocket.chat/mongo-adapter';
|
|
import { isTruthy } from '@rocket.chat/tools';
|
|
import type { SettingsContextQuery, SettingsContextValue } from '@rocket.chat/ui-contexts';
|
|
import { SettingsContext, useAtLeastOnePermission, useEndpoint } from '@rocket.chat/ui-contexts';
|
|
import { useQueryClient } from '@tanstack/react-query';
|
|
import type { ReactNode } from 'react';
|
|
import { useCallback, useMemo } from 'react';
|
|
|
|
import { PublicSettingsCachedStore, PrivateSettingsCachedStore } from '../cachedStores';
|
|
import { useShowSettingAlerts } from '../hooks/useShowSettingAlerts';
|
|
import { PrivateCachedStore } from '../lib/cachedStores/CachedStore';
|
|
import { applyQueryOptions } from '../lib/cachedStores/applyQueryOptions';
|
|
import { getCodeSettingError } from '../lib/utils/getCodeSettingError';
|
|
|
|
const settingsManagementPermissions = ['view-privileged-setting', 'edit-privileged-setting', 'manage-selected-settings'];
|
|
|
|
export type SettingsProviderProps = {
|
|
children?: ReactNode;
|
|
};
|
|
|
|
const SettingsProvider = ({ children }: SettingsProviderProps) => {
|
|
const canManageSettings = useAtLeastOnePermission(settingsManagementPermissions);
|
|
|
|
const cachedCollection = canManageSettings ? PrivateSettingsCachedStore : PublicSettingsCachedStore;
|
|
|
|
const isLoading = !cachedCollection.useReady();
|
|
|
|
if (isLoading) {
|
|
throw (async () => {
|
|
if (cachedCollection instanceof PrivateCachedStore) {
|
|
cachedCollection.listen();
|
|
}
|
|
|
|
await cachedCollection.init();
|
|
})();
|
|
}
|
|
|
|
const querySetting = useMemo(
|
|
() =>
|
|
(_id: ISetting['_id']): [subscribe: (onStoreChange: () => void) => () => void, getSnapshot: () => ISetting | undefined] => {
|
|
let snapshot = cachedCollection.store.getState().get(_id);
|
|
|
|
const subscribe = (onStoreChange: () => void) =>
|
|
cachedCollection.store.subscribe(() => {
|
|
const newSnapshot = cachedCollection.store.getState().get(_id);
|
|
if (newSnapshot === snapshot) return;
|
|
snapshot = newSnapshot;
|
|
onStoreChange();
|
|
});
|
|
|
|
const getSnapshot = () => snapshot;
|
|
|
|
return [subscribe, getSnapshot];
|
|
},
|
|
[cachedCollection.store],
|
|
);
|
|
|
|
const querySettings = useMemo(() => {
|
|
return (query: SettingsContextQuery): [subscribe: (onStoreChange: () => void) => () => void, getSnapshot: () => ISetting[]] => {
|
|
const effectiveQuery = {
|
|
...('_id' in query && Array.isArray(query._id) && { _id: { $in: query._id } }),
|
|
...('_id' in query && !Array.isArray(query._id) && { _id: query._id }),
|
|
...('group' in query && { group: query.group }),
|
|
...('section' in query &&
|
|
(query.section
|
|
? { section: query.section }
|
|
: {
|
|
$or: [{ section: { $exists: false } }, { section: undefined }],
|
|
})),
|
|
};
|
|
|
|
const options = {
|
|
sort: {
|
|
section: 1,
|
|
sorter: 1,
|
|
i18nLabel: 1,
|
|
},
|
|
...('skip' in query && typeof query.skip === 'number' && { skip: query.skip }),
|
|
...('limit' in query && typeof query.limit === 'number' && { limit: query.limit }),
|
|
} as const;
|
|
|
|
const predicate = createPredicateFromFilter<ISetting>(effectiveQuery);
|
|
let snapshot = applyQueryOptions(cachedCollection.store.getState().filter(predicate), options);
|
|
|
|
const subscribe = (onStoreChange: () => void) =>
|
|
cachedCollection.store.subscribe(() => {
|
|
const newSnapshot = applyQueryOptions(cachedCollection.store.getState().filter(predicate), options);
|
|
if (newSnapshot === snapshot) return;
|
|
snapshot = newSnapshot;
|
|
onStoreChange();
|
|
});
|
|
|
|
const getSnapshot = () => snapshot;
|
|
|
|
return [subscribe, getSnapshot];
|
|
};
|
|
}, [cachedCollection.store]);
|
|
|
|
const queryClient = useQueryClient();
|
|
|
|
const showAlerts = useShowSettingAlerts();
|
|
|
|
const saveSettings = useEndpoint('POST', '/v1/settings');
|
|
const dispatch = useCallback(
|
|
async (changes: Partial<ISetting>[], onSaved?: () => void) => {
|
|
const changedSettingIds = changes.map((s) => s._id).filter(isTruthy);
|
|
const alerts = cachedCollection.store
|
|
.getState()
|
|
.filter((setting) => Boolean(setting.alert && changedSettingIds.includes(setting._id)));
|
|
// FIXME: This is a temporary solution to invalidate queries when settings change
|
|
changes.forEach((val) => {
|
|
if (val._id === 'Enterprise_License') {
|
|
queryClient.invalidateQueries({ queryKey: ['licenses'] });
|
|
}
|
|
});
|
|
|
|
const hasInvalidCodeSetting = changes.some((change) => {
|
|
if (!change._id || change.value === undefined) {
|
|
return false;
|
|
}
|
|
const setting = cachedCollection.store.getState().get(change._id);
|
|
return setting !== undefined && isSettingCode(setting) && getCodeSettingError(setting.code, change.value) !== undefined;
|
|
});
|
|
|
|
if (hasInvalidCodeSetting) {
|
|
return;
|
|
}
|
|
|
|
if (alerts.length) {
|
|
const accepted = await showAlerts(alerts);
|
|
|
|
if (!accepted) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
await saveSettings({ settings: changes as Pick<ISetting, '_id' | 'value'>[] });
|
|
|
|
onSaved?.();
|
|
},
|
|
[queryClient, saveSettings, showAlerts, cachedCollection.store],
|
|
);
|
|
|
|
const contextValue = useMemo<SettingsContextValue>(
|
|
() => ({
|
|
hasPrivateAccess: canManageSettings,
|
|
querySetting,
|
|
querySettings,
|
|
dispatch,
|
|
}),
|
|
[canManageSettings, querySetting, querySettings, dispatch],
|
|
);
|
|
|
|
return <SettingsContext.Provider value={contextValue}>{children}</SettingsContext.Provider>;
|
|
};
|
|
|
|
export default SettingsProvider;
|