From 57264d93b1b2508c7eb75a5507086ef2b8c0436a Mon Sep 17 00:00:00 2001 From: Mike Bryant Date: Fri, 26 Jul 2024 17:02:11 +0100 Subject: [PATCH] Rework various forms and integrate type metadata into forms This is done by extending the existing form config system, now called field hints, which is integrated into various action builders for editing and saving forms. --- Gruntfile.js | 9 + .../admin/app/assets/css/admin/_admin.scss | 35 +- modules/admin/app/assets/css/dmeditor.scss | 27 + modules/admin/app/assets/js/admin.js | 6 +- .../datasets/components/_manager-convert.vue | 1 - modules/admin/app/assets/js/dmeditor/api.ts | 23 +- .../admin/app/assets/js/dmeditor/app.spec.ts | 10 +- modules/admin/app/assets/js/dmeditor/app.vue | 3 + .../js/dmeditor/components/_list-et.vue | 126 +- .../dmeditor/components/_modal-et-editor.vue | 48 +- .../dmeditor/components/_modal-fm-editor.vue | 126 +- .../app/assets/js/lib/vue-i18n.global.js | 6690 +++++++++++++++++ .../authorities/HistoricalAgents.scala | 20 +- .../app/controllers/countries/Countries.scala | 37 +- .../datamodel/EntityTypeMetadataApi.scala | 2 +- .../controllers/datasets/HarvestConfigs.scala | 6 +- .../admin/app/controllers/groups/Groups.scala | 6 +- .../institutions/Repositories.scala | 35 +- .../app/controllers/keywords/Concepts.scala | 16 +- .../controllers/sets/AuthoritativeSets.scala | 33 +- .../controllers/units/DocumentaryUnits.scala | 54 +- .../controllers/virtual/VirtualUnits.scala | 68 +- .../vocabularies/Vocabularies.scala | 20 +- modules/admin/app/views/admin/Helpers.scala | 5 + .../admin/authoritativeSet/create.scala.html | 7 +- .../admin/authoritativeSet/edit.scala.html | 4 +- .../admin/authoritativeSet/form.scala.html | 12 +- .../admin/common/validationErrors.scala.html | 34 + .../app/views/admin/concept/create.scala.html | 4 +- .../app/views/admin/concept/edit.scala.html | 4 +- .../app/views/admin/concept/form.scala.html | 24 +- .../views/admin/concept/inlineName.scala.html | 11 - .../admin/concept/inlineNameSet.scala.html | 16 - .../app/views/admin/country/create.scala.html | 10 +- .../app/views/admin/country/edit.scala.html | 8 +- .../app/views/admin/country/form.scala.html | 16 +- .../app/views/admin/country/show.scala.html | 8 +- .../views/admin/datamodel/editor.scala.html | 26 +- .../admin/documentaryUnit/create.scala.html | 6 +- .../createDescription.scala.html | 4 +- .../documentaryUnit/description.scala.html | 3 +- .../admin/documentaryUnit/edit.scala.html | 4 +- .../editDescription.scala.html | 4 +- .../admin/documentaryUnit/form.scala.html | 4 +- .../admin/documentaryUnit/show.scala.html | 106 +- .../admin/historicalAgent/create.scala.html | 4 +- .../historicalAgent/description.scala.html | 3 +- .../admin/historicalAgent/edit.scala.html | 4 +- .../admin/historicalAgent/form.scala.html | 4 +- .../admin/historicalAgent/show.scala.html | 13 +- .../adminSinglePageAppLayout.scala.html | 1 + .../admin/repository/addressForm.scala.html | 2 +- .../views/admin/repository/create.scala.html | 4 +- .../admin/repository/description.scala.html | 3 +- .../repository/descriptionForm.scala.html | 20 +- .../views/admin/repository/edit.scala.html | 4 +- .../views/admin/repository/form.scala.html | 4 +- .../admin/repository/inlineGeoname.scala.html | 22 +- .../views/admin/repository/show.scala.html | 12 +- .../views/admin/virtualUnit/create.scala.html | 4 +- .../virtualUnit/createDescription.scala.html | 4 +- .../views/admin/virtualUnit/edit.scala.html | 4 +- .../virtualUnit/editDescription.scala.html | 4 +- .../views/admin/virtualUnit/form.scala.html | 4 +- .../views/admin/vocabulary/create.scala.html | 8 +- .../views/admin/vocabulary/edit.scala.html | 8 +- .../views/admin/vocabulary/form.scala.html | 12 +- .../formHelpers/enumChoiceInput.scala.html | 22 +- .../formHelpers/fieldTemplate.scala.html | 6 +- .../fieldTemplateWithHints.scala.html | 28 + .../formHelpers/inlineDateSet.scala.html | 18 +- .../formHelpers/inlineNameSet.scala.html | 18 +- .../formHelpers/inlineTextSet.scala.html | 21 +- modules/admin/conf/datamodel.routes | 2 +- modules/admin/conf/messages | 3 + .../api/app/controllers/api/v1/ApiV1.scala | 2 + .../controllers/base/CoreActionBuilders.scala | 3 + .../scala/controllers/generic/Create.scala | 34 +- .../scala/controllers/generic/Creator.scala | 63 +- .../scala/controllers/generic/Update.scala | 37 +- .../main/scala}/forms/FormFieldHints.scala | 29 +- .../main/scala/forms/mappings/package.scala | 38 + .../core/src/main/scala/forms/package.scala | 24 - .../main/scala/models/AuthoritativeSet.scala | 3 +- .../scala/models/ConceptDescription.scala | 2 +- .../core/src/main/scala/models/Country.scala | 18 +- .../main/scala/models/DocumentaryUnit.scala | 3 + .../models/DocumentaryUnitDescription.scala | 2 +- .../core/src/main/scala/models/Entity.scala | 3 +- .../scala}/models/EntityTypeMetadata.scala | 0 .../main/scala}/models/FieldMetadata.scala | 2 +- .../main/scala/models/FieldMetadataSet.scala | 68 + .../core/src/main/scala/models/Group.scala | 3 +- .../models/HistoricalAgentDescription.scala | 3 +- .../core/src/main/scala/models/IsadG.scala | 12 + .../core/src/main/scala/models/Isdiah.scala | 7 +- .../scala/models/RepositoryDescription.scala | 2 +- .../src/main/scala/models/Vocabulary.scala | 3 +- .../datamodel/EntityTypeMetadataService.scala | 5 + .../SqlEntityTypeMetadataService.scala | 19 +- .../portal/app/assets/css/portal/_layout.scss | 3 +- .../portal/app/assets/css/portal/_style.scss | 12 + .../app/controllers/AppComponents.scala | 41 +- .../portal/base/PortalController.scala | 2 + .../portal/app/models/FieldMetadataSet.scala | 30 - .../portal/app/views/common/footer.scala.html | 1 + modules/portal/app/views/dataModel.scala.html | 141 +- .../views/formHelpers/lineInput.scala.html | 4 +- modules/portal/conf/messages | 2 + .../test/models/FieldMetadataSetSpec.scala | 20 +- package-lock.json | 104 + package.json | 1 + .../SqlEntityTypeMetadataServiceSpec.scala | 7 +- 113 files changed, 7957 insertions(+), 783 deletions(-) create mode 100644 modules/admin/app/assets/js/lib/vue-i18n.global.js create mode 100644 modules/admin/app/views/admin/common/validationErrors.scala.html delete mode 100644 modules/admin/app/views/admin/concept/inlineName.scala.html delete mode 100644 modules/admin/app/views/admin/concept/inlineNameSet.scala.html create mode 100644 modules/admin/app/views/formHelpers/fieldTemplateWithHints.scala.html rename modules/{portal/app => core/src/main/scala}/forms/FormFieldHints.scala (75%) create mode 100644 modules/core/src/main/scala/forms/mappings/package.scala rename modules/{portal/app => core/src/main/scala}/models/EntityTypeMetadata.scala (100%) rename modules/{portal/app => core/src/main/scala}/models/FieldMetadata.scala (100%) create mode 100644 modules/core/src/main/scala/models/FieldMetadataSet.scala rename modules/{portal/app => core/src/main/scala}/services/datamodel/EntityTypeMetadataService.scala (95%) rename modules/{portal/app => core/src/main/scala}/services/datamodel/SqlEntityTypeMetadataService.scala (84%) delete mode 100644 modules/portal/app/models/FieldMetadataSet.scala diff --git a/Gruntfile.js b/Gruntfile.js index 96ace8aafa..87c89e0e52 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -205,6 +205,15 @@ module.exports = function (grunt) { ], dest: paths.adminJsLib }, + { + expand: true, + cwd: 'node_modules/vue-i18n/dist', + src: [ + // not using runtime 'cos we need the messages compiler + 'vue-i18n.global.js' + ], + dest: paths.adminJsLib + }, ], }, }, diff --git a/modules/admin/app/assets/css/admin/_admin.scss b/modules/admin/app/assets/css/admin/_admin.scss index d1a96563e5..be870e3543 100644 --- a/modules/admin/app/assets/css/admin/_admin.scss +++ b/modules/admin/app/assets/css/admin/_admin.scss @@ -127,8 +127,11 @@ body > .admin-content > .flash { padding-top: $margin-md; } -.add-address { +.add-address, +.add-concept +{ float: right; + margin-bottom: $margin-md; } .admin-help-notice { @@ -315,12 +318,18 @@ pre.code-format { margin-bottom: $margin-md; } -.add-description { - margin-bottom: $margin-md; - @include clearfix(); - a { - float: right; +.control-elements { + .add-inline-element:not(.btn) { + display: block; + background-color: $gray-100; + border-bottom: 1px solid darken($gray-100, 5%); + padding: 0 $margin-xs; + color: $gray-700; + } + .add-inline-element:hover:not(.btn) { + background-color: darken($gray-100, 5%); } + } .inline-element-block-controls { @@ -436,6 +445,20 @@ pre.code-format { } } +// Metadata validation +.metadata-validation { + summary { + font-size: $font-size-lg; + @extend .alert, .alert-warning; + } + .validation-errors { + @extend .alert, .alert-danger; + } + .validation-warnings { + @extend .alert, .alert-warning; + } +} + // Vue Single-page applications %expanding-row { display: flex; diff --git a/modules/admin/app/assets/css/dmeditor.scss b/modules/admin/app/assets/css/dmeditor.scss index 179e9a1ada..addeafbddb 100644 --- a/modules/admin/app/assets/css/dmeditor.scss +++ b/modules/admin/app/assets/css/dmeditor.scss @@ -12,3 +12,30 @@ $active-table-row: #e7f1ff; overflow: auto; flex-basis: 0; } + +.section { + background-color: $gray-100; + font-weight: bold; +} + +.fm-list td.fm-actions { + width: 5rem; +} + +.fm-see-also a + a { + display: inline-block; +} + +.fm-usage { + white-space: nowrap; + +} + +.fm-list td:last-child a { + margin-left: $margin-xs; +} + +#delete-metadata { + margin-right: auto; +} + diff --git a/modules/admin/app/assets/js/admin.js b/modules/admin/app/assets/js/admin.js index 350c6c4d38..c8cb3b104f 100644 --- a/modules/admin/app/assets/js/admin.js +++ b/modules/admin/app/assets/js/admin.js @@ -188,7 +188,7 @@ jQuery(function ($) { event.preventDefault(); var container = $(event.target).closest(".inline-formset"); - var set = container.children(".inline-element-list"); + var set = container.children(".control-elements").children(".inline-element-list").first(); var prefix = container.data("prefix"); if (!prefix) { throw "No prefix found for formset"; @@ -212,6 +212,10 @@ jQuery(function ($) { // And a help popover addPopover($elem.find("textarea,input")); + + // Focus the first input + $elem.find("input,textarea").first().focus(); + }).on("click", ".remove-inline-element", function (event) { event.preventDefault(); $(this) diff --git a/modules/admin/app/assets/js/datasets/components/_manager-convert.vue b/modules/admin/app/assets/js/datasets/components/_manager-convert.vue index 3f180bf347..062ead04e0 100644 --- a/modules/admin/app/assets/js/datasets/components/_manager-convert.vue +++ b/modules/admin/app/assets/js/datasets/components/_manager-convert.vue @@ -20,7 +20,6 @@ import _takeWhile from 'lodash/takeWhile'; import _find from 'lodash/find'; import {DataTransformation} from "../types"; - let initialConvertState = function (config) { return { ingesting: {}, diff --git a/modules/admin/app/assets/js/dmeditor/api.ts b/modules/admin/app/assets/js/dmeditor/api.ts index e7a92aba19..9c2bfc3179 100644 --- a/modules/admin/app/assets/js/dmeditor/api.ts +++ b/modules/admin/app/assets/js/dmeditor/api.ts @@ -1,5 +1,6 @@ import axios from "axios"; import { + EntityType, EntityTypeMetadata, EntityTypeMetadataInfo, FieldMetadata, FieldMetadataInfo, FieldMetadataTemplates, @@ -30,36 +31,36 @@ export default class EntityTypeMetadataApi { withCredentials: true, }).then(r => r.data); } - list(): Promise> { - return EntityTypeMetadataApi.call>(this.service.list(), {}); + list(): Promise> { + return EntityTypeMetadataApi.call>(this.service.list(), {}); } - get(entityType: string): Promise> { - return EntityTypeMetadataApi.call>(this.service.list(), {}, {entityType}); + get(entityType: EntityType): Promise> { + return EntityTypeMetadataApi.call>(this.service.list(), {}, {entityType}); } - save(entityType: string, data: EntityTypeMetadataInfo): Promise { + save(entityType: EntityType, data: EntityTypeMetadataInfo): Promise { return EntityTypeMetadataApi.call(this.service.save(entityType), data); } - delete(entityType: string): Promise { + delete(entityType: EntityType): Promise { return EntityTypeMetadataApi.call(this.service.delete(entityType)); } - listFields(entityType?: string): Promise> { + listFields(entityType?: EntityType): Promise> { let params = entityType ? {entityType} : {}; - return EntityTypeMetadataApi.call>(this.service.listFields(), {}, params) + return EntityTypeMetadataApi.call>(this.service.listFields(), {}, params) } - getField(entityType: string, id: string): Promise { + getField(entityType: EntityType, id: string): Promise { return EntityTypeMetadataApi.call(this.service.getField(entityType, id)); } - saveField(entityType: string, id: string, data: FieldMetadataInfo): Promise { + saveField(entityType: EntityType, id: string, data: FieldMetadataInfo): Promise { return EntityTypeMetadataApi.call(this.service.saveField(entityType, id), data); } - deleteField(entityType: string, id: string): Promise { + deleteField(entityType: EntityType, id: string): Promise { return EntityTypeMetadataApi.call(this.service.deleteField(entityType, id)); } diff --git a/modules/admin/app/assets/js/dmeditor/app.spec.ts b/modules/admin/app/assets/js/dmeditor/app.spec.ts index 279bfb3ada..563deec623 100644 --- a/modules/admin/app/assets/js/dmeditor/app.spec.ts +++ b/modules/admin/app/assets/js/dmeditor/app.spec.ts @@ -1,10 +1,14 @@ -import {mount, flushPromises} from '@vue/test-utils'; +import {mount, flushPromises,config} from '@vue/test-utils'; import App from './app.vue'; import ListEt from './components/_list-et.vue'; import EntityTypeMetadataApi from "./api"; jest.mock('./api'); +config.global.mocks = { + '$t': (msg) => msg, // return i18n key +} + const defaultInit = { props: { service: {}, @@ -55,7 +59,9 @@ test('deleting a field', async () => { expect(wrapper.find(".fm-list").exists()).toBe(true); expect(wrapper.find("#fm-Country-history").exists()).toBe(true); - await wrapper.find("#fm-Country-history .fm-delete").trigger("click"); + await wrapper.find("#fm-Country-history .fm-edit").trigger("click"); + expect(wrapper.find("#delete-metadata").exists()).toBe(true); + await wrapper.find("#delete-metadata").trigger("click"); expect(wrapper.find(".confirm-delete-field-metadata").exists()).toBe(true); await wrapper.find(".confirm-delete-field-metadata button.accept").trigger("click"); await flushPromises(); diff --git a/modules/admin/app/assets/js/dmeditor/app.vue b/modules/admin/app/assets/js/dmeditor/app.vue index d1947272cd..9f90a1bd61 100644 --- a/modules/admin/app/assets/js/dmeditor/app.vue +++ b/modules/admin/app/assets/js/dmeditor/app.vue @@ -1,6 +1,9 @@ - - diff --git a/modules/admin/app/assets/js/dmeditor/components/_modal-fm-editor.vue b/modules/admin/app/assets/js/dmeditor/components/_modal-fm-editor.vue index 447b9cf9b1..3872723080 100644 --- a/modules/admin/app/assets/js/dmeditor/components/_modal-fm-editor.vue +++ b/modules/admin/app/assets/js/dmeditor/components/_modal-fm-editor.vue @@ -1,21 +1,25 @@