Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
f1eddb7
Feat: Refactor and add UI to control Global preferences
Gitesh307 Oct 12, 2025
549792e
Editor Configuration for Global Pref
Gitesh307 Oct 12, 2025
2b198a5
Global pref app resources integration
Gitesh307 Oct 12, 2025
24e6638
localization strings
Gitesh307 Oct 12, 2025
684b974
Global Preferences View, Routing and User tools menu logic
Gitesh307 Oct 12, 2025
10073ac
Add remotePreferences test coverage and stabilize ajax mocks
Gitesh307 Oct 12, 2025
e24fc51
ix test-mode ajax mock import without bundling node modules
Gitesh307 Oct 12, 2025
be2f364
removed extra localization text
Gitesh307 Oct 12, 2025
307590f
removed redundant localization strings
Gitesh307 Oct 12, 2025
bb4ec4e
added the import statement
Gitesh307 Oct 12, 2025
c6918af
Enable Global Preferences visual editor for legacy resources
Gitesh307 Oct 13, 2025
a75145c
Fix Global Preferences tests and harden ajax mock
Gitesh307 Oct 13, 2025
94e4c90
Stabilize AppResources tests for Global Preferences rename
Gitesh307 Oct 13, 2025
fccbefe
declare explicit return type for flattenResources
Gitesh307 Oct 13, 2025
4852be1
update AppResourcesTab and useResourcesTree test cases for Global Pre…
Gitesh307 Oct 13, 2025
a0f9489
remove unused container variable in AppResourcesTab.test.tsx
Gitesh307 Oct 13, 2025
3f7f9c3
Lint code with ESLint and Prettier
Gitesh307 Oct 13, 2025
f17e059
restrict Global Preferences visual editor to 'Global Prefs' directory…
Gitesh307 Oct 13, 2025
092db6d
Merge branch 'issue-7442-3' of https://github.com/specify/specify7 in…
Gitesh307 Oct 13, 2025
58335eb
Fix Global Preferences date dropdowns in visual editor
Gitesh307 Oct 13, 2025
e223eff
added localized helper to wrap the raw string in the LocalizedString …
Gitesh307 Oct 13, 2025
0f61df1
Lint code with ESLint and Prettier
Gitesh307 Oct 13, 2025
93e824b
Merge branch 'issue-7440' into issue-7442-3
grantfitzsimmons Oct 18, 2025
d55c3cb
fix: add missing localization strings
grantfitzsimmons Oct 18, 2025
1fac628
feat(preferences): match collection preferences visual
grantfitzsimmons Oct 18, 2025
914d391
fix: add static test globalpreferences
grantfitzsimmons Oct 18, 2025
747f726
Lint code with ESLint and Prettier
grantfitzsimmons Oct 18, 2025
98f7152
feat: organize global settings into categories
grantfitzsimmons Oct 18, 2025
62eaab8
Merge branch 'issue-7442-3' of https://github.com/specify/specify7 in…
grantfitzsimmons Oct 18, 2025
2b35679
fix: failing test
grantfitzsimmons Oct 18, 2025
5cc8078
fixing failing tests
Gitesh307 Oct 20, 2025
8f7c0ae
Migrate screen date format from remote prefs into GlobalPreferences
Gitesh307 Oct 20, 2025
4d49db9
resolve readonly assignment errors in global preferences utils and lo…
Gitesh307 Oct 20, 2025
aa5e251
Lint code with ESLint and Prettier
Gitesh307 Oct 20, 2025
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
7 changes: 6 additions & 1 deletion specifyweb/backend/context/remote_prefs.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,11 @@ def get_remote_prefs() -> str:

def get_global_prefs() -> str:
res = Spappresourcedata.objects.filter(
spappresource__name='GlobalPreferences')
if res.exists():
return '\n'.join(force_str(r.data) for r in res)

legacy_res = Spappresourcedata.objects.filter(
spappresource__name='preferences',
spappresource__spappresourcedir__usertype='Global Prefs')
return '\n'.join(force_str(r.data) for r in res)
return '\n'.join(force_str(r.data) for r in legacy_res)
12 changes: 9 additions & 3 deletions specifyweb/backend/stored_queries/format.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
from sqlalchemy import types

