diff options
Diffstat (limited to 'plugins/Transitions/vue/src')
7 files changed, 561 insertions, 0 deletions
diff --git a/plugins/Transitions/vue/src/TransitionExporter/TransitionExporter.adapter.ts b/plugins/Transitions/vue/src/TransitionExporter/TransitionExporter.adapter.ts new file mode 100644 index 0000000000..82f1a78ca5 --- /dev/null +++ b/plugins/Transitions/vue/src/TransitionExporter/TransitionExporter.adapter.ts @@ -0,0 +1,20 @@ +/*! + * Matomo - free/libre analytics platform + * + * @link https://matomo.org + * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later + */ + +import { IDirective } from 'angular'; +import TransitionExporter from './TransitionExporter'; + +function transitionExporter(): IDirective { + return { + restrict: 'A', + link(scope, element) { + TransitionExporter.mounted(element[0]); + }, + }; +} + +window.angular.module('piwikApp').directive('transitionExporter', transitionExporter); diff --git a/plugins/Transitions/vue/src/TransitionExporter/TransitionExporter.ts b/plugins/Transitions/vue/src/TransitionExporter/TransitionExporter.ts new file mode 100644 index 0000000000..b9b6a0b7bd --- /dev/null +++ b/plugins/Transitions/vue/src/TransitionExporter/TransitionExporter.ts @@ -0,0 +1,50 @@ +/*! + * Matomo - free/libre analytics platform + * + * @link https://matomo.org + * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later + */ + +import { createVueApp, translate } from 'CoreHome'; +import TransitionExporterPopover from './TransitionExporterPopover'; +import { actionName } from './transitionParams'; + +const { Piwik_Popover } = window; + +export default { + mounted(element: HTMLElement): void { + element.addEventListener('click', (e) => { + e.preventDefault(); + + const props = { + exportFormat: 'JSON', + exportFormatOptions: [ + { key: 'JSON', value: 'JSON' }, + { key: 'XML', value: 'XML' }, + ], + }; + + const app = createVueApp({ + template: ` + <popover v-bind="bind"/>`, + data() { + return { + bind: props, + }; + }, + }); + app.component('popover', TransitionExporterPopover); + + const mountPoint = document.createElement('div'); + app.mount(mountPoint); + + Piwik_Popover.showLoading(''); + Piwik_Popover.setTitle(`${actionName.value} ${translate('Transitions_Transitions')}`); + Piwik_Popover.setContent(mountPoint); + + Piwik_Popover.onClose(() => { + app.unmount(); + }); + }); + }, +}; diff --git a/plugins/Transitions/vue/src/TransitionExporter/TransitionExporterPopover.less b/plugins/Transitions/vue/src/TransitionExporter/TransitionExporterPopover.less new file mode 100644 index 0000000000..c793232928 --- /dev/null +++ b/plugins/Transitions/vue/src/TransitionExporter/TransitionExporterPopover.less @@ -0,0 +1,52 @@ +.transitions-export-popover { + + [name=format] { + .form-group label.fieldRadioTitle { + display: block; + } + + p.radio { + width: 50%; + float: left; + display: block; + } + } + + textarea { + word-break: break-all; + padding: 5px; + height: 80px; + } + + .toggle-export-url { + font-size: 14px; + margin-left: 20px; + } + + .filter_limit { + clear: both; + float: none; + + .matomo-field { + width: 50%; + float: left; + } + } + + .showoptions > span { + color: @color-blue-piwik; + cursor: pointer; + text-decoration: underline; + } + + .tooltip { + color: @color-silver; + font-size: 13px; + padding: 5px; + } + + .tooltip > a { + color: @color-blue-piwik; + text-decoration: underline; + } +}
\ No newline at end of file diff --git a/plugins/Transitions/vue/src/TransitionExporter/TransitionExporterPopover.vue b/plugins/Transitions/vue/src/TransitionExporter/TransitionExporterPopover.vue new file mode 100644 index 0000000000..b900645d31 --- /dev/null +++ b/plugins/Transitions/vue/src/TransitionExporter/TransitionExporterPopover.vue @@ -0,0 +1,91 @@ +<!-- + Matomo - free/libre analytics platform + + @link https://matomo.org + @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later +--> + +<template> + <div class="transition-export-popover row"> + <div class="col l6"> + <div class="input-field"> + <div class="matomo-field"> + <Field + uicontrol="radio" + name="exportFormat" + :title="translate('CoreHome_ExportFormat')" + :model-value="exportFormat" + @update:model-value="exportFormat = $event" + :full-width="true" + :options="exportFormatOptions" + /> + </div> + </div> + </div> + + <div class="col l12"> + <a + class="btn" + :href="exportLink" + target="_new" + title="translate('CoreHome_ExportTooltip')" + > + {{ translate('General_Export') }} + </a> + </div> + </div> +</template> + +<script lang="ts"> +import { defineComponent } from 'vue'; +import { Matomo, MatomoUrl } from 'CoreHome'; +import { Field } from 'CorePluginsAdmin'; +import { actionType, actionName } from './transitionParams'; + +interface TransitionExportPopoverState { + exportFormat: string; +} + +export default defineComponent({ + props: { + exportFormatOptions: { + type: Object, + required: true, + }, + }, + components: { + Field, + }, + data(): TransitionExportPopoverState { + return { + exportFormat: 'JSON', + }; + }, + computed: { + exportLink() { + const exportUrlParams: QueryParameters = { + module: 'API', + }; + + exportUrlParams.method = 'Transitions.getTransitionsForAction'; + exportUrlParams.actionType = actionType.value; + exportUrlParams.actionName = actionName.value; + + exportUrlParams.idSite = Matomo.idSite; + exportUrlParams.period = Matomo.period; + exportUrlParams.date = Matomo.currentDateString; + exportUrlParams.format = this.exportFormat; + exportUrlParams.token_auth = Matomo.token_auth; + exportUrlParams.force_api_session = 1; + + const currentUrl = window.location.href; + + const urlParts = currentUrl.split('/'); + urlParts.pop(); + + const url = urlParts.join('/'); + return `${url}/index.php?${MatomoUrl.stringify(exportUrlParams)}`; + }, + }, +}); +</script> diff --git a/plugins/Transitions/vue/src/TransitionExporter/transitionParams.ts b/plugins/Transitions/vue/src/TransitionExporter/transitionParams.ts new file mode 100644 index 0000000000..f49b698cf9 --- /dev/null +++ b/plugins/Transitions/vue/src/TransitionExporter/transitionParams.ts @@ -0,0 +1,29 @@ +/*! + * Matomo - free/libre analytics platform + * + * @link https://matomo.org + * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later + */ + +import { ref } from 'vue'; +import { Matomo } from 'CoreHome'; + +interface DataChangedParams { + actionType: string; + actionName: string; +} + +const actionType = ref(''); +const actionName = ref(''); + +const onDataChanged = (params: DataChangedParams) => { + actionType.value = params.actionType; + actionName.value = params.actionName; +}; + +Matomo.on('Transitions.dataChanged', onDataChanged); + +export { + actionType, + actionName, +}; diff --git a/plugins/Transitions/vue/src/TransitionSwitcher/TransitionSwitcher.vue b/plugins/Transitions/vue/src/TransitionSwitcher/TransitionSwitcher.vue new file mode 100644 index 0000000000..6b54359a96 --- /dev/null +++ b/plugins/Transitions/vue/src/TransitionSwitcher/TransitionSwitcher.vue @@ -0,0 +1,308 @@ +<!-- + Matomo - free/libre analytics platform + @link https://matomo.org + @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later +--> + +<template> + <div + :class="{ widgetBody: isWidget }" + id="transitions_report" + > + <div class="row"> + <div class="col s12 m3"> + <div name="actionType"> + <Field + uicontrol="select" + name="actionType" + v-model="actionType" + :title="translate('Actions_ActionType')" + :full-width="true" + :options="actionTypeOptions" + > + </Field> + </div> + </div> + <div class="col s12 m9"> + <div name="actionName"> + <Field + uicontrol="select" + name="actionName" + v-model="actionName" + :title="translate('Transitions_TopX', 100)" + :full-width="true" + :disabled="!isEnabled" + :options="actionNameOptions" + > + </Field> + </div> + </div> + </div> + <ActivityIndicator :loading="isLoading" /> + <div + class="loadingPiwik" + style="display:none;" + id="transitions_inline_loading" + > + <img src="plugins/Morpheus/images/loading-blue.gif" alt/> + <span>{{ translate('General_LoadingData') }}</span> + </div> + <div + class="popoverContainer" + v-show="!isLoading && isEnabled" + > + </div> + <div + id="Transitions_Error_Container" + v-show="!isLoading" + > + </div> + <div + class="dataTableWrapper" + v-show="isEnabled" + > + <div class="dataTableFeatures"> + <div class="dataTableFooterNavigation"> + <div class="dataTableControls"> + <div class="row"> + <a + class="dataTableAction" + v-transition-exporter + > + <span class="icon-export" /> + </a> + </div> + </div> + </div> + </div> + </div> + <div class="alert alert-info"> + {{ translate('Transitions_AvailableInOtherReports') }} + {{ translate('Actions_PageUrls') }}, {{ translate('Actions_SubmenuPageTitles') }}, + {{ translate('Actions_SubmenuPagesEntry') }} + {{ translate('General_And') }} + {{ translate('Actions_SubmenuPagesExit') }}. + <span v-html="$sanitize(availableInOtherReports2)"></span> + </div> + </div> +</template> + +<script lang="ts"> +import { defineComponent, onBeforeUnmount, ref } from 'vue'; +import { + translate, + AjaxHelper, + Matomo, + ActivityIndicator, +} from 'CoreHome'; +import { Field } from 'CorePluginsAdmin'; +import TransitionExporter from '../TransitionExporter/TransitionExporter'; + +interface Option { + key: string; + value: unknown; + url?: string; +} + +interface TransitionSwitcherState { + actionType: string; + actionNameOptions: Option[]; + actionTypeOptions: Option[]; + isLoading: boolean; + actionName: string|null; + isEnabled: boolean; + noDataKey: string; +} + +interface ActionReportRow { + label: string; + nb_hits: string|number; + segment: string; + url: string; +} + +export default defineComponent({ + props: { + isWidget: Boolean, + }, + components: { + Field, + ActivityIndicator, + }, + directives: { + TransitionExporter, + }, + data(): TransitionSwitcherState { + return { + actionType: 'Actions.getPageUrls', + actionNameOptions: [], + actionTypeOptions: [ + { + key: 'Actions.getPageUrls', + value: translate('Actions_PageUrls'), + }, { + key: 'Actions.getPageTitles', + value: translate('Actions_WidgetPageTitles'), + }, + ], + isLoading: false, + actionName: null, + isEnabled: true, + noDataKey: '_____ignore_____', + }; + }, + setup() { + let transitionsInstance: Transitions|null = null; + const transitionsUrl = ref<null|string>(); + + const onSwitchTransitionsUrl = (params: { url: string }) => { + if (params?.url) { + transitionsUrl.value = params.url; + } + }; + + Matomo.on('Transitions.switchTransitionsUrl', onSwitchTransitionsUrl); + + onBeforeUnmount(() => { + Matomo.off('Transitions.switchTransitionsUrl', onSwitchTransitionsUrl); + }); + + const createTransitionsInstance = (type: string, actionName: string) => { + if (!transitionsInstance) { + transitionsInstance = new window.Piwik_Transitions(type, actionName, null, ''); + } else { + transitionsInstance.reset(type, actionName, ''); + } + }; + + const getTransitionsInstance = () => transitionsInstance; + + return { + transitionsUrl, + createTransitionsInstance, + getTransitionsInstance, + }; + }, + watch: { + transitionsUrl(newValue) { + let url = newValue; + if (this.isUrlReport) { + url = url.replace('https://', '').replace('http://', ''); + } + + const found = this.actionNameOptions.find((option) => { + let optionUrl = option.url; + + if (optionUrl && this.isUrlReport) { + optionUrl = String(optionUrl).replace('https://', '').replace('http://', ''); + } else { + optionUrl = undefined; + } + + return option.key === url || (url === optionUrl && optionUrl); + }); + + if (found) { + this.actionName = found.key; + } else { + // we only fetch top 100 in the report... so the entry the user clicked on, might not + // be in the top 100 + this.actionNameOptions = [ + ...this.actionNameOptions, + { key: url, value: url }, + ]; + this.actionName = url; + } + }, + actionName(newValue) { + if (newValue === null || newValue === this.noDataKey) { + return; + } + + const type = this.isUrlReport ? 'url' : 'title'; + + this.createTransitionsInstance(type, newValue); + + this.getTransitionsInstance()!.showPopover(true); + }, + actionType(newValue) { + this.fetch(newValue); + }, + }, + created() { + this.fetch(this.actionType); + }, + methods: { + detectActionName(reports: ActionReportRow[]) { + const othersLabel = translate('General_Others'); + + reports.forEach((report) => { + if (!report) { + return; + } + + if (report.label === othersLabel) { + return; + } + + const key = this.isUrlReport ? report.url : report.label; + if (key) { + const pageviews = translate('Transitions_NumPageviews', report.nb_hits as string); + const label = `${report.label} (${pageviews})`; + this.actionNameOptions.push({ + key, + value: label, + url: report.url, + }); + + if (!this.actionName) { + this.actionName = key; + } + } + }); + }, + fetch(type: string) { + this.isLoading = true; + this.actionNameOptions = []; + this.actionName = null; + AjaxHelper.fetch<ActionReportRow[]>({ + method: type, + flat: 1, + filter_limit: 100, + filter_sort_order: 'desc', + filter_sort_column: 'nb_hits', + showColumns: 'label,nb_hits,url', + }).then((report) => { + this.isLoading = false; + this.actionNameOptions = []; + this.actionName = null; + + if (report?.length) { + this.isEnabled = true; + this.detectActionName(report); + } + + if (this.actionName === null || this.actionNameOptions.length === 0) { + this.isEnabled = false; + this.actionName = this.noDataKey; + this.actionNameOptions.push({ + key: this.noDataKey, + value: translate('CoreHome_ThereIsNoDataForThisReport'), + }); + } + }).catch(() => { + this.isLoading = false; + this.isEnabled = false; + }); + }, + }, + computed: { + isUrlReport() { + return this.actionType === 'Actions.getPageUrls'; + }, + availableInOtherReports2() { + return translate('Transitions_AvailableInOtherReports2', '<span class="icon-transition"></span>'); + }, + }, +}); +</script> diff --git a/plugins/Transitions/vue/src/index.ts b/plugins/Transitions/vue/src/index.ts new file mode 100644 index 0000000000..dff1aa7557 --- /dev/null +++ b/plugins/Transitions/vue/src/index.ts @@ -0,0 +1,11 @@ +/*! + * Matomo - free/libre analytics platform + * + * @link https://matomo.org + * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later + */ + +import './TransitionExporter/TransitionExporter.adapter.ts'; + +export { default as TransitionExporter } from './TransitionExporter/TransitionExporter'; +export { default as TransitionSwitcher } from './TransitionSwitcher/TransitionSwitcher.vue'; |