Bugzilla – Attachment 161580 Details for
Bug 35919
Add record sources CRUD
Home
|
New
|
Browse
|
Search
|
[?]
|
Reports
|
Help
|
New Account
|
Log In
[x]
|
Forgot Password
Login:
[x]
[patch]
Bug 35919: Add record sources admin page
Bug-35919-Add-record-sources-admin-page.patch (text/plain), 22.40 KB, created by
Matt Blenkinsop
on 2024-01-29 10:35:40 UTC
(
hide
)
Description:
Bug 35919: Add record sources admin page
Filename:
MIME Type:
Creator:
Matt Blenkinsop
Created:
2024-01-29 10:35:40 UTC
Size:
22.40 KB
patch
obsolete
>From 6d3710a5e8143cdf51d315c364d9702111b6539b Mon Sep 17 00:00:00 2001 >From: Tomas Cohen Arazi <tomascohen@theke.io> >Date: Wed, 24 Jan 2024 16:18:13 -0300 >Subject: [PATCH] Bug 35919: Add record sources admin page > >This patch introduces a Vue.js based record sources managing page. To >test it: > >1. Apply this patch >2. Build the Vue.js stuff: > $ ktd --shell > k$ yarn js:build > k$ restart_all >3. On the staff interface, go to Administration > Record sources >4. Play with the interface and the offered actions >=> SUCCESS: Things go well >5. Sign off :-D > >Signed-off-by: Tomas Cohen Arazi <tomascohen@theke.io> >Signed-off-by: Matt Blenkinsop <matt.blenkinsop@ptfs-europe.com> >--- > admin/record_sources.pl | 37 +++++ > debian/templates/apache-shared-intranet.conf | 2 + > .../prog/en/modules/admin/admin-home.tt | 4 + > .../prog/en/modules/admin/record_sources.tt | 34 +++++ > .../components/Admin/RecordSources/Edit.vue | 111 ++++++++++++++ > .../components/Admin/RecordSources/List.vue | 139 ++++++++++++++++++ > .../components/Admin/RecordSources/Main.vue | 34 +++++ > .../js/vue/fetch/record_sources-api-client.js | 59 ++++++++ > .../js/vue/modules/admin/record_sources.ts | 54 +++++++ > .../js/vue/routes/admin/record_sources.js | 40 +++++ > webpack.config.js | 1 + > 11 files changed, 515 insertions(+) > create mode 100755 admin/record_sources.pl > create mode 100644 koha-tmpl/intranet-tmpl/prog/en/modules/admin/record_sources.tt > create mode 100644 koha-tmpl/intranet-tmpl/prog/js/vue/components/Admin/RecordSources/Edit.vue > create mode 100644 koha-tmpl/intranet-tmpl/prog/js/vue/components/Admin/RecordSources/List.vue > create mode 100644 koha-tmpl/intranet-tmpl/prog/js/vue/components/Admin/RecordSources/Main.vue > create mode 100644 koha-tmpl/intranet-tmpl/prog/js/vue/fetch/record_sources-api-client.js > create mode 100644 koha-tmpl/intranet-tmpl/prog/js/vue/modules/admin/record_sources.ts > create mode 100644 koha-tmpl/intranet-tmpl/prog/js/vue/routes/admin/record_sources.js > >diff --git a/admin/record_sources.pl b/admin/record_sources.pl >new file mode 100755 >index 0000000000..9524f32dd6 >--- /dev/null >+++ b/admin/record_sources.pl >@@ -0,0 +1,37 @@ >+#!/usr/bin/perl >+ >+# Copyright 2023 Theke Solutions >+# >+# This file is part of Koha. >+# >+# Koha is free software; you can redistribute it and/or modify it >+# under the terms of the GNU General Public License as published by >+# the Free Software Foundation; either version 3 of the License, or >+# (at your option) any later version. >+# >+# Koha is distributed in the hope that it will be useful, but >+# WITHOUT ANY WARRANTY; without even the implied warranty of >+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the >+# GNU General Public License for more details. >+# >+# You should have received a copy of the GNU General Public License >+# along with Koha; if not, see <http://www.gnu.org/licenses>. >+ >+use Modern::Perl; >+ >+use CGI qw ( -utf8 ); >+use C4::Auth qw( get_template_and_user ); >+use C4::Output qw( output_html_with_http_headers ); >+ >+my $query = CGI->new; >+ >+my ( $template, $loggedinuser, $cookie ) = get_template_and_user( >+ { >+ template_name => "admin/record_sources.tt", >+ query => $query, >+ type => "intranet", >+ flagsrequired => { parameters => 'manage_record_sources' }, >+ } >+); >+ >+output_html_with_http_headers $query, $cookie, $template->output; >diff --git a/debian/templates/apache-shared-intranet.conf b/debian/templates/apache-shared-intranet.conf >index b4fe0ea226..835b0812b6 100644 >--- a/debian/templates/apache-shared-intranet.conf >+++ b/debian/templates/apache-shared-intranet.conf >@@ -13,6 +13,7 @@ ScriptAlias /search "/usr/share/koha/intranet/cgi-bin/catalogue/search.pl" > > # Protect dev package install > RewriteEngine on >+ > RewriteRule ^/cgi-bin/koha/(C4|debian|etc|installer/data|install_misc|Koha|misc|selenium|t|test|tmp|xt)/|\.PL$ /notfound [PT] > > RewriteRule ^/bib/([^\/]*)/?$ /cgi-bin/koha/catalogue/detail.pl?biblionumber=$1 [PT] >@@ -23,6 +24,7 @@ RewriteRule ^(.*)_[0-9]{2}\.[0-9]{7}\.(js|css)$ $1.$2 [L] > RewriteRule ^/cgi-bin/koha/erm/.*$ /cgi-bin/koha/erm/erm.pl [PT] > RewriteCond %{REQUEST_URI} !^/cgi-bin/koha/preservation/.*.pl$ > RewriteRule ^/cgi-bin/koha/preservation/.*$ /cgi-bin/koha/preservation/home.pl [PT] >+RewriteRule ^/cgi-bin/koha/admin/record_sources(.*)?$ /cgi-bin/koha/admin/record_sources.pl$1 [PT] > > Alias "/api" "/usr/share/koha/api" > <Directory "/usr/share/koha/api"> >diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/admin/admin-home.tt b/koha-tmpl/intranet-tmpl/prog/en/modules/admin/admin-home.tt >index 28b17808e1..8fcafa7e3f 100644 >--- a/koha-tmpl/intranet-tmpl/prog/en/modules/admin/admin-home.tt >+++ b/koha-tmpl/intranet-tmpl/prog/en/modules/admin/admin-home.tt >@@ -209,6 +209,10 @@ > <dt><a href="/cgi-bin/koha/admin/searchengine/elasticsearch/mappings.pl">Search engine configuration (Elasticsearch)</a></dt> > <dd>Manage indexes, facets, and their mappings to MARC fields and subfields</dd> > [% END %] >+ [% IF ( CAN_user_parameters_manage_record_sources ) %] >+ <dt><a href="/cgi-bin/koha/admin/record_sources">Record sources</a></dt> >+ <dd>Define record sources to import from</dd> >+ [% END %] > </dl> > [% END %] > >diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/admin/record_sources.tt b/koha-tmpl/intranet-tmpl/prog/en/modules/admin/record_sources.tt >new file mode 100644 >index 0000000000..533a171ec2 >--- /dev/null >+++ b/koha-tmpl/intranet-tmpl/prog/en/modules/admin/record_sources.tt >@@ -0,0 +1,34 @@ >+[% USE raw %] >+[% USE To %] >+[% USE Asset %] >+[% USE KohaDates %] >+[% USE TablesSettings %] >+[% USE AuthorisedValues %] >+[% SET footerjs = 1 %] >+[% PROCESS 'i18n.inc' %] >+[% INCLUDE 'doc-head-open.inc' %] >+<title> >+ Record sources › Koha >+</title> >+[% INCLUDE 'doc-head-close.inc' %] >+</head> >+ >+<body id="record_sources" class="record_sources"> >+[% WRAPPER 'header.inc' %] >+ [% INCLUDE 'prefs-admin-search.inc' %] >+[% END %] >+ >+<div id="record-source"> <!-- this is closed in intranet-bottom.inc --> >+ >+[% MACRO jsinclude BLOCK %] >+ [% INCLUDE 'calendar.inc' %] <!-- FIXME: this shouldn't be needed --> >+ [% INCLUDE 'datatables.inc' %] >+ [% INCLUDE 'columns_settings.inc' %] >+ [% INCLUDE 'js-patron-format.inc' %] >+ [% INCLUDE 'js-date-format.inc' %] >+ >+ [% Asset.js("js/vue/dist/admin/record_sources.js") | $raw %] >+ >+[% END %] >+ >+[% INCLUDE 'intranet-bottom.inc' %] >diff --git a/koha-tmpl/intranet-tmpl/prog/js/vue/components/Admin/RecordSources/Edit.vue b/koha-tmpl/intranet-tmpl/prog/js/vue/components/Admin/RecordSources/Edit.vue >new file mode 100644 >index 0000000000..6569421c15 >--- /dev/null >+++ b/koha-tmpl/intranet-tmpl/prog/js/vue/components/Admin/RecordSources/Edit.vue >@@ -0,0 +1,111 @@ >+<template> >+ <div v-if="!initialized">{{ $__("Loading") }}</div> >+ <div v-else id="record_source_edit"> >+ <h1>{{ title }}</h1> >+ <form @submit="processSubmit"> >+ <fieldset class="rows"> >+ <ol> >+ <li> >+ <label class="required" for="name"> >+ {{ $__("Name") }}: >+ </label> >+ <input id="name" v-model="row.name" required /> >+ <span class="required">{{ $__("Required") }}</span> >+ </li> >+ <li> >+ <label :for="`user_id`"> >+ {{ $__("Can be edited") }}: >+ </label> >+ <input >+ id="can_be_edited" >+ type="checkbox" >+ v-model="row.can_be_edited" >+ /> >+ </li> >+ </ol> >+ </fieldset> >+ <fieldset class="action"> >+ <input type="submit" :value="$__('Submit')" /> >+ <router-link >+ to="../record_sources" >+ role="button" >+ class="cancel" >+ >{{ $__("Cancel") }}</router-link >+ > >+ </fieldset> >+ </form> >+ </div> >+</template> >+ >+<script> >+import { inject } from "vue" >+import { RecordSourcesAPIClient } from "../../../fetch/record_sources-api-client" >+ >+export default { >+ name: "Edit", >+ setup() { >+ const { record_source } = new RecordSourcesAPIClient() >+ const { setMessage } = inject("mainStore") >+ return { >+ setMessage, >+ api: record_source, >+ } >+ }, >+ data() { >+ return { >+ row: { >+ name: "", >+ }, >+ initialized: false, >+ } >+ }, >+ methods: { >+ processSubmit(event) { >+ event.preventDefault() >+ const _hasValue = value => { >+ return value !== undefined && value !== null && value !== "" >+ } >+ if (!_hasValue(this.row.name)) return false >+ let response >+ if (this.row.record_source_id) { >+ const { record_source_id: id, ...row } = this.row >+ response = this.api >+ .update({ id, row }) >+ .then(() => this.$__("Record source updated!")) >+ } else { >+ response = this.api >+ .create({ row: this.row }) >+ .then(() => this.$__("Record source created!")) >+ } >+ return response.then(responseMessage => { >+ this.setMessage(responseMessage) >+ return this.$router.push({ path: "../record_sources" }) >+ }) >+ }, >+ }, >+ created() { >+ const { id } = this.$route.params >+ if (id !== undefined) { >+ this.api >+ .get({ >+ id, >+ }) >+ .then(response => { >+ Object.keys(response).forEach(key => { >+ this.row[key] = response[key] >+ }) >+ this.initialized = true >+ }) >+ } else { >+ this.initialized = true >+ } >+ }, >+ computed: { >+ title() { >+ if (!this.row || !this.row.record_source_id) >+ return this.$__("Add record source") >+ return this.$__("Edit '%s'").format(this.row.name) >+ }, >+ }, >+} >+</script> >diff --git a/koha-tmpl/intranet-tmpl/prog/js/vue/components/Admin/RecordSources/List.vue b/koha-tmpl/intranet-tmpl/prog/js/vue/components/Admin/RecordSources/List.vue >new file mode 100644 >index 0000000000..ad9271ad1b >--- /dev/null >+++ b/koha-tmpl/intranet-tmpl/prog/js/vue/components/Admin/RecordSources/List.vue >@@ -0,0 +1,139 @@ >+<template> >+ <div v-if="!initialized">{{ $__("Loading") }}</div> >+ <div v-else id="record_sources_list"> >+ <Toolbar> >+ <ToolbarButton >+ :to="{ name: 'Add' }" >+ icon="plus" >+ :title="$__('New record source')" >+ /> >+ </Toolbar> >+ <h1>{{ title }}</h1> >+ <div v-if="record_sources_count > 0" class="page-section"> >+ <KohaTable >+ ref="table" >+ v-bind="tableOptions" >+ @edit="doEdit" >+ @delete="doDelete" >+ ></KohaTable> >+ </div> >+ <div v-else class="dialog message"> >+ {{ $__("There are no record sources defined") }} >+ </div> >+ </div> >+</template> >+ >+<script> >+import Toolbar from "../../Toolbar.vue" >+import ToolbarButton from "../../ToolbarButton.vue" >+import { inject } from "vue" >+import { RecordSourcesAPIClient } from "../../../fetch/record_sources-api-client" >+import KohaTable from "../../KohaTable.vue" >+export default { >+ name: "List", >+ data() { >+ return { >+ title: this.$__("Record sources"), >+ tableOptions: { >+ columns: [ >+ { >+ title: this.$__("ID"), >+ data: "record_source_id", >+ searchable: true, >+ }, >+ { >+ title: this.$__("Name"), >+ data: "name", >+ searchable: true, >+ }, >+ { >+ title: __("Can be edited"), >+ data: "can_be_edited", >+ searchable: true, >+ orderable: true, >+ render: function (data, type, row, meta) { >+ return escape_str( >+ row.can_be_edited ? __("Yes") : __("No") >+ ) >+ }, >+ }, >+ ], >+ actions: { >+ "-1": ["edit", "delete"], >+ }, >+ url: "/api/v1/record_sources", >+ }, >+ initialized: false, >+ record_sources_count: 0, >+ } >+ }, >+ setup() { >+ const { setWarning, setMessage, setError, setConfirmationDialog } = >+ inject("mainStore") >+ const { record_source } = new RecordSourcesAPIClient() >+ return { >+ setWarning, >+ setMessage, >+ setError, >+ setConfirmationDialog, >+ api: record_source, >+ } >+ }, >+ beforeRouteEnter(to, from, next) { >+ next(vm => { >+ vm.getRecordSourcesCount().then(() => (vm.initialized = true)) >+ }) >+ }, >+ methods: { >+ async getRecordSourcesCount() { >+ const count = await this.api.count() >+ this.record_sources_count = count >+ }, >+ newRecordSource() { >+ this.$router.push({ path: "record_sources/add" }) >+ }, >+ doEdit(data) { >+ this.$router.push({ >+ path: `record_sources/${data.record_source_id}`, >+ props: { >+ data, >+ }, >+ }) >+ }, >+ doDelete(data, dt) { >+ this.setConfirmationDialog( >+ { >+ title: this.$__( >+ "Are you sure you want to remove this record source?" >+ ), >+ message: data.name, >+ accept_label: this.$__("Yes, remove"), >+ cancel_label: this.$__("No, do not remove"), >+ }, >+ () => { >+ this.api >+ .delete({ >+ id: data.record_source_id, >+ }) >+ .then(response => { >+ if (response) { >+ this.setMessage( >+ this.$__( >+ "Record source '%s' removed" >+ ).format(data.name), >+ true >+ ) >+ dt.draw() >+ } >+ }) >+ } >+ ) >+ }, >+ }, >+ components: { >+ KohaTable, >+ Toolbar, >+ ToolbarButton, >+ }, >+} >+</script> >diff --git a/koha-tmpl/intranet-tmpl/prog/js/vue/components/Admin/RecordSources/Main.vue b/koha-tmpl/intranet-tmpl/prog/js/vue/components/Admin/RecordSources/Main.vue >new file mode 100644 >index 0000000000..057c78a4ae >--- /dev/null >+++ b/koha-tmpl/intranet-tmpl/prog/js/vue/components/Admin/RecordSources/Main.vue >@@ -0,0 +1,34 @@ >+<template> >+ <div> >+ <div id="sub-header"> >+ <Breadcrumbs></Breadcrumbs> >+ <Help /> >+ </div> >+ <div class="main container-fluid"> >+ <div class="row"> >+ <div class="col-md-10 col-md-offset-1 col-lg-8 col-lg-offset-2"> >+ <main> >+ <Dialog></Dialog> >+ <router-view /> >+ </main> >+ </div> >+ </div> >+ </div> >+ </div> >+</template> >+ >+<script> >+import Breadcrumbs from "../../Breadcrumbs.vue" >+import Help from "../../Help.vue" >+import Dialog from "../../Dialog.vue" >+ >+export default { >+ components: { >+ Breadcrumbs, >+ Dialog, >+ Help, >+ }, >+} >+</script> >+ >+<style></style> >diff --git a/koha-tmpl/intranet-tmpl/prog/js/vue/fetch/record_sources-api-client.js b/koha-tmpl/intranet-tmpl/prog/js/vue/fetch/record_sources-api-client.js >new file mode 100644 >index 0000000000..bc5b84e21b >--- /dev/null >+++ b/koha-tmpl/intranet-tmpl/prog/js/vue/fetch/record_sources-api-client.js >@@ -0,0 +1,59 @@ >+import HttpClient from "./http-client"; >+ >+export class RecordSourcesAPIClient extends HttpClient { >+ constructor() { >+ super({ >+ baseURL: "/api/v1/record_sources", >+ }); >+ } >+ >+ get record_source() { >+ return { >+ create: ({ row, headers = {} }) => { >+ const requestHeaders = { >+ ...headers, >+ "Content-Type": "application/json", >+ }; >+ return this.post({ >+ endpoint: "", >+ headers: requestHeaders, >+ body: JSON.stringify(row), >+ }); >+ }, >+ delete: ({ id, headers = {} }) => { >+ return this.delete({ >+ endpoint: "/" + id, >+ headers, >+ }); >+ }, >+ update: ({ id, headers = {}, row }) => { >+ const requestHeaders = { >+ ...headers, >+ "Content-Type": "application/json", >+ }; >+ return this.put({ >+ endpoint: "/" + id, >+ headers: requestHeaders, >+ body: JSON.stringify(row), >+ }); >+ }, >+ get: ({ id, headers = {} }) => { >+ return this.get({ >+ endpoint: "/" + id, >+ headers, >+ }); >+ }, >+ count: (query = {}) => { >+ return this.count({ >+ endpoint: >+ "?" + >+ new URLSearchParams({ >+ _page: 1, >+ _per_page: 1, >+ ...(query && { q: JSON.stringify(query) }), >+ }), >+ }); >+ }, >+ }; >+ } >+} >diff --git a/koha-tmpl/intranet-tmpl/prog/js/vue/modules/admin/record_sources.ts b/koha-tmpl/intranet-tmpl/prog/js/vue/modules/admin/record_sources.ts >new file mode 100644 >index 0000000000..80f43f4ea2 >--- /dev/null >+++ b/koha-tmpl/intranet-tmpl/prog/js/vue/modules/admin/record_sources.ts >@@ -0,0 +1,54 @@ >+import { createApp } from "vue"; >+import { createPinia } from "pinia"; >+import { createWebHistory, createRouter } from "vue-router"; >+ >+import { library } from "@fortawesome/fontawesome-svg-core"; >+import { >+ faPlus, >+ faMinus, >+ faPencil, >+ faTrash, >+ faSpinner, >+} from "@fortawesome/free-solid-svg-icons"; >+import { FontAwesomeIcon } from "@fortawesome/vue-fontawesome"; >+import vSelect from "vue-select"; >+import { useNavigationStore } from "../../stores/navigation"; >+import { useMainStore } from "../../stores/main"; >+import routesDef from "../../routes/admin/record_sources"; >+ >+library.add(faPlus, faMinus, faPencil, faTrash, faSpinner); >+ >+const pinia = createPinia(); >+const navigationStore = useNavigationStore(pinia); >+const mainStore = useMainStore(pinia); >+const { removeMessages } = mainStore; >+const { setRoutes } = navigationStore; >+const routes = setRoutes(routesDef); >+ >+const router = createRouter({ >+ history: createWebHistory(), >+ linkExactActiveClass: "current", >+ routes, >+}); >+ >+import App from "../../components/Admin/RecordSources/Main.vue"; >+import i18n from "../../i18n"; >+ >+const app = createApp(App); >+ >+const rootComponent = app >+ .use(i18n) >+ .use(pinia) >+ .use(router) >+ .component("font-awesome-icon", FontAwesomeIcon) >+ .component("v-select", vSelect); >+ >+app.config.unwrapInjectedRef = true; >+app.provide("mainStore", mainStore); >+app.provide("navigationStore", navigationStore); >+app.mount("#record-source"); >+ >+router.beforeEach(to => { >+ navigationStore.$patch({ current: to.matched, params: to.params || {} }); >+ removeMessages(); // This will actually flag the messages as displayed already >+}); >diff --git a/koha-tmpl/intranet-tmpl/prog/js/vue/routes/admin/record_sources.js b/koha-tmpl/intranet-tmpl/prog/js/vue/routes/admin/record_sources.js >new file mode 100644 >index 0000000000..49833980c6 >--- /dev/null >+++ b/koha-tmpl/intranet-tmpl/prog/js/vue/routes/admin/record_sources.js >@@ -0,0 +1,40 @@ >+import { markRaw } from "vue"; >+import Edit from "../../components/Admin/RecordSources/Edit.vue"; >+import List from "../../components/Admin/RecordSources/List.vue"; >+import { $__ } from "../../i18n"; >+ >+export default { >+ title: $__("Administration"), >+ path: "", >+ href: "/cgi-bin/koha/admin/admin-home.pl", >+ BeforeUnloadEvent() { >+ window.location.href = "/cgi-bin/koha/admin/admin-home.pl"; >+ }, >+ is_base: true, >+ is_default: true, >+ children: [ >+ { >+ title: $__("Record sources"), >+ path: "/cgi-bin/koha/admin/record_sources", >+ children: [ >+ { >+ title: $__("List"), >+ path: "", >+ component: markRaw(List), >+ }, >+ { >+ title: $__("Add record source"), >+ path: "add", >+ name: "Add", >+ component: markRaw(Edit), >+ }, >+ { >+ title: $__("Edit record source"), >+ path: ":id", >+ name: "Edit", >+ component: markRaw(Edit), >+ }, >+ ], >+ }, >+ ], >+}; >diff --git a/webpack.config.js b/webpack.config.js >index c22256785a..ae87653c24 100644 >--- a/webpack.config.js >+++ b/webpack.config.js >@@ -7,6 +7,7 @@ module.exports = { > entry: { > erm: "./koha-tmpl/intranet-tmpl/prog/js/vue/modules/erm.ts", > preservation: "./koha-tmpl/intranet-tmpl/prog/js/vue/modules/preservation.ts", >+ "admin/record_sources": "./koha-tmpl/intranet-tmpl/prog/js/vue/modules/admin/record_sources.ts", > }, > output: { > filename: "[name].js", >-- >2.37.1 (Apple Git-137.1)
You cannot view the attachment while viewing its details because your browser does not support IFRAMEs.
View the attachment on a separate page
.
View Attachment As Diff
View Attachment As Raw
Actions:
View
|
Diff
|
Splinter Review
Attachments on
bug 35919
:
161531
|
161532
|
161533
|
161534
|
161535
|
161537
|
161538
|
161577
|
161578
|
161579
|
161580
|
161581
|
161582
|
161583
|
162489
|
162594
|
162595
|
162596
|
162599
|
163348
|
163349
|
163350
|
163351
|
163352
|
163353
|
163354
|
163355
|
163360
|
163369
|
163389
|
163390
|
163391
|
163392
|
163393
|
163394
|
163395
|
163396
|
163397
|
163398
|
163399
|
163400
|
164708
|
164709
|
164710
|
164711
|
164712
|
164713
|
164714
|
164715
|
164716
|
164717
|
165274