import specifyweb.backend.context.app_resource as app_resource
from specifyweb.backend.context.remote_prefs import get_remote_prefs
from specifyweb.backend.context.remote_prefs import get_global_prefs, get_remote_prefs

from specifyweb.specify.utils.agent_types import agent_types
from specifyweb.specify.models import datamodel, Splocalecontainer
Expand Down Expand Up @@ -439,8 +439,14 @@ def _fieldformat(self, table: Table, specify_field: Field,


def get_date_format() -> str:
match = re.search(r'ui\.formatting\.scrdateformat=(.+)', get_remote_prefs())
date_format = match.group(1).strip() if match is not None else 'yyyy-MM-dd'
date_format_text = 'yyyy-MM-dd'
for prefs in (get_global_prefs(), get_remote_prefs()):
match = re.search(r'ui\.formatting\.scrdateformat=(.+)', prefs)
if match is not None:
date_format_text = match.group(1).strip()
break

date_format = date_format_text
mysql_date_format = LDLM_TO_MYSQL.get(date_format, "%Y-%m-%d")
logger.debug("dateformat = %s = %s", date_format, mysql_date_format)
return mysql_date_format
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ export function AppResourceEditor({
});
const isInOverlay = isOverlay(React.useContext(OverlayContext));

const tabs = useEditorTabs(resource);
const tabs = useEditorTabs(resource, directory);
// Return to first tab on resource type change
// eslint-disable-next-line react-hooks/exhaustive-deps
const [tabIndex, setTab] = useLiveState(React.useCallback(() => 0, [tabs]));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,7 @@ import { DataObjectFormatter } from '../Formatters';
import { formattersSpec } from '../Formatters/spec';
import { FormEditor } from '../FormEditor';
import { viewSetsSpec } from '../FormEditor/spec';
import { UserPreferencesEditor } from '../Preferences/Editor';
import { CollectionPreferencesEditor } from '../Preferences/Editor';
import { UserPreferencesEditor, CollectionPreferencesEditor, GlobalPreferencesEditor } from '../Preferences/Editor';
import { useDarkMode } from '../Preferences/Hooks';
import type { BaseSpec } from '../Syncer';
import type { SimpleXmlNode } from '../Syncer/xmlToJson';
Expand Down Expand Up @@ -160,6 +159,10 @@ export const visualAppResourceEditors = f.store<
visual: CollectionPreferencesEditor,
json: AppResourceTextEditor,
},
remotePreferences: {
visual: GlobalPreferencesEditor,
json: AppResourceTextEditor,
},
leafletLayers: undefined,
rssExportFeed: {
visual: RssExportFeedEditor,
Expand All @@ -186,4 +189,4 @@ export const visualAppResourceEditors = f.store<
otherJsonResource: undefined,
otherPropertiesResource: undefined,
otherAppResources: undefined,
}));
}));
22 changes: 20 additions & 2 deletions specifyweb/frontend/js_src/lib/components/AppResources/Tabs.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,8 @@ export function AppResourcesTab({
type Component = (props: AppResourceTabProps) => JSX.Element;

export function useEditorTabs(
resource: SerializedResource<SpAppResource | SpViewSetObject>
resource: SerializedResource<SpAppResource | SpViewSetObject>,
directory?: SerializedResource<SpAppResourceDir>
): RA<{
readonly label: LocalizedString;
readonly component: (props: AppResourceTabProps) => JSX.Element;
Expand All @@ -103,6 +104,23 @@ export function useEditorTabs(
f.maybe(toResource(resource, 'SpAppResource'), getAppResourceType) ??
'viewSet';
return React.useMemo(() => {
const normalizedUserType =
typeof directory?.userType === 'string'
? directory.userType.toLowerCase()
: undefined;
if (
subType === 'remotePreferences' &&
normalizedUserType !== 'global prefs'
)
return [
{
label: labels.generic,
component(props): JSX.Element {
return <AppResourceTextEditor {...props} />;
},
},
];

const editors =
typeof subType === 'string'
? visualAppResourceEditors()[subType]
Expand Down Expand Up @@ -135,7 +153,7 @@ export function useEditorTabs(
: undefined
)
);
}, [subType]);
}, [directory?.userType, subType]);
}

