Skip to content

DataPackage Utilities part 1 - SystemMetadata and supporting utilities - #2812

Open
robyngit wants to merge 56 commits into
developfrom
feature-2804-d1-datapackage-utilities
Open

DataPackage Utilities part 1 - SystemMetadata and supporting utilities#2812
robyngit wants to merge 56 commits into
developfrom
feature-2804-d1-datapackage-utilities

Conversation

@robyngit

@robyngit robyngit commented Apr 10, 2026

Copy link
Copy Markdown
Member

This PR updates the SystemMetadata class (prev. SysMeta) and adds supporting sub-classes, XML helpers, and validation utilities. With this update, the SystemMetadata class covers the complete sysMeta schema, including parsing, validation, and serialization of all elements. It also adds methods to enable editing, like add, remove, and replace methods on nested complex (e.g. AccessPolicy and ReplicationPolicy).

Changes

  • Add sub-classes to SystemMetadata to represent nested sysmeta sections / child elements:
    • Checksum
    • AccessPolicy / AccessRule
    • ReplicationPolicy
    • Replica / ReplicaList
    • MediaType / MediaTypeProperty
  • Add SysMetaSchema where XML schema definition and related constants live.
  • Added/refined common utility modules used by SystemMetadata. These utilities will be used by ResourceMap and can replace some repeated code across the codebase:
    • ValueUtilities
    • ValidationUtilities
    • DateUtilities
    • UrlUtilities
    • DataONEXmlUtilities
    • expanded XMLUtilities / XMLTypes
  • Updated SysMetaService and related DataONE service plumbing to use normalized URLs/PIDs, stronger request config handling, and SystemMetadata parsing/serialization.
  • Updated components that use SystemMetadata to use the new model shape and methods, including:
    • VersionTracker
    • ResourceMapResolver
    • VersionHistoryView
    • metadata/package resolution paths
  • Added/updated tests for the new sysmeta domain models, XML/validation utilities, DataONE services, resolver/version-history flows, and affected views.

How SystemMetadata Works

SystemMetadata is a typed model with direct properties (rather than the previous nested .data object). Basic fields like identifier, seriesId, obsoletes, dateUploaded, etc. live directly on the instance as simlpe strings, numbers, etc. More complex elements that have their own nested structure are represented as child classes/objects, like accessPolicy (which has its own nested AccessRule objects), replicationPolicy (with nested fields and a list of Replica objects), and mediaType (with nested MediaTypeProperty objects).

The model is designed around four operations:

  • Parse: SystemMetadata.fromXml() / .parse() reads DataONE sysmeta XML into normalized root properties and child objects.
  • Validate: .validate() returns structured validation issues for root fields and nested content.
  • Serialize: .serialize() writes canonical v2 sysmeta XML.
  • Export: .toJSON() returns a JSON-safe domain-shaped snapshot.

Parsing is tolerant of common XML irregularities, so that viewing and editing objects is not blocked by minor sysMeta errors. Errors that are recoverable include:

  • Missing namespace prefixes or namespace declarations on systemMetadata and child elements. Parsing matches element local names case-insensitively and ignores prefixes, so namespace-free XML and mixed-prefix XML still parse.
  • Valid v1 sysmeta documents are read and then normalized as v2.
  • v2-era fields appearing in otherwise v1-shaped XML, e.g. seriesId is accepted even when the xml is v1.
  • Unknown child elements. They are ignored and then excluded on serialization.
  • Known child elements that are out of order. The parser does not require input order to match the order defined in XSD.
  • Duplicate singleton fields, where the last one wins.
  • Invalid mediaType properties, accessPolicy rules, and replica entries are dropped individually while valid sibling are preserved
  • Optional fields with invalid values. They are reset to defaults.
  • Boolean/integer/date/text normalization where values are salvageable.

Fixes are tracked in parseWarnings.

Truly invalid required content still fails parsing with an error, including malformed XML, DataONE <error> responses, the wrong document root, and missing or invalid required properties.

Upon serialization, the model writes valid, normalized v2 SysMeta.

Reviewing

This is a big PR since it involves a key model and creates many new supporting files. The most important review areas are:

  • src/js/models/sysmeta/SystemMetadata.js and its child models
  • src/js/models/dataONEServices/SysMetaService.js
  • src/js/models/sysmeta/VersionTracker.js
  • src/js/models/resourceMap/ResourceMapResolver.js

