diff --git a/app/controllers/api/places_controller.rb b/app/controllers/api/places_controller.rb index 35016d7..5048b71 100644 --- a/app/controllers/api/places_controller.rb +++ b/app/controllers/api/places_controller.rb @@ -1,3 +1,7 @@ class Api::PlacesController < Api::BaseController + # Search attributes search_attributes :name, :city, :state, :country + + # Preloads + preloads :qualifications, only: :show end diff --git a/app/controllers/api/value_lists_controller.rb b/app/controllers/api/value_lists_controller.rb index a9f0bb4..abdb859 100644 --- a/app/controllers/api/value_lists_controller.rb +++ b/app/controllers/api/value_lists_controller.rb @@ -2,6 +2,9 @@ class Api::ValueListsController < Api::BaseController # Search columns search_attributes :object, :group, :human_name, :comment + # Preloads + preloads :qualifications, only: [:index, :show] + def objects_list objects_list = ValueList .all @@ -26,18 +29,6 @@ def groups_list } end - def authorized_vocabularies - vocabs_list = ValueList - .where.not(authorized_vocabulary: nil) - .order(:authorized_vocabulary) - .distinct - .pluck(:authorized_vocabulary) - - render json: { - authorized_vocabularies: vocabs_list - } - end - protected def apply_filters(query) diff --git a/app/models/place.rb b/app/models/place.rb index 37dc510..04bacb0 100644 --- a/app/models/place.rb +++ b/app/models/place.rb @@ -1,5 +1,6 @@ class Place < ApplicationRecord # Includes + include Qualifiable include Recordable include Search::Place @@ -11,6 +12,7 @@ class Place < ApplicationRecord # Resourceable parameters allow_params :name, :place_type, :lat, :long, :city, :state, :country, :url, :database_value, :notes, :same_as, - :part_of, locations_attributes: [:id, :locateable_id, :locateable_type, :description, :certainty, - :notes, :_destroy, qualifications_attributes: [:id, :value_list_id, :notes, :persistent, :_destroy]] + :part_of, :authorized_vocabulary_url, + locations_attributes: [:id, :locateable_id, :locateable_type, :description, :certainty, :notes, :_destroy, + qualifications_attributes: [:id, :value_list_id, :notes, :persistent, :_destroy]] end diff --git a/app/models/value_list.rb b/app/models/value_list.rb index 419403d..1965f3e 100644 --- a/app/models/value_list.rb +++ b/app/models/value_list.rb @@ -1,10 +1,8 @@ class ValueList < ApplicationRecord # Includes + include Qualifiable include Recordable - # Relationships - has_many :qualifications - # Resource params allow_params :authorized_vocabulary, :comment, :object, :group, :human_name, :authorized_vocabulary_url, :database_value diff --git a/app/serializers/people_serializer.rb b/app/serializers/people_serializer.rb index b049260..da70973 100644 --- a/app/serializers/people_serializer.rb +++ b/app/serializers/people_serializer.rb @@ -3,9 +3,8 @@ class PeopleSerializer < BaseSerializer include LocateableSerializer index_attributes :id, :name, :display_name, :person_type, qualifications: QualificationsSerializer - show_attributes :id, :name, :display_name, :person_type, :authorized_vocabulary, :url, :database_value, - :comment, :part_of, :same_as, :artist_birth_date, :artist_death_date, :years_active, - qualifications: QualificationsSerializer + show_attributes :id, :name, :display_name, :person_type, :url, :database_value, :comment, :part_of, :same_as, + :artist_birth_date, :artist_death_date, :years_active, qualifications: QualificationsSerializer # For unauthenticated users, only display participations for artworks that are published show_attributes(:participations) do |person, current_user| diff --git a/app/serializers/places_serializer.rb b/app/serializers/places_serializer.rb index 036f6cb..d114eb7 100644 --- a/app/serializers/places_serializer.rb +++ b/app/serializers/places_serializer.rb @@ -1,6 +1,7 @@ class PlacesSerializer < BaseSerializer index_attributes :id, :name, :place_type, :lat, :long, :city, :state, :country - show_attributes :id, :name, :place_type, :lat, :long, :city, :state, :country, :url, :database_value, :notes, :same_as, :part_of + show_attributes :id, :name, :place_type, :lat, :long, :city, :state, :country, :url, :database_value, :notes, + :same_as, :part_of, :authorized_vocabulary_url, qualifications: QualificationsSerializer # For unauthenticated users, only display locations for artworks that are published show_attributes(:locations) do |place, current_user| diff --git a/app/serializers/value_lists_serializer.rb b/app/serializers/value_lists_serializer.rb index 1711588..513a2d9 100644 --- a/app/serializers/value_lists_serializer.rb +++ b/app/serializers/value_lists_serializer.rb @@ -1,7 +1,7 @@ class ValueListsSerializer < BaseSerializer - index_attributes :id, :object, :group, :human_name, :authorized_vocabulary, :authorized_vocabulary_url, - :database_value, :comment, :qualifications_count + index_attributes :id, :object, :group, :human_name, :authorized_vocabulary_url, :database_value, :comment, + :qualifications_count, qualifications: QualificationsSerializer - show_attributes :id, :object, :group, :human_name, :authorized_vocabulary, :authorized_vocabulary_url, - :database_value, :comment, :qualifications_count + show_attributes :id, :object, :group, :human_name, :authorized_vocabulary_url, :database_value, :comment, + :qualifications_count, qualifications: QualificationsSerializer end diff --git a/client/src/components/PersonForm.js b/client/src/components/PersonForm.js index 077602b..ca319bd 100644 --- a/client/src/components/PersonForm.js +++ b/client/src/components/PersonForm.js @@ -63,12 +63,11 @@ const PersonForm = (props: Props) => ( required={props.isRequired('years_active')} value={props.item.years_active || ''} /> - { onChange={props.onTextInputChange.bind(this, 'url')} value={props.item.url || ''} /> + + { - const [authorizedVocabulariesList, setAuthorizedVocabulariesList] = useState([]); - - useEffect(() => { - ValueLists.getAuthorizedVocabulariesList().then(({ data }) => { - setAuthorizedVocabulariesList(data.authorized_vocabularies.map((vocab) => ({ - key: vocab, - value: vocab, - text: vocab - }))); - }); - }, []); - - return ( - - ( + + + + 0} + error={props.isError('object')} + label={props.t('ValueList.labels.objectName')} + onChange={props.onTextInputChange.bind(this, 'object')} + required={props.isRequired('object')} + value={props.item.object || ''} /> - - 0} - error={props.isError('object')} - label={props.t('ValueList.labels.objectName')} - onChange={props.onTextInputChange.bind(this, 'object')} - required={props.isRequired('object')} - value={props.item.object || ''} - /> - 0} - error={props.isError('group')} - label={props.t('ValueList.labels.groupName')} - onChange={props.onTextInputChange.bind(this, 'group')} - required={props.isRequired('group')} - value={props.item.group || ''} - /> - - - - - - - - - { props.children } - - ); -}; + 0} + error={props.isError('group')} + label={props.t('ValueList.labels.groupName')} + onChange={props.onTextInputChange.bind(this, 'group')} + required={props.isRequired('group')} + value={props.item.group || ''} + /> + + + + + + + { props.children } + +); export default withTranslation()(ValueListModal); diff --git a/client/src/i18n/en.json b/client/src/i18n/en.json index 89e0bdd..87c2909 100644 --- a/client/src/i18n/en.json +++ b/client/src/i18n/en.json @@ -396,7 +396,9 @@ "notes": "Notes", "state": "State", "type": "Type", - "url": "Authorized Vocabulary URL" + "url": "Institutional URL", + "authorizedVocabulary": "Authorized vocabulary", + "authorizedVocabularyUrl": "Authorized vocabulary URL" }, "locations": { "columns": { diff --git a/client/src/pages/admin/ValueLists.css b/client/src/pages/admin/ValueLists.css index e69de29..7bd0619 100644 --- a/client/src/pages/admin/ValueLists.css +++ b/client/src/pages/admin/ValueLists.css @@ -0,0 +1,4 @@ +.value-lists > .tab > .ui.menu { + overflow: auto; + padding-bottom: 0.25em; +} \ No newline at end of file diff --git a/client/src/pages/admin/ValueLists.js b/client/src/pages/admin/ValueLists.js index 8362902..39fbddc 100644 --- a/client/src/pages/admin/ValueLists.js +++ b/client/src/pages/admin/ValueLists.js @@ -1,15 +1,13 @@ // @flow import type { EditContainerProps } from '@performant-software/shared-components/types'; -import React, { useEffect, useState } from 'react'; +import React, { useCallback, useEffect, useMemo, useState } from 'react'; +import { Container, Header, Tab } from 'semantic-ui-react'; +import _ from 'underscore'; import ValueListsTable from '../../components/ValueListsTable'; import withMenuBar from '../../hooks/MenuBar'; import ValueListsService from '../../services/ValueLists'; -import { - Container, - Header, - Tab -} from 'semantic-ui-react'; +import './ValueLists.css'; import type { ValueList as ValueListType } from '../../types/ValueList'; import type { Translateable } from '../../types/Translateable'; @@ -21,24 +19,49 @@ type Props = EditContainerProps & Translateable & { const ValueLists = (props: Props) => { const [objectsList, setObjectsList] = useState([]); - useEffect(() => { - ValueListsService.getObjectsList() - .then((response) => setObjectsList(response.data.objects)); - }, []); - - const panes = objectsList.map((objectName) => ({ + /** + * Memo-izes the tab panes. + * + * @type {{menuItem: *, render: function(): *}[]} + */ + const panes = useMemo(() => objectsList.map((objectName) => ({ menuItem: objectName, render: () => ( - - + + ) - })); + })), [objectsList]); + + /** + * Sets the tab list on the state. + */ + useEffect(() => { + ValueListsService + .getObjectsList() + .then((response) => setObjectsList(response.data.objects)) + }, []); return ( - -
{props.t('Admin.menu.valueLists')}
- + +
+ { props.t('Admin.menu.valueLists') } +
+
); }; diff --git a/client/src/transforms/Place.js b/client/src/transforms/Place.js index 83d1c92..4974411 100644 --- a/client/src/transforms/Place.js +++ b/client/src/transforms/Place.js @@ -3,6 +3,7 @@ import _ from 'underscore'; import BaseTransform from './BaseTransform'; import Locations from './Locations'; +import Qualifications from './Qualifications'; import type { Place as PlaceType } from '../types/Place'; @@ -28,7 +29,8 @@ class Place extends BaseTransform { 'database_value', 'notes', 'same_as', - 'part_of' + 'part_of', + 'authorized_vocabulary_url' ]; } @@ -59,7 +61,8 @@ class Place extends BaseTransform { return { place: { ..._.pick(place, this.getPayloadKeys()), - ...Locations.toPayload(place) + ...Locations.toPayload(place), + ...Qualifications.toPayload(place) } }; } diff --git a/client/src/transforms/ValueList.js b/client/src/transforms/ValueList.js index ea0cb66..1e456bb 100644 --- a/client/src/transforms/ValueList.js +++ b/client/src/transforms/ValueList.js @@ -2,6 +2,7 @@ import _ from 'underscore'; import BaseTransform from './BaseTransform'; +import Qualifications from './Qualifications'; import type { ValueList as ValueListType } from '../types/ValueList'; @@ -52,12 +53,16 @@ class ValueList extends BaseTransform { /** * Returns the value_list object to be sent to the server on POST/PUT requests. * - * @param option + * @param valueList * * @returns {*} */ - toPayload(option: ValueListType) { - return { value_list: _.pick(option, this.PAYLOAD_KEYS) }; + toPayload(valueList: ValueListType) { + return { + value_list: { + ..._.pick(valueList, this.PAYLOAD_KEYS), + ...Qualifications.toPayload(valueList) + }}; } } diff --git a/client/src/types/Place.js b/client/src/types/Place.js index 9c1c236..8d5295b 100644 --- a/client/src/types/Place.js +++ b/client/src/types/Place.js @@ -14,6 +14,7 @@ export type Place = { notes: string, same_as: number, part_of: number, + authorized_vocabulary_url, locations: Array }; diff --git a/config/routes.rb b/config/routes.rb index 504e13f..430f919 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -19,7 +19,6 @@ resources :visual_contexts, except: :index resources :users - get 'authorized_vocabularies' => 'value_lists#authorized_vocabularies' get 'value_lists_objects', to: 'value_lists#objects_list' get 'value_lists_groups', to: 'value_lists#groups_list' diff --git a/db/data/20241211163139_create_authorized_vocabulary_value_lists.rb b/db/data/20241211163139_create_authorized_vocabulary_value_lists.rb new file mode 100644 index 0000000..bce9f89 --- /dev/null +++ b/db/data/20241211163139_create_authorized_vocabulary_value_lists.rb @@ -0,0 +1,41 @@ +# frozen_string_literal: true + +class CreateAuthorizedVocabularyValueLists < ActiveRecord::Migration[7.0] + def up + execute <<-SQL.squish + INSERT INTO value_lists (object, "group", human_name, created_at, updated_at) + SELECT DISTINCT 'General', 'Authorized Vocabulary', authorized_vocabulary, current_timestamp, current_timestamp + FROM value_lists + WHERE authorized_vocabulary IS NOT NULL + UNION + SELECT DISTINCT 'General', 'Authorized Vocabulary', authorized_vocabulary, current_timestamp, current_timestamp + FROM people + WHERE authorized_vocabulary IS NOT NULL + ORDER BY authorized_vocabulary + SQL + + execute <<-SQL.squish + INSERT INTO qualifications (qualifiable_id, qualifiable_type, value_list_id) + SELECT value_lists.id, 'ValueList', v.id + FROM value_lists + JOIN value_lists v ON v.human_name = value_lists.authorized_vocabulary + AND v.object = 'General' + AND v.group = 'Authorized Vocabulary' + WHERE value_lists.authorized_vocabulary IS NOT NULL + SQL + + execute <<-SQL.squish + INSERT INTO qualifications (qualifiable_id, qualifiable_type, value_list_id) + SELECT people.id, 'Person', value_lists.id + FROM people + JOIN value_lists ON value_lists.human_name = people.authorized_vocabulary + AND value_lists.object = 'General' + AND value_lists.group = 'Authorized Vocabulary' + WHERE people.authorized_vocabulary IS NOT NULL + SQL + end + + def down + raise ActiveRecord::IrreversibleMigration + end +end diff --git a/db/data/20241211173335_reset_value_list_qualifications_count.rb b/db/data/20241211173335_reset_value_list_qualifications_count.rb new file mode 100644 index 0000000..193409d --- /dev/null +++ b/db/data/20241211173335_reset_value_list_qualifications_count.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +class ResetValueListQualificationsCount < ActiveRecord::Migration[7.0] + def up + execute <<-SQL.squish + WITH qualification_counts AS ( + SELECT qualifications.value_list_id, COUNT(*) AS qualifications_count + FROM qualifications + GROUP BY qualifications.value_list_id + ) + UPDATE value_lists + SET qualifications_count = qualification_counts.qualifications_count + FROM qualification_counts + WHERE qualification_counts.value_list_id = value_lists.id + SQL + end + + def down + raise ActiveRecord::IrreversibleMigration + end +end diff --git a/db/data_schema.rb b/db/data_schema.rb index 32c8bb4..b821825 100644 --- a/db/data_schema.rb +++ b/db/data_schema.rb @@ -1 +1 @@ -DataMigrate::Data.define(version: 20241127155125) +DataMigrate::Data.define(version: 20241211173335) diff --git a/db/migrate/20241211200500_remove_value_lists_authorized_vocabulary.rb b/db/migrate/20241211200500_remove_value_lists_authorized_vocabulary.rb new file mode 100644 index 0000000..7874624 --- /dev/null +++ b/db/migrate/20241211200500_remove_value_lists_authorized_vocabulary.rb @@ -0,0 +1,5 @@ +class RemoveValueListsAuthorizedVocabulary < ActiveRecord::Migration[7.0] + def change + remove_column :value_lists, :authorized_vocabulary + end +end diff --git a/db/migrate/20241211201922_remove_people_authorized_vocabulary.rb b/db/migrate/20241211201922_remove_people_authorized_vocabulary.rb new file mode 100644 index 0000000..3813bba --- /dev/null +++ b/db/migrate/20241211201922_remove_people_authorized_vocabulary.rb @@ -0,0 +1,5 @@ +class RemovePeopleAuthorizedVocabulary < ActiveRecord::Migration[7.0] + def change + remove_column :people, :authorized_vocabulary + end +end diff --git a/db/migrate/20241211202543_add_authorized_vocabulary_url_to_places.rb b/db/migrate/20241211202543_add_authorized_vocabulary_url_to_places.rb new file mode 100644 index 0000000..83c1684 --- /dev/null +++ b/db/migrate/20241211202543_add_authorized_vocabulary_url_to_places.rb @@ -0,0 +1,5 @@ +class AddAuthorizedVocabularyUrlToPlaces < ActiveRecord::Migration[7.0] + def change + add_column :places, :authorized_vocabulary_url, :string + end +end diff --git a/db/schema.rb b/db/schema.rb index 560dad0..a37b980 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema[7.0].define(version: 2024_12_03_181033) do +ActiveRecord::Schema[7.0].define(version: 2024_12_11_202543) do # These are extensions that must be enabled in order to support this database enable_extension "pg_stat_statements" enable_extension "plpgsql" @@ -180,7 +180,6 @@ t.string "name" t.string "display_name" t.string "person_type" - t.string "authorized_vocabulary" t.string "url" t.string "database_value" t.string "comment" @@ -236,6 +235,7 @@ t.datetime "updated_at", null: false t.bigint "created_by_id" t.bigint "updated_by_id" + t.string "authorized_vocabulary_url" t.index ["created_by_id"], name: "index_places_on_created_by_id" t.index ["updated_by_id"], name: "index_places_on_updated_by_id" end @@ -288,7 +288,6 @@ t.string "human_name" t.string "authorized_vocabulary_url" t.text "comment" - t.string "authorized_vocabulary" t.string "airtable_id" t.datetime "airtable_timestamp", precision: nil t.datetime "created_at", null: false