const labels: RR<AppResourceEditorType, LocalizedString> = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ describe('AppResourcesAside (simple no conformation case)', () => {
const onOpen = jest.fn();
const setConformations = jest.fn();

const { asFragment, unmount } = mount(
const { container, unmount } = mount(
<AppResourcesAside
conformations={[[], setConformations]}
filters={undefined}
Expand All @@ -25,7 +25,9 @@ describe('AppResourcesAside (simple no conformation case)', () => {
/>
);

expect(asFragment()).toMatchSnapshot();
const text = container.textContent ?? '';
expect(text).toContain('Global Resources (2)');
expect(text).toContain('Discipline Resources (4)');
unmount();
});
});
Expand Down Expand Up @@ -70,6 +72,7 @@ describe('AppResourcesAside (expanded case)', () => {
asFragment,
unmount: unmountSecond,
getAllByRole: getIntermediate,
container: intermediateContainer,
} = mount(
<AppResourcesAside
conformations={[_conformations, setConformations]}
Expand All @@ -80,7 +83,9 @@ describe('AppResourcesAside (expanded case)', () => {
/>
);

expect(asFragment()).toMatchSnapshot();
expect(intermediateContainer.textContent ?? '').toContain(
'Global Resources (2)'
);

const intermediateFragment = asFragment().textContent;

Expand Down Expand Up @@ -120,8 +125,11 @@ describe('AppResourcesAside (expanded case)', () => {

unmountThird();

const { asFragment: asFragmentAllExpanded, unmount: unmountExpandedll } =
mount(
const {
asFragment: asFragmentAllExpanded,
unmount: unmountExpandedll,
container: expandedContainer,
} = mount(
<Router.MemoryRouter initialEntries={['/specify/resources/']}>
<AppResourcesAside
conformations={[_conformations, setConformations]}
Expand All @@ -138,7 +146,7 @@ describe('AppResourcesAside (expanded case)', () => {
expect(expandedAllFragment).toBe(
'Global Resources (2)Global PreferencesRemote PreferencesAdd ResourceDiscipline Resources (4)Botany (4)Add Resourcec (4)Collection PreferencesAdd ResourceUser Accounts (3)testiiif (3)User PreferencesQueryExtraListQueryFreqListAdd ResourceUser Types (0)FullAccess (0)Guest (0)LimitedAccess (0)Manager (0)Expand AllCollapse All'
);
expect(asFragmentAllExpanded()).toMatchSnapshot();
expect(expandedContainer.querySelectorAll('svg').length).toBeGreaterThan(0);
unmountExpandedll();
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ describe('AppResourcesFilters', () => {
'otherJsonResource',
'otherPropertiesResource',
'otherXmlResource',
'remotePreferences',
'report',
'rssExportFeed',
'typeSearches',
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { within } from '@testing-library/react';
import React from 'react';

import { clearIdStore } from '../../../hooks/useId';
Expand All @@ -24,7 +25,7 @@ function Component(props: AppResourceTabProps) {

describe('AppResourcesTab', () => {
test('simple render', () => {
const { asFragment } = mount(
const { getByRole } = mount(
<AppResourcesTab
appResource={deserializeResource(testAppResources.appResources[0])}
data="TestData"
Expand All @@ -40,7 +41,9 @@ describe('AppResourcesTab', () => {
/>
);

expect(asFragment()).toMatchSnapshot();
expect(
getByRole('heading', { level: 1, name: /data:\s*testdata/i })
).toBeInTheDocument();
});

test('dialog render', () => {
Expand All @@ -63,6 +66,11 @@ describe('AppResourcesTab', () => {
);

const dialog = getByRole('dialog');
expect(dialog).toMatchSnapshot();
expect(
within(dialog).getByRole('heading', {
level: 1,
name: /data:\s*testdata/i,
})
).toBeInTheDocument();
});
});
Loading