robyngit added 30 commits March 18, 2026 11:13
- Class handles persistent storage of object versions using localForage, to be integrated with VersionTracker
- Add unit tests for VersionStorage class

Issue #2783
- Add DataONEHttpError, HttpRetryPolicy, and UrlBuilder models to eventually be used within a DataONEHttpClient model for handling DataONE-specific HTTP requests, retries, deduplication, and error handling.

Issue #2783
- Convert the VersionStorage model to a more generic PersistentStorage model that can be used for other caching needs.
- Add more complete tests for PersistentStorage.

Issue #2783
- Class is used for DataONE API calls and handles URL building, retries, dedupe of in‑flight requests, abort/timeouts, and response parsing.
- Add unit tests for the new class.

Issue #2783
- SysMeta class purly handles parsing (and eventuallt serialization) of XML

Issue #2783
- Create DataONEService, an extendable base class that handles token resolution, caching, upload/download, and error handling.

Issue #2783
- Extends DataONEService
- Returns SysMeta object on download
- Add unit tests for SysMetaService

Issue #2783
buildUrl now handles empty paths without resetting to root, and strips trailing slashes.

Issue #2783
- Remove logic for caching SysMeta and leverage SysMetaService instead
- Uses cached SysMeta instead of full version chains which are fragile
- Remove add version logic and istead rely on SysMetaService/DataONEService to cache successfully uploaded sysMeta

Issue #2783
In Utilities:
- add stableStringify for deterministic object/array hashing to be used to create singleton and cache keys
- add normalizeUrl helper (for keys and general use) that trims and removes trailing slashes, with optional fallback
- add buildInstanceKey and getSingleton for consistent singleton management (DataONEHttpClient, SysMetaService)
- add awaitMetacatUI helper for deferred MetacatUI access (e.g., in tests)
- add unit tests covering new utilities

Issue #2783
- normalize config passed to constructor, stricter validation
- replace "namespace" with deterministic key based on config
- derive singleton/store keys using the new Utilities.buildInstanceKey
- allow localforageConfig overrides for name/storeName/version
- expose removeItem errors
- switch record shape to `{ value, expiresAt }` from `{ value, updatedAt, ttlMs }`
- set default TTL to 1 hour
- add and update tests to match new behaviour

Issue #2783
DataONEHttpClient:
  - Make sure we don't lose functions when cloning options during normalization
  - Make header merging case-insensitive so request headers override defaults without duplication
DataONEService:
  - Fix bug where per-request auth option was ignored
  - Create private local storage cache based on loggedin user username rather than token because token can change during a session
  - Remove ability to pass explicit token in request options for simplification (always rely on getToken function)
SysMetaService:
  - On upload, preserve any headers passed in options
  - Avoid caching sysMeta XML that is invalid
For all the above:
  - Add stricter validation for client & request options
  - Build instance and deduplication keys based on normalized config objects
  - Add more unit tests
- Also increase timeout for fetching token (UserModel) to account for slow responses/connections

Issue #2783
- Stop creating singletons for VersionTracker class, only underlying DataONEHttpClient (to dedup SysMeta fetches) and PersistentStorage (to share SysMeta cache) are singletons.
- Add stricter input validation (especially for constructor options)
- Make sure getAdjacent doesn't ignore useCache cacheKey and options
- Make sure request options (e.g. for HTTP
  requests to get SysMeta) are passed through
  properly
- Queue notify promises during version traversal so that errors can be handled explicitly, while still not slowing down the loop.
- Ensure notify emits errors even when no adjacent SysMeta is found
- Expand test coverage

Also: Fix parsing of numeric fields in SysMeta

Issue #2783
- Use new PersistentStorage class rather than localForage directly (since it now includes more robust error handling, etc.)
- Remove excess RM verification in guessPid step
- Fix overly general listener removal in resolveFromSeriesId (only remove the listener for the specific handler)
- Don't make RM resolver a singleton, only underlying Persistent Storage and DataONEHttpClient need to be singletons for shared caching and request dedup
- Catch errors such that Solr fails, keep resolution going to next steps
- Improve JSdocs
- Update unit tests and add new ones
- escape PID values in searchIndex queries via QueryService.escapeLucene
- correct checkLogForMultipleRMs so only rms.length > 1 sets multipleRMs
- handle index lookup failures during walkSysmeta
- Add more unit tests

