You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: CHANGELOG.md
+9Lines changed: 9 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -5,6 +5,15 @@ All notable changes to this project will be documented in this file.
5
5
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
and this project uses [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
8
+
## [1.1.0] - 2026-05-11
9
+
10
+
### Added
11
+
- Expression syntax for `fylr Field Path`: combine multiple fields and static strings using `+` (e.g. `"Label: " + objecttype.field1 + "\n" + objecttype.field2`)
12
+
- String literals with escape sequences (`\n` newline, `\t` tab) in field path expressions
13
+
- Conditional groups `(...)`: if any field inside a group is empty, the entire group — including its static labels — is omitted from the output
14
+
-`|decimal2` format specifier for fixed-point integer fields stored as ×100 (e.g. `2030` → `20.30`)
15
+
- Support for nested table fields in dot-path resolution: paths now traverse through fylr's `_nested:<objecttype>__<fieldname>` array keys automatically
Copy file name to clipboardExpand all lines: README.md
+39-2Lines changed: 39 additions & 2 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -51,8 +51,45 @@ Each row maps a DataCite metadata field to a field path in the fylr object. The
51
51
|---|---|
52
52
| Profile ID | Must match the `ID` of a profile in the Profiles table |
53
53
| DataCite Field | Target field name. Supported: `title`, `creator`, `publisher`, `date`, `description`, `contributor`, `subjects`, `resourceTypeGeneral`, `publicationYear`|
54
-
| fylr Field Path | Dot-separated path to the field in the fylr object, e.g. `haustieranatomie.titel`|
55
-
| Default Value | Fallback value if the field path resolves to nothing |
54
+
| fylr Field Path | A field path or expression (see below) |
55
+
| Default Value | Fallback value if the expression resolves to an empty string |
56
+
57
+
#### fylr Field Path expressions
58
+
59
+
The **fylr Field Path** column accepts either a plain dot-path or a concatenation expression built from field references, string literals, and optional conditional groups.
**Conditional groups** — wrap part of an expression in `(...)`. The group is omitted entirely if any field inside it is empty. Use this to avoid orphan labels when optional fields are missing:
If `hoehe` is empty the whole `("\nHeight: " + haustieranatomie.hoehe)` group is dropped — no stray label appears.
87
+
88
+
**Decimal format specifier** — append `|decimal2` to a field reference to divide the stored integer by 100 and format it with two decimal places. Useful for fields stored as fixed-point integers (e.g. a value of `2030` is displayed as `20.30`):
-`getNestedValue()` — simple dot-path getter for reading from `info.json`.
306
+
-`httpRequest()` — Node stdlib HTTP wrapper returning a Promise.
301
307
302
308
There are commented-out `console.error` debug lines throughout. They are intentionally preserved for quick re-enablement during future debugging; do not delete them without a reason.
303
309
@@ -318,7 +324,7 @@ Orchestrates everything in a single async function:
318
324
9.**Compute the DataCite Basic Auth header** ([L128](server/webhook/register-doi.js#L128)).
319
325
10.**Loop over objects** ([L143-323](server/webhook/register-doi.js#L143-L323)):
320
326
- Read `_system_object_id` and `_objecttype`.
321
-
- For each field mapping: strip an optional `<objecttype>.` prefix from the dot-path (the UI sometimes includes it, sometimes not), pick `_current[objecttype]` as the root if it exists (richer data, closer to what a full object fetch would return), and resolve the path.
327
+
- For each field mapping: call `resolveExpressionAsync()` with the raw `fylr_field_path` value. This handles both plain dot-paths and concatenation expressions transparently.
322
328
- Construct the DOI as `<doi_prefix><system_object_id>`.
323
329
- Construct the landing URL by substituting `%system_object_id%` in the template.
324
330
- Build the DataCite payload (see [JSON:API payload format](#jsonapi-payload-format)). Note the defensive `:unkn` fallbacks — DataCite requires certain fields and rejects empty ones.
@@ -328,15 +334,44 @@ Orchestrates everything in a single async function:
328
334
329
335
**Why `exit(0)` on config errors?** A non-zero exit causes fylr to treat the whole webhook invocation as failed and may hide the emitted JSON body. Emitting a structured error on stdout plus `exit(0)` gives cleaner admin-visible diagnostics.
330
336
331
-
### `resolveFieldPathAsync()`
337
+
### `resolveExpressionAsync()`
338
+
339
+
The entry point for all field-path resolution. It accepts either a plain dot-path (backwards compatible) or a concatenation expression and returns a string.
340
+
341
+
The expression syntax:
342
+
343
+
| Token | Example | Behaviour |
344
+
|---|---|---|
345
+
| Plain path |`haustieranatomie.titel`| resolves via `resolveFieldPathAsync`|
346
+
| Concatenation |`field1 + field2`| results joined without separator |
347
+
| String literal |`"Label: "` or `"\n"`| used verbatim (`\n` → newline, `\t` → tab) |
348
+
| Conditional group |`("Label: " + field)`| entire group omitted if any field inside is empty |
349
+
| Format specifier |`field\|decimal2`| integer ÷ 100, two decimal places (e.g. `2030` → `20.30`) |
350
+
351
+
An optional `<objecttype>.` prefix on each path token is stripped before resolution, so paths with or without the prefix work identically.
Parses an expression string into an array of typed tokens used by `resolveExpressionAsync`. Token types:
356
+
357
+
-`{type: 'literal', value: string}` — a quoted string literal
358
+
-`{type: 'path', value: string, format?: string}` — a dot-path reference, with an optional `format` field when `|decimal2` is appended
359
+
-`{type: 'group', tokens: Token[]}` — a conditional group enclosed in `(...)`, recursively parsed
360
+
361
+
The parser is recursive-descent, handling nested groups naturally. `+` is the only operator; whitespace around it is ignored.
362
+
363
+
### `formatDecimal2()`
364
+
365
+
Divides the raw string value by 100 and formats the result with `Number.toFixed(2)`. Used for field values stored as fixed-point integers (×100). Returns the original value unchanged if it is not a valid number.
366
+
367
+
### `resolveFieldPathAsync()`
334
368
335
-
Resolves a dot-separated path (e.g. `haustieranatomie.titel` or `hersteller.hersteller.name`) into a value. The logic has three subtleties that would not be obvious from the signature:
369
+
Resolves a dot-separated path (e.g. `haustieranatomie.titel` or `hersteller.hersteller.name`) into a value. The logic has four subtleties that would not be obvious from the signature:
336
370
337
-
1.**fylr date fields are wrapped objects** (`{value: "2025-04-05"}`). At the end of the walk, if the result is an object with a `value` key, it is unwrapped ([L417](server/webhook/register-doi.js#L417)).
371
+
1.**fylr date fields are wrapped objects** (`{value: "2025-04-05"}`). At the end of the walk, if the result is an object with a `value` key, it is unwrapped.
338
372
2.**The root is always `obj[objecttype]`** — fylr objects are keyed by objecttype at the top level. Passing in a bare object without that wrapper returns `undefined`.
339
-
3.**Linked objects are shallow in the webhook payload**. When an object links to another object, the webhook payload only carries `{_id, _version}` for the linked object. If a dot-path descends into a linked object (either directly navigating into its typed key or trying to access a missing field on a wrapper), the function fetches the full linked object via `GET /api/v1/db/<objecttype>/_all_fields/<id>?format=long` using the plugin user's Bearer token, then continues the walk.
373
+
3.**Nested table fields use a special key format.** In the fylr API payload, a nested table named `tierart` on objecttype `haustieranatomie` is stored under the key `_nested:haustieranatomie__tierart` (not `tierart`). The traversal loop tries the plain part name first; if not found, it automatically tries the `_nested:<objecttype>__<part>` variant before proceeding. The value is an array; the first element is taken and traversal continues into it.
374
+
4.**Linked objects are shallow in the webhook payload**. When an object links to another object, the webhook payload only carries `{_id, _version}` for the linked object. If a dot-path descends into a linked object (either directly navigating into its typed key or trying to access a missing field on a wrapper), the function fetches the full linked object via `GET /api/v1/db/<objecttype>/_all_fields/<id>?format=long` using the plugin user's Bearer token, then continues the walk.
340
375
341
376
Both linked-object-fetch branches record failures via the shared `warnings` array so the caller can decide how to surface them. Errors from fetches are never fatal; the path just returns `undefined` and the mapping falls back to its `default_value`.
342
377
@@ -445,4 +480,5 @@ Using the internal API URL (`info.api_url`) avoids any reverse-proxy interferenc
445
480
|`PublishUnknownCollector: collector ""`| The `_basetype` wrapper got removed, or the `collector` field inside `publish` is empty, or the collector name doesn't match the one configured in fylr |
446
481
| Publish entry returns 401/403 | The plugin user in `datacite_global.api_user` lacks the `system.api.publish.post` right |
447
482
| Linked-object field resolves to `undefined`| Either the plugin user can't read the linked objecttype, or the path is wrong. Uncomment the linked-object `console.error` lines to see the failed fetch URL. |
483
+
| Nested table field (e.g. `tierart`) resolves to `undefined`| The field is stored under `_nested:<objecttype>__<fieldname>` in the payload. Make sure the path continues *into* the nested rows (e.g. `objecttype.nestedField.linkedType.linkedType.textField`). |
448
484
| Admin UI shows raw l10n keys instead of labels | New parameter was added to `manifest.master.yml` but not to `datacite-loca.csv`|
0 commit comments