From e1583a334a1b693cbf41f1552c65a667376632bc Mon Sep 17 00:00:00 2001 From: Matt Blenkinsop Date: Fri, 16 Jun 2023 16:27:53 +0000 Subject: [PATCH] Bug 34587: Add reports by data provider Add the option to have a report by provider that rolls all usage up into one top-level figure to see how often that provider is being used a given period Signed-off-by: Jessica Zairo Signed-off-by: Michaela Sieber Signed-off-by: Nick Clemens Signed-off-by: Tomas Cohen Arazi --- Koha/ERM/UsageDataProvider.pm | 55 ++++++++++++ Koha/REST/V1/ERM/UsageDataProviders.pm | 88 +++++++++++++++++++ .../definitions/erm_usage_data_provider.yaml | 10 +++ .../paths/erm_usage_data_providers.yaml | 60 ++++++++++++- api/v1/swagger/swagger.yaml | 2 + .../ERM/UsageStatisticsReportBuilder.vue | 49 +++++++---- .../ERM/UsageStatisticsReportsViewer.vue | 57 ++++++++++-- 7 files changed, 297 insertions(+), 24 deletions(-) diff --git a/Koha/ERM/UsageDataProvider.pm b/Koha/ERM/UsageDataProvider.pm index 90d3ff0e57..36844fe548 100644 --- a/Koha/ERM/UsageDataProvider.pm +++ b/Koha/ERM/UsageDataProvider.pm @@ -921,6 +921,61 @@ sub get_report_type_specific_fields { } +=head3 test_connection + +Tests the connection of the harvester to the SUSHI service and returns any alerts of planned SUSHI outages + +=cut + +sub test_connection { + my ($self) = @_; + + my $url = $self->service_url; + $url .= '/status'; + $url .= '?customer_id=' . $self->customer_id; + $url .= '&requestor_id=' . $self->requestor_id if $self->requestor_id; + $url .= '&api_key=' . $self->api_key if $self->api_key; + + my $request = HTTP::Request->new( 'GET' => $url ); + my $ua = LWP::UserAgent->new; + my $response = $ua->simple_request($request); + + my @result = decode_json( $response->decoded_content ); + if ( $result[0][0]->{Service_Active} ) { + return 1; + } + else { + return 0; + } + +} + +=head3 erm_usage_titles + +Method to embed erm_usage_titles to titles for report formatting + +=cut + +sub erm_usage_titles { + my ($self) = @_; + my $usage_title_rs = $self->_result->erm_usage_titles; + return Koha::ERM::UsageTitles->_new_from_dbic($usage_title_rs); +} + +=head3 erm_usage_muses + +Method to embed erm_usage_muses to titles for report formatting + +=cut + +sub erm_usage_muses { + my ($self) = @_; + my $usage_mus_rs = $self->_result->erm_usage_muses; + return Koha::ERM::MonthlyUsages->_new_from_dbic($usage_mus_rs); +} + + + =head3 _type =cut diff --git a/Koha/REST/V1/ERM/UsageDataProviders.pm b/Koha/REST/V1/ERM/UsageDataProviders.pm index 8aa9e71542..be53957831 100644 --- a/Koha/REST/V1/ERM/UsageDataProviders.pm +++ b/Koha/REST/V1/ERM/UsageDataProviders.pm @@ -359,4 +359,92 @@ sub test_connection { }; } +=head3 providers_report + +=cut + +sub providers_report { + my $c = shift->openapi->valid_input or return; + + return try { + + my $args = $c->validation->output; + + my $usage_data_providers_set = Koha::ERM::UsageDataProviders->new; + my $usage_data_providers = $c->objects->search( $usage_data_providers_set ); + + my @query_params_array; + my $json = JSON->new; + + if ( ref( $args->{q} ) eq 'ARRAY' ) { + foreach my $q ( @{ $args->{q} } ) { + push @query_params_array, $json->decode($q) + if $q; + } + } + + my $metric_types = $query_params_array[0][0]->{'erm_usage_titles.erm_usage_muses.metric_type'}; + + my @usage_data_provider_report_data; + + for my $usage_data_provider ( @{ $usage_data_providers } ) { + + # Split usage_data_providers into metric_types i.e. one table row per metric_type + for my $metric_type ( @$metric_types ) { + my @filtered_title_data; + + for my $title ( @{ $usage_data_provider->{'erm_usage_titles'} }) { + my $statistics = $title->{'erm_usage_muses'}; + my @filtered_statistics = grep { $metric_type eq $_->{metric_type} } @$statistics; + + my %title_hash = ( + usage_data_provider_id => $title->{usage_data_provider_id}, + title_id => $title->{title_id}, + title => $title->{title}, + erm_usage_muses => \@filtered_statistics, + online_issn => $title->{online_issn}, + print_issn => $title->{print_issn}, + title_doi => $title->{title_doi}, + title_uri => $title->{title_uri}, + metric_type => $metric_type, + publisher => $title->{publisher}, + publisher_id => $title->{publisher_id}, + ); + + push @filtered_title_data, \%title_hash; + } + + + my %usage_data_provider_hash = ( + erm_usage_data_provider_id => $usage_data_provider->{erm_usage_data_provider_id}, + erm_usage_titles => \@filtered_title_data, + aggregator => $usage_data_provider->{aggregator}, + api_key => $usage_data_provider->{api_key}, + begin_date => $usage_data_provider->{begin_date}, + customer_id => $usage_data_provider->{customer_id}, + description => $usage_data_provider->{description}, + end_date => $usage_data_provider->{end_date}, + method => $usage_data_provider->{method}, + name => $usage_data_provider->{name}, + report_release => $usage_data_provider->{report_release}, + report_types => $usage_data_provider->{report_types}, + requestor_email => $usage_data_provider->{requestor_email}, + requestor_id => $usage_data_provider->{requestor_id}, + requestor_name => $usage_data_provider->{requestor_name}, + service_type => $usage_data_provider->{service_type}, + service_url => $usage_data_provider->{service_url}, + metric_type => $metric_type + ); + + push @usage_data_provider_report_data, \%usage_data_provider_hash; + }; + }; + + return $c->render( status => 200, openapi => \@usage_data_provider_report_data ); + } + catch { + $c->unhandled_exception($_); + }; +} + 1; diff --git a/api/v1/swagger/definitions/erm_usage_data_provider.yaml b/api/v1/swagger/definitions/erm_usage_data_provider.yaml index 8db15ae256..112efa9925 100644 --- a/api/v1/swagger/definitions/erm_usage_data_provider.yaml +++ b/api/v1/swagger/definitions/erm_usage_data_provider.yaml @@ -85,11 +85,21 @@ properties: type: - string - "null" + metric_type: + description: metric type of the harvester when reporting + type: + - string + - "null" counter_files: type: array description: counter files items: $ref: erm_counter_file.yaml + erm_usage_titles: + type: array + description: usage titles + items: + $ref: erm_usage_title.yaml additionalProperties: false required: diff --git a/api/v1/swagger/paths/erm_usage_data_providers.yaml b/api/v1/swagger/paths/erm_usage_data_providers.yaml index 6ad30e320f..85b759d1bf 100644 --- a/api/v1/swagger/paths/erm_usage_data_providers.yaml +++ b/api/v1/swagger/paths/erm_usage_data_providers.yaml @@ -103,6 +103,7 @@ type: string enum: - counter_files + - erm_usage_titles.erm_usage_muses collectionFormat: csv - $ref: "../swagger.yaml#/parameters/match" - $ref: "../swagger.yaml#/parameters/order_by" @@ -217,6 +218,9 @@ type: array items: type: string + enum: + - counter_files + - erm_usage_titles.erm_usage_muses collectionFormat: csv responses: 200: @@ -416,7 +420,7 @@ operationId: testUsageDataProviderHarvester tags: - usage data provider harvester - summary: Run this data provider's harvester + summary: Test this data provider's harvester produces: - application/json parameters: @@ -445,6 +449,60 @@ description: Under maintenance schema: $ref: "../swagger.yaml#/definitions/error" + x-koha-authorization: + permissions: + erm: 1 +"/erm/usage_data_providers/report": + get: + x-mojo-to: ERM::UsageDataProviders#providers_report + operationId: reportUsageDataProvider + tags: + - usage data provider report + summary: Run a report for this data provider + produces: + - application/json + parameters: + - $ref: "../swagger.yaml#/parameters/match" + - $ref: "../swagger.yaml#/parameters/order_by" + - $ref: "../swagger.yaml#/parameters/page" + - $ref: "../swagger.yaml#/parameters/per_page" + - $ref: "../swagger.yaml#/parameters/q_param" + - $ref: "../swagger.yaml#/parameters/q_body" + - $ref: "../swagger.yaml#/parameters/q_header" + - name: x-koha-embed + in: header + required: false + description: Embed list sent as a request header + type: array + items: + type: string + enum: + - counter_files + - erm_usage_titles.erm_usage_muses + responses: + 200: + description: Successful report run + schema: + items: + $ref: "../swagger.yaml#/definitions/erm_usage_data_provider" + 400: + description: Bad request + schema: + $ref: "../swagger.yaml#/definitions/error" + 403: + description: Access forbidden + schema: + $ref: "../swagger.yaml#/definitions/error" + 500: + description: |- + Internal server error. Possible `error_code` attribute values: + * `internal_server_error` + schema: + $ref: "../swagger.yaml#/definitions/error" + 503: + description: Under maintenance + schema: + $ref: "../swagger.yaml#/definitions/error" x-koha-authorization: permissions: erm: 1 \ No newline at end of file diff --git a/api/v1/swagger/swagger.yaml b/api/v1/swagger/swagger.yaml index 8625f1e8a6..72d287cc10 100644 --- a/api/v1/swagger/swagger.yaml +++ b/api/v1/swagger/swagger.yaml @@ -297,6 +297,8 @@ paths: $ref: "./paths/erm_usage_data_providers.yaml#/~1erm~1usage_data_providers~1{erm_usage_data_provider_id}~1run" "/erm/usage_data_providers/{erm_usage_data_provider_id}/test_connection": $ref: "./paths/erm_usage_data_providers.yaml#/~1erm~1usage_data_providers~1{erm_usage_data_provider_id}~1test_connection" + "/erm/usage_data_providers/report": + $ref: "./paths/erm_usage_data_providers.yaml#/~1erm~1usage_data_providers~1report" /erm/usage_titles: $ref: ./paths/erm_usage_titles.yaml#/~1erm~1usage_titles /erm/users: diff --git a/koha-tmpl/intranet-tmpl/prog/js/vue/components/ERM/UsageStatisticsReportBuilder.vue b/koha-tmpl/intranet-tmpl/prog/js/vue/components/ERM/UsageStatisticsReportBuilder.vue index 7495f3b0f8..3900b65c69 100644 --- a/koha-tmpl/intranet-tmpl/prog/js/vue/components/ERM/UsageStatisticsReportBuilder.vue +++ b/koha-tmpl/intranet-tmpl/prog/js/vue/components/ERM/UsageStatisticsReportBuilder.vue @@ -30,6 +30,10 @@ value: 'metric_type', description: 'By metric type', }, + { + value: 'usage_data_provider', + description: 'By data provider totals', + }, ]" /> @@ -637,10 +641,24 @@ export default { this.time_period_columns_builder = time_period_columns }, - buildMonthlyUrlQuery(query, time_period_columns, metric_type_report) { - let url = metric_type_report - ? "/api/v1/erm/usage_titles/metric_types_report" - : "/api/v1/erm/usage_titles/monthly_report" + buildMonthlyUrlQuery(query, time_period_columns, data_display) { + let url + let prefix + switch (data_display) { + case "monthly": + url = "/api/v1/erm/usage_titles/monthly_report" + prefix = "erm_usage_muses" + break + case "metric_type": + url = "/api/v1/erm/usage_titles/metric_types_report" + prefix = "erm_usage_muses" + break + case "usage_data_provider": + url = "/api/v1/erm/usage_data_providers/report" + prefix = "erm_usage_titles.erm_usage_muses" + break + } + // Work out which years are included in the query const years = [] const { @@ -665,8 +683,8 @@ export default { const queryArray = years.map(year => { const queryByYear = {} - queryByYear[`erm_usage_muses.year`] = year - queryByYear[`erm_usage_muses.report_type`] = report_type + queryByYear[`${prefix}.year`] = year + queryByYear[`${prefix}.report_type`] = report_type // Find the months applicable to each year, ignoring months that have been de-selected const queryMonths = months[year] @@ -674,21 +692,21 @@ export default { .map(month => { return month.value }) - queryByYear[`erm_usage_muses.month`] = queryMonths + queryByYear[`${prefix}.month`] = queryMonths // Add any title query if (titles) { const title_ids = titles.map(title => { return title.title_id }) - queryByYear[`erm_usage_muses.title_id`] = title_ids + queryByYear[`${prefix}.title_id`] = title_ids } // Add any metric types query if (metric_types) { - queryByYear[`erm_usage_muses.metric_type`] = metric_types + queryByYear[`${prefix}.metric_type`] = metric_types } // Add any data provider query if (usage_data_providers) { - queryByYear[`erm_usage_muses.usage_data_provider_id`] = + queryByYear[`${prefix}.usage_data_provider_id`] = usage_data_providers } @@ -889,19 +907,18 @@ export default { queryObject.metric_types = final_metric_types } - const metric_report_type = - data_display === "metric_type" ? true : false const url = !data_display.includes("yearly") ? this.buildMonthlyUrlQuery( queryObject, this.time_period_columns_builder, - metric_report_type + data_display ) : this.buildYearlyUrlQuery(queryObject) const type = data_display - const columns = this.defineColumns( - this.title_property_column_options - ) + const columns = + data_display === "usage_data_provider" + ? [] + : this.defineColumns(this.title_property_column_options) const yearly_filter = data_display.includes("monthly") ? this.yearly_filter_required : false diff --git a/koha-tmpl/intranet-tmpl/prog/js/vue/components/ERM/UsageStatisticsReportsViewer.vue b/koha-tmpl/intranet-tmpl/prog/js/vue/components/ERM/UsageStatisticsReportsViewer.vue index 7e9f0ab196..1db72386bf 100644 --- a/koha-tmpl/intranet-tmpl/prog/js/vue/components/ERM/UsageStatisticsReportsViewer.vue +++ b/koha-tmpl/intranet-tmpl/prog/js/vue/components/ERM/UsageStatisticsReportsViewer.vue @@ -47,6 +47,20 @@ export default { this.report_type === "yearly" ? ["erm_usage_yuses"] : ["erm_usage_muses"] + switch (this.report_type) { + case "monthly": + this.embed = "erm_usage_muses" + break + case "yearly": + this.embed = "erm_usage_yuses" + break + case "metric_type": + this.embed = "erm_usage_muses" + break + case "usage_data_provider": + this.embed = "erm_usage_titles.erm_usage_muses" + break + } this.years = Object.keys(this.params.tp_columns) this.year = this.years[this.years.length - 1] @@ -78,15 +92,16 @@ export default { const yearly_filter = params.yearly_filter const query = params.queryObject - const column_set = [ - { + const column_set = [...columns] + + report_type !== "usage_data_provider" && + column_set.unshift({ title: __("Title"), data: "title", searchable: true, orderable: true, - }, - ...columns, - ] + }) + // Add metric type to each row if (report_type !== "metric_type") { column_set.push({ @@ -99,6 +114,36 @@ export default { }) } + if (report_type === "usage_data_provider") { + column_set.unshift({ + title: __("Data provider"), + data: "name", + searchable: true, + orderable: true, + }) + column_set.push({ + title: __("Period Total"), + render: function (data, type, row, meta) { + const sum = row.erm_usage_titles.reduce( + (acc, title) => { + const titleSum = title.erm_usage_muses.reduce( + (acc, mus) => { + return acc + mus.usage_count + }, + 0 + ) + return acc + titleSum + }, + 0 + ) + return sum + }, + searchable: true, + orderable: true, + }) + return column_set + } + // Add monthly columns if (yearly_filter) { months_data.forEach(month => { @@ -307,8 +352,6 @@ export default { }, watch: { table() { - // table needs to be rendered before header can be created and - // table is hidden by .hide-table until table header is created if (this.report_type !== "metric_type") { this.mergeTitleDataIntoOneLine( this.params.queryObject.metric_types.length -- 2.39.5