Issue #2783
Update VersionNav & EMLEditor to conform to changes made to the ResourceMapResolver and VersionTracker.

Issue #2783
DataONEObjects - to organize sysMeta for each version
VersionTimelineGroups - to group versions by date

Issue #2766
- When sysMeta download for notification is aborted, stop notification and don't throw an error. This can happen on re-render.

Issue #2766
Also move VersionHistoryView into the VersionHistory folder

Issue #2766
- Set a max width on the entire version history view instead of just the version timeline
- Change the warning messages to show the error part of the message first, followed by the # of versions found

Issue #2766
- Use local timezone consistently for grouping and displaying dates in the version history view
- Re-render timeline groups when date or date label change
- Add a new DateUtility class and move date helpers from Version history views and collections to this class

Issue #2766
- Pass the VersionTimeLineGroup model directly to the VersionTimelineGroupView for consistency, rather than passing individual attributes
- Rename `renderAll` method from VersionTimelineGroupView to `render` for consistency with other views

Issue #2766
- Add badge next to versions with DOI and add filter to hide all except those with DOIs
- Add tooltips to ToggleView, clean up some code & jsdocs
- Add disabled state to ToggleView
- Along the way, clean up VersionTimelineGroup css class names and usage
- Remove unused class/element in VersionHistoryView

Issue #2766
- Use the "tokenChecked" property of the user model to determine whether the token has already been checked instead of calling multiple times

Issue #2766
- Show relative dates rather than older/newer
- Show separate badges for errors, warnings, and key points like current, newest, oldest. A single version can have multiple badges.
- Add descriptions to badges (will use as tooltips)
- Add getRelativeDateString method + tests for DateUtility
- Fix JSdocs linting issues in DateUtility
- Remove unused "showStatus" option from ObjectVersionView (always show badges)

Issue #2766
- Add a badge to show the relative position of each version in the version history timeline
- Add tooltips to all badges
- Make badge descriptions dynamic, include the date diff or version diff in the descriptions
- Remove redundant "hasPopupModule" function from ToggleView since view won't render without Semantic import

Issue #2766
- When the VersionTracker reaches the maximum number of hops allowed in a single search, it will now set a `maxHopsReached` property on the record for that PID
- The VersionHistoryView includes an appropriate message in the error message shown to the user when one or both sides of the version chain are incomplete.

Issue #2766
- During obsolesence chain traversal in VersionTracker, detect when an
  object's dateUploaded is more recent than the object that obsoletes it
  (i.e. not chronological)

Misc:
- Forward options for downloads through VersionTracker getNext/getPrev
- Make notify always emit placeholder sysmeta with errors/versionHistory

Issue #2766
- Flag date conflicts in version history view
    - date conflict summary banner to VersionHistoryView
    - inline date conflict notes to ObjectVersionView
- Highlight timeline groups containing visible date conflicts
- Sort by obsolesence chain order (newest to oldest) instead of
  date uploaded in version history timeline
- Group timeline entries by contiguous chain-order date segments,
   with date conflict segments grouped separately and highlighted
   with a warning style
- Unify hidden-state styling with version-history--hidden class
- Normalize border color CSS custom property naming
- Expand test coverage for version history views and collections,
  including date conflict scenarios

Issue #2766
robyngit added 26 commits March 18, 2026 11:15
- Reduce the number of total re-renders, update views instead when possible
- Only render badge tooltips once, after all versions are found (or an error occurs)
- Prevent remaining "progress" messages from showing once the the full chain is found or an error occurs (otherwise it sometimes overwrites the final status message)

Issue #2766
- New AppModel setting, "showVersionHistory", defaults to true

Issue #2766
Files missing from commit e4d9ac6
- Version history test
- autoAddTooltip setting in ObjectVersionView
Refresh button clears all versions in local storage and/or memory and re-fetches the version history from the server.

Issue #2766
Use empty string instead of empty span elements for badges that are not applicable. Badges are in a container now and the empty badges are no longer needed to maintain grid structure.

Issue #2766
- Remove top-level summary banner and show only per-version date notes in Version History
- Update per-version message wording to be less alarming
- Switch timeline markers to blue info styling instead of yellow warning styling
- Update tests to reflect these changes

Issue #2766
Need to be updated to reflect design changes, especially the multiple badges displayed and date format

Issue #2766
Needed to show upload/download progress

