Skip to content

Commit

Permalink
Merge branch 'production' into issue-5138
Browse files Browse the repository at this point in the history
  • Loading branch information
sharadsw authored Jul 30, 2024
2 parents 24eebd4 + 526a059 commit c18eefe
Show file tree
Hide file tree
Showing 30 changed files with 522 additions and 590 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,11 @@ All notable changes to this project will be documented in this file.

The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).

## [7.9.6.2](https://github.com/specify/specify7/compare/v7.9.6.1...v7.9.6.2) (22 July 2024)

- Fixed an issue that prevented `TimestampModified` from being captured upon saving a record since the `v7.9.6` release ([#5108](https://github.com/specify/specify7/issues/5108)*Reported by the University of Kansas and Ohio State University*)
- Fixed an issue that caused large trees to perform slowly or crash the browser due to using too much memory ([#5115](https://github.com/specify/specify7/pull/5115)*Reported by The Hebrew University of Jerusalem and Royal Botanic Gardens Edinburgh*)

## [7.9.6.1](https://github.com/specify/specify7/compare/v7.9.6...v7.9.6.1) (9 July 2024)

- Fixes an issue that led to tree definition item separators being trimmed ([#5076](https://github.com/specify/specify7/pull/5076))
Expand Down
1 change: 0 additions & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,3 @@ PyJWT==2.3.0
django-auth-ldap==1.2.15
jsonschema==3.2.0
typing-extensions==4.3.0
django-model-utils==4.4.0
5 changes: 0 additions & 5 deletions specifyweb/attachment_gw/models.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,4 @@
from django.db import models
from django.conf import settings
from django.db.models.deletion import CASCADE, SET_NULL
from django.utils import timezone
from model_utils import FieldTracker
from functools import partialmethod
from specifyweb.specify.models import datamodel, custom_save
from ..workbench.models import Dataset
Expand All @@ -15,7 +11,6 @@ class Spattachmentdataset(Dataset):
class Meta:
db_table = 'attachmentdataset'

timestamptracker = FieldTracker(fields=['timestampcreated', 'timestampmodified'])
save = partialmethod(custom_save)

# from django.apps import apps
Expand Down
1 change: 0 additions & 1 deletion specifyweb/frontend/js_src/css/main.css
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,6 @@
/* Make spinner buttons larger */
[type='number']:not([readonly], .no-arrows)::-webkit-outer-spin-button,
[type='number']:not([readonly], .no-arrows)::-webkit-inner-spin-button {
-webkit-appearance: inner-spin-button !important;
@apply absolute right-0 top-0 h-full w-2;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@ function entrypoint(): void {

interceptLogs();

console.group('Specify App Starting');
if (process.env.NODE_ENV === 'production') {
console.group('Specify App Starting');
console.log(
'%cDocumentation for Developers:\n',
'font-weight: bold',
Expand All @@ -27,7 +27,6 @@ function entrypoint(): void {
const entrypointName =
parseDjangoDump<ReturnType<typeof getEntrypointName>>('entrypoint-name') ??
'main';
console.log(entrypointName);
unlockInitialContext(entrypointName);

globalThis.addEventListener?.('load', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,13 @@ export function interceptLogs(): void {
const context = getLogContext();
const hasContext = Object.keys(context).length > 0;

// Silencing https://github.com/reactjs/react-modal/issues/808
if (
args[0] ===
"React-Modal: Cannot register modal instance that's already open"
)
return;

/**
* If actively redirecting log output, don't print to console
* (printing object to console prevents garbage collection
Expand Down
18 changes: 0 additions & 18 deletions specifyweb/frontend/js_src/lib/components/InitialContext/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,7 @@
*/

import type { MimeType } from '../../utils/ajax';
import { f } from '../../utils/functools';
import { defined } from '../../utils/types';
import { formatNumber } from '../Atoms/Internationalization';
import { SECOND } from '../Atoms/timeUnits';

/**
* This belongs to ./components/toolbar/cachebuster.tsx but was moved here
Expand Down Expand Up @@ -56,7 +53,6 @@ export const unlockInitialContext = (entrypoint: typeof entrypointName): void =>
export const load = async <T>(path: string, mimeType: MimeType): Promise<T> =>
contextUnlockedPromise.then(async (entrypoint) => {
if (entrypoint !== 'main') return foreverFetch<T>();
const startTime = Date.now();

// Doing async import to avoid a circular dependency
const { ajax } = await import('../../utils/ajax');
Expand All @@ -65,21 +61,7 @@ export const load = async <T>(path: string, mimeType: MimeType): Promise<T> =>
errorMode: 'visible',
headers: { Accept: mimeType },
});
const endTime = Date.now();
const timePassed = endTime - startTime;
// A very crude detection mechanism
const isCached = timePassed < 100;

// So as not to spam the tests
if (process.env.NODE_ENV !== 'test')
console.log(
`${path} %c[${
isCached
? 'cached'
: `${formatNumber(f.round(timePassed / SECOND, 0.01))}s`
}]`,
`color: ${isCached ? '#9fa' : '#f99'}`
);
return data;
});

Expand Down
Original file line number Diff line number Diff line change
@@ -1,64 +1,71 @@
export const tableActions = ['read', 'create', 'update', 'delete'] as const;
export const collectionAccessResource = '/system/sp7/collection';
export const operationPolicies = {
'/system/sp7/collection': ['access'],
'/admin/user/password': ['update'],
'/admin/user/agents': ['update'],
'/admin/user/sp6/is_admin': ['update'],
'/record/merge': ['update', 'delete'],
'/admin/user/invite_link': ['create'],
'/admin/user/oic_providers': ['read'],
'/admin/user/password': ['update'],
'/admin/user/sp6/collection_access': ['read', 'update'],
'/report': ['execute'],
'/admin/user/sp6/is_admin': ['update'],
'/attachment_import/dataset': [
'create',
'update',
'delete',
'upload',
'rollback',
],
'/export/dwca': ['execute'],
'/export/feed': ['force_update'],
'/permissions/library/roles': ['read', 'create', 'update', 'delete'],
'/permissions/list_admins': ['read'],
'/permissions/policies/user': ['read', 'update'],
'/permissions/user/roles': ['read', 'update'],
'/permissions/roles': [
'read',
'create',
'update',
'delete',
'copy_from_library',
],
'/permissions/library/roles': ['read', 'create', 'update', 'delete'],
'/tree/edit/taxon': ['merge', 'move', 'synonymize', 'desynonymize', 'repair'],
'/permissions/user/roles': ['read', 'update'],
'/querybuilder/query': [
'execute',
'export_csv',
'export_kml',
'create_recordset',
],
'/record/merge': ['update', 'delete'],
'/report': ['execute'],
'/system/sp7/collection': ['access'],
'/tree/edit/geography': [
'merge',
'move',
'synonymize',
'desynonymize',
'repair',
],
'/tree/edit/storage': [
'/tree/edit/geologictimeperiod': [
'merge',
'move',
'synonymize',
'desynonymize',
'repair',
'bulk_move',
],
'/tree/edit/geologictimeperiod': [
'/tree/edit/lithostrat': [
'merge',
'move',
'synonymize',
'desynonymize',
'repair',
],
'/tree/edit/lithostrat': [
'/tree/edit/storage': [
'merge',
'move',
'bulk_move',
'synonymize',
'desynonymize',
'repair',
],
'/querybuilder/query': [
'execute',
'export_csv',
'export_kml',
'create_recordset',
],
'/tree/edit/taxon': ['merge', 'move', 'synonymize', 'desynonymize', 'repair'],
'/workbench/dataset': [
'create',
'update',
Expand All @@ -69,13 +76,6 @@ export const operationPolicies = {
'transfer',
'create_recordset',
],
'/attachment_import/dataset': [
'create',
'update',
'delete',
'upload',
'rollback',
],
} as const;

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,9 @@ const checkRegistry = async (): Promise<void> =>
).then((policies) =>
sortPolicies(policies) === sortPolicies(operationPolicies)
? undefined
: error('Front-end has outdated list of operation policies')
: error(
'Front-end list of operation policies is out of date. To resolve this error, please update "operationPolicies" in specifyweb/frontend/js_src/lib/components/Permissions/definitions.ts based on the response from the http://localhost/permissions/registry/ endpoint'
)
);

export type PermissionsQueryItem = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import { Link } from '../Atoms/Link';
import type { SpecifyTable } from '../DataModel/specifyTable';
import { genericTables } from '../DataModel/tables';
import type { Tables } from '../DataModel/types';
import { userInformation } from '../InitialContext/userInformation';
import { Dialog } from '../Molecules/Dialog';
import { TableIcon } from '../Molecules/TableIcon';
import { hasTablePermission } from '../Permissions/helpers';
Expand Down Expand Up @@ -86,7 +87,9 @@ export function tablesFilter(
): boolean {
if (selectedTables?.includes(name) === true) return true;

const isRestricted = overrides.isHidden || overrides.isSystem;
const isRestricted = userInformation.isadmin
? overrides.isHidden
: overrides.isHidden || overrides.isSystem;
if (!showHiddenTables && isRestricted) return false;
const hasAccess = hasTablePermission(name, 'read');
if (!showNoAccessTables && !hasAccess) return false;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ describe('tablesFilter', () => {
));

test('showHiddenTables excludes hidden and system', () =>
expect(tablesFilter(false, true, true, tables.Institution)).toBe(false));
expect(tablesFilter(false, true, true, tables.Institution)).toBe(true));

test('showNoAccessTables excludes table without permission', () => {
const tablePermissions =
Expand Down
26 changes: 4 additions & 22 deletions specifyweb/frontend/js_src/lib/components/TreeView/Row.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,12 @@
import React from 'react';

import { useAsyncState } from '../../hooks/useAsyncState';
import { useId } from '../../hooks/useId';
import { commonText } from '../../localization/common';
import { treeText } from '../../localization/tree';
import type { RA } from '../../utils/types';
import { Button } from '../Atoms/Button';
import { className } from '../Atoms/className';
import { icons } from '../Atoms/Icons';
import { fetchRows } from '../DataModel/collection';
import type { AnyTree } from '../DataModel/helperTypes';
import { getPref } from '../InitialContext/remotePrefs';
import { userPreferences } from '../Preferences/userPreferences';
Expand Down Expand Up @@ -150,21 +148,6 @@ export function TreeRow<SCHEMA extends AnyTree>({
const hasNoChildrenNodes =
nodeStats?.directCount === 0 && nodeStats.childCount === 0;

const acceptedChildrenKey = `accepted${treeName.toLowerCase()}`;
const [synonymsNames] = useAsyncState(
React.useCallback(
async () =>
fetchRows(treeName as 'Taxon', {
fields: { name: ['string'] },
limit: 0,
[acceptedChildrenKey]: row.nodeId,
domainFilter: false,
}).then((rows) => rows.map(({ name }) => name)),
[acceptedChildrenKey, treeName, row.nodeId]
),
false
);

return hideEmptyNodes && hasNoChildrenNodes ? null : (
<li role="treeitem row">
{ranks.map((rankId) => {
Expand Down Expand Up @@ -247,12 +230,11 @@ export function TreeRow<SCHEMA extends AnyTree>({
? treeText.acceptedName({
name: row.acceptedName ?? row.acceptedId.toString(),
})
: synonymsNames === undefined ||
synonymsNames.length === 0
? undefined
: treeText.synonyms({
names: synonymsNames.join(', '),
: typeof row.synonyms === 'string'
? treeText.synonyms({
names: row.synonyms,
})
: undefined
}
>
{doIncludeAuthorPref &&
Expand Down
7 changes: 5 additions & 2 deletions specifyweb/frontend/js_src/lib/components/TreeView/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,9 @@ export const fetchRows = async (fetchUrl: string) =>
number,
number | null,
string | null,
string,
number
string | null,
number,
string | null
]
>
>(fetchUrl, {
Expand All @@ -44,6 +45,7 @@ export const fetchRows = async (fetchUrl: string) =>
acceptedName = undefined,
author = undefined,
children,
synonyms = undefined,
],
index,
{ length }
Expand All @@ -59,6 +61,7 @@ export const fetchRows = async (fetchUrl: string) =>
author,
children,
isLastChild: index + 1 === length,
synonyms,
})
)
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -574,7 +574,7 @@ export function CustomSelectElement({
aria-haspopup="listbox"
className={`
flex min-h-[theme(spacing.8)] min-w-max cursor-pointer
items-center gap-1 rounded px-1 text-left
items-center gap-1 rounded border border-gray-500 px-1 text-left
md:min-w-[unset] dark:border-none
${
defaultOption?.isRequired === true
Expand Down
26 changes: 9 additions & 17 deletions specifyweb/specify/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -414,6 +414,10 @@ def set_field_if_exists(obj, field: str, value) -> None:
if f.concrete:
setattr(obj, field, value)

def _maybe_delete(data: Dict[str, Any], to_delete: str):
if to_delete in data:
del data[to_delete]

def cleanData(model, data: Dict[str, Any], agent) -> Dict[str, Any]:
"""Returns a copy of data with only fields that are part of model, removing
metadata fields and warning on unexpected extra fields."""
Expand Down Expand Up @@ -446,30 +450,18 @@ def cleanData(model, data: Dict[str, Any], agent) -> Dict[str, Any]:

if model is models.Agent:
# setting user agents is part of the user management system.
try:
del cleaned['specifyuser']
except KeyError:
pass
_maybe_delete(cleaned, 'specifyuser')

# guid should only be updatable for taxon and geography
if model not in (models.Taxon, models.Geography):
try:
del cleaned['guid']
except KeyError:
pass
_maybe_delete(cleaned, 'guid')

# timestampcreated should never be updated.
# ... well it is now ¯\_(ツ)_/¯
# New requirments are for timestampcreated to be overridable.
try:
# del cleaned['timestampcreated']
pass
except KeyError:
pass
# _maybe_delete(cleaned, 'timestampcreated')

# Password should be set though the /api/set_password/<id>/ endpoint
if model is models.Specifyuser and 'password' in cleaned:
del cleaned['password']
if model is models.Specifyuser:
_maybe_delete(cleaned, 'password')

return cleaned

Expand Down
Loading

0 comments on commit c18eefe

Please sign in to comment.