Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

pkp/pkp-lib#8248 COUNTER R5 TSV reports #373

Merged
merged 11 commits into from
Oct 15, 2024
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}) {
bozana marked this conversation as resolved.
Show resolved Hide resolved
const form = ref(_form);

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

function connectWithPayload(payload) {
watch(
payload,
Expand Down
31 changes: 31 additions & 0 deletions src/pages/counter/CounterReportsPage.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<template>
<h1 class="app__pageHeading">
{{ title }}
</h1>
<p v-html="description" />
<Notification
v-if="usageNotPossible"
type="warning"
class="pkpNotification--backendPage__header"
bozana marked this conversation as resolved.
Show resolved Hide resolved
>
{{ t('manager.statistics.counterR5Reports.usageNotPossible') }}
</Notification>
<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';

defineProps({
counterReportsListPanel: {type: Object, required: true},
usageNotPossible: {type: Boolean, required: true},
title: {type: String, required: true},
description: {type: String, required: true},
});
bozana marked this conversation as resolved.
Show resolved Hide resolved
</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>
102 changes: 102 additions & 0 deletions src/pages/counter/components/CounterReportsListPanel.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
<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, onMounted} 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 = ref([]);
const isLoadingItems = ref(false);
const activeForm = ref(null);
const activeFormTitle = ref('');

onMounted(() => {
/* Load the items */
getItems();
});

/**
* Get the list of items (COUNTER R5 reports) from the server
*/
async function getItems() {
isLoadingItems.value = true;

bozana marked this conversation as resolved.
Show resolved Hide resolved
const {apiUrl} = useApiUrl(`stats/sushi/reports`);
const {data, isSuccess, fetch} = useFetch(apiUrl, {
method: 'GET',
});
await fetch();

if (isSuccess) {
items.value = data.value;
}
bozana marked this conversation as resolved.
Show resolved Hide resolved
isLoadingItems.value = false;
}

/**
* 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>

<style lang="less">
@import '../../../styles/_import';
</style>
bozana marked this conversation as resolved.
Show resolved Hide resolved
Loading