Issue #2804
- Refactor XMLUtilities around DOMParser and XMLSerializer
- Replace cleanXMLText with DOMParser/XMLSerializer-based XML helpers
  - Native APIs handle character encoding and escaping
- Rename some functions for clarity and consistency
- Add element lookup, selector, and invalid-character utilities
- Update MetadataView to use new fn name
- Switch EMLEntityView back to legacy model-level XML cleaning
  - Manual text encoding required for XML parsed with jQuery's HTML parser
- Add tests for XMLUtilities

Issue #2804
- extract generic value, URL, comparison, and formatting helpers into ValueUtilities
- move DOI validation and shared validation helpers into ValidationUtilities
- add DataONEXmlUtilities for parsing DataONE error XML and plain-error serialization
- expand XMLUtilities with stricter parsing, root/namespace checks, sequence validation, and serialization helpers
- update service, model, and view call sites to use the new utility modules
- centralize object format fetching through Utilities.getObjectFormats()
- add unit coverage for the new utility modules and XML helper behavior

Issue #2804
- SysMeta can now parse, serialize, and validate all SysMeta docs (covers all fields in the schema)
- Add sub-classes for access policy, replication, replica, and media type
- move SysMeta field metadata into SysMetaSchema
- Update editor/version-history integration to use the new SysMeta model shape
- Add more SysMeta and SysMetaService unit tests

Issue #2804
- Service handles /reserve/ and /generate/ DataONE endpoints

Issue #2804
- add identifier XML parsing & default header helpers for DataONE services
- add CN generate URL coverage in AppModel
- add tests for editor/version history and upload progress validation
- add missing ObjectFormats import to Utilities.js

Issue #2804
- Move URL utilities from dataONEServices/UrlBuilder and ValueUtilities to common/UrlUtilities, and remove UrlBuilder
- Update tests and callers to use new UrlUtilities

Issue #2804
- add reusable validation and value utility helpers for normalization, sorting, and exceptions
- centralize shared DataONEService client config and request option handling
- standardize IdentifierService and SysMetaService validation
- simplify SysMeta validation exception handling
- add unit tests for all new utilities and services

Issue #2804
- Add normalizePid and encodePidPath utilities to DataONEService
- Use in SysMetaService and IdentifierService
- Add and update unit tests

Issue #2804
- ObjectService can download, upload, and update DataONE objects
- Also fix linting issues in DataONEService
- Add ObjectService tests

Issue #2804
- extend UrlUtilities with RFC3986/DataONE PID path-segment encode/decode methods
- add ValueUtilities helpers for list coercion, plain-object checks, cloned array-valued records, readable string lists, and validated string choice handling
- rename integer validation helpers positiveInteger --> nonNegativeInteger (since it includes zero)
- fix MetacatUI property lookup so undefined and falsy values are handled correctly
- rename objectFormat method to reflect async behavior
- rename DateUtility to DateUtilities and add improve date parsing and formatting methods
- update callers to use new module/method names
- add tests

Issue #2804
- add XMLTypes plus new ValueUtilities and ValidationUtilities helpers
- add DataONEXmlUtilities.parseRequiredDocument and cover DataONE error XML handling
- simplify XMLUtilities by removing methods that can be handled by native DOM APIs
- add & update unit tests

Issue #2804
- rename SysMeta to SystemMetadata
- expose properties directly on the instance instead of a .data object
- add add, replace, and remove methods for properties that can have multiple values (e.g. accessPolicy, mediaType, replica, etc.)
- allow some irregular XML to be parsed and repaired, record problems in parseWarnings
- always serialize sysmeta to v2
- use simple field normalization/validation via XMLTypes
- add a checksum class and use it for checksum properties instead of a plain string
- update SysMetaService, version history integrations, and unit tests

Issue #2804
- Use `ResourceMapResolver` to resolve resourceMap IDs when Solr returns
  none or multiple
- Check for existence of sysMeta if Solr lookup fails, use that to
  determine if dataset is indexing vs truly not found
- break apart `MetadataView` model getting function into
  `onModelSync`/`onModelError` handlers
- use `QueryService` to find metadata for data objects
- normalize event-listener cleanup to avoid duplicate error handling
- add `MetadataView` unit tests

Issue #569
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: No status

Development

Successfully merging this pull request may close these issues.

Create utilities to support reliable data package fetching, uploading, and updating

1 participant