Skip to content

Commit

Permalink
Merge pull request #373 from bozana/9666
Browse files Browse the repository at this point in the history
pkp/pkp-lib#8248 COUNTER R5 TSV reports
  • Loading branch information
bozana authored Oct 15, 2024
2 parents 3f74ed3 + c551c55 commit 157a8cf
Show file tree
Hide file tree
Showing 6 changed files with 254 additions and 8 deletions.
2 changes: 2 additions & 0 deletions src/components/Container/Page.vue
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import ReviewerSubmissionPage from '@/pages/reviewerSubmission/ReviewerSubmissio
import JobsPage from '@/pages/jobs/JobsPage.vue';
import FailedJobsPage from '@/pages/jobs/FailedJobsPage.vue';
import FailedJobDetailsPage from '@/pages/jobs/FailedJobDetailsPage.vue';
import CounterReportsPage from '@/pages/counter/CounterReportsPage.vue';
export default {
name: 'Page',
Expand All @@ -17,6 +18,7 @@ export default {
JobsPage,
FailedJobsPage,
FailedJobDetailsPage,
CounterReportsPage,
},
extends: Container,
data() {
Expand Down
28 changes: 21 additions & 7 deletions src/components/Form/Form.vue
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
:primary-locale-key="primaryLocale"
:locales="availableLocales"
:visible="visibleLocales"
@updateLocales="setVisibleLocales"
@update-locales="setVisibleLocales"
/>
<div v-if="pages.length > 1" class="pkpForm__pageNav">
<ol class="pkpForm__pageNavList">
Expand Down Expand Up @@ -59,10 +59,10 @@
:available-locales="availableLocales"
:is-saving="isSaving"
@change="fieldChanged"
@pageSubmitted="nextPage"
@previousPage="setCurrentPage(false)"
@showField="showField"
@showLocale="showLocale"
@page-submitted="nextPage"
@previous-page="setCurrentPage(false)"
@show-field="showField"
@show-locale="showLocale"
@cancel="cancel"
@set-errors="setErrors"
/>
Expand Down Expand Up @@ -128,6 +128,10 @@ export default {
visibleLocales: Array,
/** The locale(s) supported by this form. If a form has multilingual fields, it will display a separate input control for each of these locales. */
supportedFormLocales: Array,
/** For custom AJAX call, while still keep the error handling within Form
* Async function, receiving data from form and returning {validationError, data} from useFetch
*/
customSubmit: Function,
},
emits: [
/** When the form props need to be updated. The payload is an object with any keys that need to be modified. */
Expand Down Expand Up @@ -285,7 +289,7 @@ export default {
/**
* Submit the form
*/
submit() {
async submit() {
if (!this.canSubmit) {
return false;
}
Expand All @@ -304,7 +308,17 @@ export default {
return;
}
if (this.action === 'emit') {
if (this.customSubmit) {
const {data, validationError} = await this.customSubmit(
this.submitValues,
);
if (validationError) {
this.error({status: 400, responseJSON: validationError});
} else if (data) {
this.success(data);
}
this.complete();
} else if (this.action === 'emit') {
this.$emit('success', this.submitValues);
} else {
$.ajax({
Expand Down
6 changes: 5 additions & 1 deletion src/composables/useForm.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,13 @@ function mapFromSelectedToValue(selected) {
return selected.map((iv) => iv.value);
}

export function useForm(_form) {
export function useForm(_form, {customSubmit} = {}) {
const form = ref(_form);

if (customSubmit) {
form.value.customSubmit = customSubmit;
}

function connectWithPayload(payload) {
watch(
payload,
Expand Down
30 changes: 30 additions & 0 deletions src/pages/counter/CounterReportsPage.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
<template>
<h1 class="app__pageHeading">
{{ t('manager.statistics.counterR5Reports') }}
</h1>
<p v-html="t('manager.statistics.counterR5Reports.description')" />
<div class="mb-4">
<Notification v-if="usageNotPossible" type="warning">
{{ t('manager.statistics.counterR5Reports.usageNotPossible') }}
</Notification>
</div>
<Panel>
<PanelSection>
<CounterReportsListPanel v-bind="counterReportsListPanel" @set="set" />
</PanelSection>
</Panel>
</template>

<script setup>
import Panel from '@/components/Panel/Panel.vue';
import PanelSection from '@/components/Panel/PanelSection.vue';
import CounterReportsListPanel from './components/CounterReportsListPanel.vue';
import {useLocalize} from '@/composables/useLocalize';
const {t} = useLocalize();
defineProps({
counterReportsListPanel: {type: Object, required: true},
usageNotPossible: {type: Boolean, required: true},
});
</script>
112 changes: 112 additions & 0 deletions src/pages/counter/components/CounterReportsEditModal.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
<template>
<SideModalBody>
<template #title>
{{ title }}
</template>
<SideModalLayoutBasic>
<PkpForm v-bind="form" @set="set" @success="closeModal" />
</SideModalLayoutBasic>
</SideModalBody>
</template>

<script setup>
import SideModalBody from '@/components/Modal/SideModalBody.vue';
import SideModalLayoutBasic from '@/components/Modal/SideModalLayoutBasic.vue';
import PkpForm from '@/components/Form/Form.vue';
import {useForm} from '@/composables/useForm';
import {useFetch} from '@/composables/useFetch';
import {inject} from 'vue';
const props = defineProps({
title: {type: String, required: true},
submitAction: {type: String, required: true},
activeForm: {type: Object, required: true},
});
const closeModal = inject('closeModal');
/**
* Get the report parameters
*
* @param Object
* @return Object
*/
function getReportParams(formSubmitValues) {
let params = {};
for (const [key, value] of Object.entries(formSubmitValues)) {
switch (key) {
case 'customer_id':
if (parseInt(value, 10) >= 0) {
params[key] = value;
}
break;
case 'begin_date':
case 'end_date':
case 'yop':
case 'item_id':
if (value != null && value.length > 0) {
params[key] = value;
}
break;
case 'metric_type':
case 'attributes_to_show':
if (value != null && value.length > 0) {
params[key] = value.join('|');
}
break;
case 'include_parent_details':
if (value == true) {
params.include_parent_details = 'True';
}
break;
case 'granularity':
if (value == true) {
params.granularity = 'Totals';
}
break;
}
}
return params;
}
const {form, set} = useForm(props.activeForm, {
customSubmit: async (submittedValues) => {
const {validationError, data, fetch} = useFetch(props.submitAction, {
expectValidationError: true,
headers: {Accept: 'text/tab-separated-values; charset=utf-8'},
query: getReportParams(submittedValues),
});
await fetch();
if (
validationError.value &&
Object.prototype.hasOwnProperty.call(validationError.value, 'Code')
) {
// COUNTER speific errors should actually not occur
// because of the form/user input validation
// but consider them for any case as well.
pkp.eventBus.$emit(
'notify',
validationError.value.Code +
': ' +
validationError.value.Message +
' (' +
validationError.value.Data +
')',
'warning',
);
validationError.value = null;
data.value = null;
} else if (data.value) {
var blob = new Blob([data.value]);
var link = document.createElement('a');
link.href = window.URL.createObjectURL(blob);
link.download = 'counterReport.tsv';
link.click();
}
return {data: data.value, validationError: validationError.value};
},
});
</script>
84 changes: 84 additions & 0 deletions src/pages/counter/components/CounterReportsListPanel.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
<template>
<div class="counterReportsListPanel">
<slot>
<ListPanel :items="items">
<template #header>
<PkpHeader>
<h2>{{ title }}</h2>
<Spinner v-if="isLoadingItems" />
</PkpHeader>
</template>
<template #item-title="{item}">
<span :id="item.Report_ID" class="text-lg-normal">
{{ item.Report_Name }} ({{ item.Report_ID }})
</span>
</template>
<template #item-actions="{item}">
<PkpButton @click="openEditModal(item.Report_ID)">
{{ t('common.edit') }}
</PkpButton>
</template>
</ListPanel>
</slot>
</div>
</template>

<script setup>
import PkpButton from '@/components/Button/Button.vue';
import Spinner from '@/components/Spinner/Spinner.vue';
import ListPanel from '@/components/ListPanel/ListPanel.vue';
import PkpHeader from '@/components/Header/Header.vue';
import cloneDeep from 'clone-deep';
import CounterReportsEditModal from './CounterReportsEditModal.vue';
import {useModal} from '@/composables/useModal';
import {useFetch} from '@/composables/useFetch';
import {useApiUrl} from '@/composables/useApiUrl';
import {useLocalize} from '@/composables/useLocalize';
import {ref, computed} from 'vue';
const {t} = useLocalize();
const props = defineProps({
form: {type: Object, required: true},
id: {type: String, required: true},
title: {type: String, required: true},
});
const items = computed(() => data.value || []);
const activeForm = ref(null);
const activeFormTitle = ref('');
const {apiUrl} = useApiUrl(`stats/sushi/reports`);
const {
data,
fetch,
isLoading: isLoadingItems,
} = useFetch(apiUrl, {
method: 'GET',
});
fetch();
/**
* Open the modal to edit an item
*
* @param {String} id
*/
function openEditModal(id) {
const report = items.value.find((report) => report.Report_ID === id);
activeForm.value = cloneDeep(props.form);
activeForm.value.method = 'GET';
activeForm.value.fields = activeForm.value.reportFields[id];
activeFormTitle.value = t('manager.statistics.counterR5Report.settings');
const {openSideModal} = useModal();
const {apiUrl} = useApiUrl(`stats/sushi/${report.Path}`);
openSideModal(CounterReportsEditModal, {
title: t('manager.statistics.counterR5Report.settings'),
submitAction: apiUrl,
activeForm,
});
}
</script>

0 comments on commit 157a8cf

Please sign in to comment.