Commit 84aeadf1 authored by Brandon Labuschagne's avatar Brandon Labuschagne Committed by Markus Koller

Remove Generic Reports code

As we are no longer moving forward with the Generic Reports
feature we should remove the code from the codebase.

It could always be re-added again at a later stage if need be!
parent c866a62a
......@@ -2,21 +2,14 @@
import Api from 'ee/api';
import { __, s__ } from '~/locale';
import createFlash from '~/flash';
import { mergeUrlParams } from '~/lib/utils/url_utility';
import MetricCard from '../../shared/components/metric_card.vue';
const REPORT_PAGE_CONFIGURATION = {
mergeRequests: {
id: 'recent_merge_requests_by_group',
},
};
export default {
name: 'GroupActivityCard',
components: {
MetricCard,
},
inject: ['groupFullPath', 'groupName', 'reportPagesPath', 'enableReportPages'],
inject: ['groupFullPath', 'groupName'],
data() {
return {
isLoading: false,
......@@ -38,7 +31,6 @@ export default {
key,
value,
label,
link: this.generateReportPageLink(key),
};
});
},
......@@ -66,21 +58,6 @@ export default {
this.isLoading = false;
});
},
displayReportLink(key) {
return this.enableReportPages && Object.keys(REPORT_PAGE_CONFIGURATION).includes(key);
},
generateReportPageLink(key) {
return this.displayReportLink(key)
? mergeUrlParams(
{
groupPath: this.groupFullPath,
groupName: this.groupName,
reportId: REPORT_PAGE_CONFIGURATION[key].id,
},
this.reportPagesPath,
)
: null;
},
},
};
</script>
......
......@@ -6,8 +6,7 @@ export default () => {
if (!container) return;
const { groupFullPath, groupName, reportPagesPath } = container.dataset;
const { reportPages: enableReportPages } = gon?.features;
const { groupFullPath, groupName } = container.dataset;
// eslint-disable-next-line no-new
new Vue({
......@@ -15,8 +14,6 @@ export default () => {
provide: {
groupFullPath,
groupName,
reportPagesPath,
enableReportPages,
},
render(h) {
return h(GroupActivityCard);
......
<script>
import { GlBreadcrumb, GlIcon, GlLoadingIcon } from '@gitlab/ui';
import { mapState, mapActions } from 'vuex';
export default {
name: 'ReportsApp',
components: {
GlBreadcrumb,
GlIcon,
GlLoadingIcon,
},
computed: {
...mapState('page', ['config', 'groupName', 'groupPath', 'isLoading']),
breadcrumbs() {
const {
groupName = null,
groupPath = null,
config: { title },
} = this;
return [
groupName && groupPath ? { text: groupName, href: `/${groupPath}` } : null,
{ text: title, href: '' },
].filter(Boolean);
},
},
mounted() {
this.fetchPageConfigData();
},
methods: {
...mapActions('page', ['fetchPageConfigData']),
},
};
</script>
<template>
<div>
<gl-loading-icon v-if="isLoading" size="md" class="gl-mt-5" />
<gl-breadcrumb v-else :items="breadcrumbs">
<template #separator>
<gl-icon name="angle-right" :size="8" />
</template>
</gl-breadcrumb>
</div>
</template>
import Vue from 'vue';
import Vuex from 'vuex';
import { queryToObject } from '~/lib/utils/url_utility';
import ReportsApp from './components/app.vue';
import createsStore from './store';
Vue.use(Vuex);
export default () => {
const el = document.querySelector('#js-reports-app');
if (!el) return false;
const store = createsStore();
const { configEndpoint } = el.dataset;
const { groupName = null, groupPath = null, reportId = null } = queryToObject(
document.location.search,
);
store.dispatch('page/setInitialPageData', { configEndpoint, groupName, groupPath, reportId });
return new Vue({
el,
name: 'ReportsApp',
store,
render: createElement => createElement(ReportsApp),
});
};
import Vuex from 'vuex';
import page from './modules/page/index';
export default () =>
new Vuex.Store({
modules: { page },
});
import axios from '~/lib/utils/axios_utils';
import createFlash from '~/flash';
import { __ } from '~/locale';
import * as types from './mutation_types';
export const setInitialPageData = ({ commit }, data) => commit(types.SET_INITIAL_PAGE_DATA, data);
export const requestPageConfigData = ({ commit }) => commit(types.REQUEST_PAGE_CONFIG_DATA);
export const receivePageConfigDataSuccess = ({ commit }, data) =>
commit(types.RECEIVE_PAGE_CONFIG_DATA_SUCCESS, data);
export const receivePageConfigDataError = ({ commit }) => {
commit(types.RECEIVE_PAGE_CONFIG_DATA_ERROR);
createFlash(__('There was an error while fetching configuration data.'));
};
export const fetchPageConfigData = ({ dispatch, state }) => {
dispatch('requestPageConfigData');
const { groupPath, reportId, configEndpoint } = state;
return axios
.get(configEndpoint.replace('REPORT_ID', reportId), {
params: {
group_id: groupPath,
},
})
.then(response => {
const { data } = response;
dispatch('receivePageConfigDataSuccess', data);
})
.catch(() => dispatch('receivePageConfigDataError'));
};
import state from './state';
import mutations from './mutations';
import * as actions from './actions';
export default {
namespaced: true,
state,
mutations,
actions,
};
export const SET_INITIAL_PAGE_DATA = 'SET_INITIAL_PAGE_DATA';
export const REQUEST_PAGE_CONFIG_DATA = 'REQUEST_PAGE_CONFIG_DATA';
export const RECEIVE_PAGE_CONFIG_DATA_SUCCESS = 'RECEIVE_PAGE_CONFIG_DATA_SUCCESS';
export const RECEIVE_PAGE_CONFIG_DATA_ERROR = 'RECEIVE_PAGE_CONFIG_DATA_ERROR';
import * as types from './mutation_types';
export default {
[types.SET_INITIAL_PAGE_DATA](state, data) {
const { configEndpoint, reportId, groupName, groupPath } = data;
state.configEndpoint = configEndpoint;
state.reportId = reportId;
state.groupName = groupName;
state.groupPath = groupPath;
},
[types.REQUEST_PAGE_CONFIG_DATA](state) {
state.isLoading = true;
},
[types.RECEIVE_PAGE_CONFIG_DATA_SUCCESS](state, data) {
state.isLoading = false;
state.config = data;
},
[types.RECEIVE_PAGE_CONFIG_DATA_ERROR](state) {
state.isLoading = false;
},
};
import { s__ } from '~/locale';
export default () => ({
isLoading: false,
configEndpoint: '',
reportId: null,
groupName: null,
groupPath: null,
config: {
title: s__('GenericReports|Report'),
chart: null,
},
});
import initReportsApp from 'ee/analytics/reports';
document.addEventListener('DOMContentLoaded', () => {
initReportsApp();
});
# frozen_string_literal: true
module Analytics
module Reports
class PagesController < ::ApplicationController
layout 'report_pages'
before_action do
render_404 unless feature_enabled? && feature_available?
end
def feature_enabled?
Feature.enabled?(Gitlab::Analytics::REPORT_PAGES_FEATURE_FLAG)
end
def feature_available?
::License.feature_available?(:group_activity_analytics)
end
end
end
end
......@@ -13,10 +13,6 @@ module EE
before_action only: :issues do
push_frontend_feature_flag(:scoped_labels, @group)
end
before_action only: :show do
push_frontend_feature_flag(:report_pages)
end
end
override :render_show_html
......
- @hide_breadcrumbs = true
- page_title _("Reports")
#js-reports-app{ data: { config_endpoint: api_v4_analytics_reports_chart_path(report_id: 'REPORT_ID') } }
- return unless show_group_activity_analytics?
#js-group-activity{ data: { group_full_path: @group.full_path, group_name: @group.name, report_pages_path: analytics_report_pages_path } }
#js-group-activity{ data: { group_full_path: @group.full_path, group_name: @group.name } }
- page_title _('Reports')
= render template: 'layouts/application'
......@@ -3,10 +3,6 @@
namespace :analytics do
root to: 'analytics#index'
constraints(::Constraints::FeatureConstrainer.new(Gitlab::Analytics::REPORT_PAGES_FEATURE_FLAG)) do
get :report_pages, to: 'reports/pages#show'
end
constraints(-> (req) { Gitlab::Analytics.cycle_analytics_enabled? }) do
resource :cycle_analytics, only: :show, path: 'value_stream_analytics'
scope module: :cycle_analytics, as: 'cycle_analytics', path: 'value_stream_analytics' do
......
recent_merge_requests_by_group:
title: Recent Issues (90 days)
chart:
type: bar
series:
open_merge_requests:
title: Merge Requests
data_retrieval: Metrics::InsightsQuery
data_retrieval_options:
issuable_type: merge_request
issuable_state: opened
group_by: week
period_limit: 12
# frozen_string_literal: true
module API
module Analytics
class Reports < Grape::API::Instance
DESCRIPTION_DETAIL =
'This feature is experimental and gated by the `:report_pages`'\
' feature flag, introduced in GitLab 13.2.'
helpers do
def api_endpoints_available?
# This will be scoped to a project or a group
Feature.enabled?(:report_pages) && ::License.feature_available?(:group_activity_analytics)
end
def load_report
loader_class = Gitlab::Analytics::Reports::ConfigLoader
report_id = params[:report_id]
loader_class.new.find_report_by_id!(report_id)
rescue loader_class::MissingReportError
not_found!("Report(#{report_id})")
end
def report
@report ||= load_report
end
end
params do
requires :report_id, type: String, desc: 'The ID of the report'
end
resource :analytics do
resource :reports do
route_param :report_id do
resource :chart do
get do
not_found! unless api_endpoints_available?
present report, with: EE::API::Entities::Analytics::Reports::Chart
end
end
end
end
resource :series do
params do
requires :series_id, type: String, desc: 'The ID of the series'
end
route_param :report_id do
route_param :series_id do
get do
not_found! unless api_endpoints_available?
# Dummy response
{
labels: %w[label1 label2 label3],
datasets: [
{
label: "Series 1",
data: [
1,
2,
3
]
}
]
}
end
end
end
end
end
end
end
end
......@@ -47,7 +47,6 @@ module EE
mount ::API::VisualReviewDiscussions
mount ::API::Analytics::CodeReviewAnalytics
mount ::API::Analytics::GroupActivityAnalytics
mount ::API::Analytics::Reports
mount ::API::ProtectedEnvironments
mount ::API::ResourceWeightEvents
......
# frozen_string_literal: true
module EE
module API
module Entities
module Analytics
module Reports
class Chart < Grape::Entity
class ChartSeriesConfig < Grape::Entity
expose :id
expose :title
end
class ChartConfig < Grape::Entity
expose :type
expose :series, using: ChartSeriesConfig
end
expose :id
expose :title
expose :chart, using: ChartConfig
end
end
end
end
end
end
......@@ -5,18 +5,15 @@ module Gitlab
# Normally each analytics feature should be guarded with a feature flag.
CYCLE_ANALYTICS_FEATURE_FLAG = :cycle_analytics
PRODUCTIVITY_ANALYTICS_FEATURE_FLAG = :productivity_analytics
REPORT_PAGES_FEATURE_FLAG = :report_pages
FEATURE_FLAGS = [
CYCLE_ANALYTICS_FEATURE_FLAG,
PRODUCTIVITY_ANALYTICS_FEATURE_FLAG,
REPORT_PAGES_FEATURE_FLAG
PRODUCTIVITY_ANALYTICS_FEATURE_FLAG
].freeze
FEATURE_FLAG_DEFAULTS = {
PRODUCTIVITY_ANALYTICS_FEATURE_FLAG => true,
CYCLE_ANALYTICS_FEATURE_FLAG => true,
REPORT_PAGES_FEATURE_FLAG => false
CYCLE_ANALYTICS_FEATURE_FLAG => true
}.freeze
def self.any_features_enabled?
......@@ -31,10 +28,6 @@ module Gitlab
feature_enabled?(PRODUCTIVITY_ANALYTICS_FEATURE_FLAG)
end
def self.report_pages_enabled?
feature_enabled?(REPORT_PAGES_FEATURE_FLAG)
end
def self.feature_enabled_by_default?(flag)
!!FEATURE_FLAG_DEFAULTS[flag]
end
......
# frozen_string_literal: true
module Gitlab
module Analytics
module Reports
class Chart
attr_reader :type, :series
def initialize(type:, series:)
@type = type
@series = series
end
end
end
end
end
# frozen_string_literal: true
module Gitlab
module Analytics
module Reports
class ConfigLoader
include ::Gitlab::Utils::StrongMemoize
MissingReportError = Class.new(StandardError)
MissingSeriesError = Class.new(StandardError)
DEFAULT_CONFIG = File.join('ee', 'fixtures', 'report_pages', 'default.yml').freeze
def find_report_by_id!(report_id)
raw_report = default_config[report_id.to_s.to_sym]
raise(MissingReportError.new) if raw_report.nil?
ReportBuilder.build(raw_report.merge(id: report_id))
end
def find_series_by_id!(report_id, series_id)
report = find_report_by_id!(report_id)
series = report.find_series_by_id(series_id)
raise(MissingSeriesError.new) if series.nil?
series
end
private
def default_config
strong_memoize(:default_config) do
yaml = File.read(Rails.root.join(DEFAULT_CONFIG).to_s)
::Gitlab::Config::Loader::Yaml.new(yaml).load!
rescue Gitlab::Config::Loader::FormatError
{}
end
end
end
end
end
end
# frozen_string_literal: true
module Gitlab
module Analytics
module Reports
class Report
attr_reader :id, :title, :chart
def initialize(id:, title:, chart:)
@id = id
@title = title
@chart = chart
end
def find_series_by_id(series_id)
chart.series.find { |series| series.id.to_s.eql?(series_id.to_s) }
end
end
end
end
end
# frozen_string_literal: true
module Gitlab
module Analytics
module Reports
class ReportBuilder
def self.build(raw_report)
series = raw_report[:chart][:series].map { |id, series| build_series(series.merge(id: id)) }
chart = Chart.new(type: raw_report[:chart][:type], series: series)
Report.new(
id: raw_report[:id],
title: raw_report[:title],
chart: chart
)
end
def self.build_series(raw_series)
Series.new(
id: raw_series[:id],
title: raw_series[:title],
data_retrieval_options: {
data_retrieval: raw_series[:data_retrieval]
}.merge(raw_series[:data_retrieval_options])
)
end
private_class_method :build_series
end
end
end
end
# frozen_string_literal: true
module Gitlab
module Analytics
module Reports
class Series
attr_reader :id, :title, :data_retrieval_options
def initialize(id:, title:, data_retrieval_options:)
@id = id
@title = title
@data_retrieval_options = data_retrieval_options
end
end
end
end
end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Analytics::Reports::PagesController do
let(:user) { create(:user) }
before do
sign_in(user)
stub_licensed_features(group_activity_analytics: true)
stub_feature_flags(Gitlab::Analytics::REPORT_PAGES_FEATURE_FLAG => true)
end
describe 'GET show' do
it 'renders the report page' do
get :show
expect(response).to render_template :show
end
context 'when the feature flag is false' do
before do
stub_feature_flags(Gitlab::Analytics::REPORT_PAGES_FEATURE_FLAG => false)
end
it 'renders 404, not found' do
get :show
expect(response).to have_gitlab_http_status(:not_found)
end
end
context 'when the feature is not available' do
before do
stub_licensed_features(group_activity_analytics: false)
end
it 'renders 404, not found' do
get :show
expect(response).to have_gitlab_http_status(:not_found)
end
end
end
end
{
"type": "object",
"properties": {
"id": { "type": "string" },
"title": { "type": "string" },
"chart": {
"type": "object",
"properties": {
"type": { "type": "string" },
"series": {
"type": "array",
"items": [
{
"type": "object",
"properties": {
"id": { "type": "string" },
"title": { "type": "string" }
},
"additionalProperties": false
}
]
}
},
"additionalProperties": false
}
},
"additionalProperties": false
}
......@@ -11,7 +11,6 @@ const TEST_GROUP_NAME = 'Gitlab Org';
const TEST_MERGE_REQUESTS_COUNT = { data: { merge_requests_count: 10 } };
const TEST_ISSUES_COUNT = { data: { issues_count: 20 } };
const TEST_NEW_MEMBERS_COUNT = { data: { new_members_count: 30 } };
const REPORT_PAGES_PATH = 'report_pages';
describe('GroupActivity component', () => {
let wrapper;
......@@ -22,8 +21,6 @@ describe('GroupActivity component', () => {
provide: {
groupFullPath: TEST_GROUP_ID,
groupName: TEST_GROUP_NAME,
reportPagesPath: REPORT_PAGES_PATH,
enableReportPages: false,
},
});
};
......@@ -90,9 +87,9 @@ describe('GroupActivity component', () => {
.then(waitForPromises)
.then(() => {
expect(findMetricCard().props('metrics')).toEqual([
{ key: 'mergeRequests', value: 10, label: 'Merge Requests opened', link: null },
{ key: 'issues', value: 20, label: 'Issues opened', link: null },
{ key: 'newMembers', value: 30, label: 'Members added', link: null },
{ key: 'mergeRequests', value: 10, label: 'Merge Requests opened' },
{ key: 'issues', value: 20, label: 'Issues opened' },
{ key: 'newMembers', value: 30, label: 'Members added' },
]);
});
});
......
import Vuex from 'vuex';
import axios from 'axios';
import MockAdapter from 'axios-mock-adapter';
import { shallowMount, createLocalVue } from '@vue/test-utils';
import { GlBreadcrumb, GlLoadingIcon } from '@gitlab/ui';
import httpStatusCodes from '~/lib/utils/http_status';
import ReportsApp from 'ee/analytics/reports/components/app.vue';
import createStore from 'ee/analytics/reports/store';
import { initialState, configData, pageData } from 'ee_jest/analytics/reports/mock_data';
const localVue = createLocalVue();
localVue.use(Vuex);
describe('ReportsApp', () => {
let wrapper;
let mock;
const createComponent = () => {
const component = shallowMount(ReportsApp, {
localVue,
store: createStore(),
});
component.vm.$store.dispatch('page/setInitialPageData', pageData);
return component;
};
const findGlBreadcrumb = () => wrapper.find(GlBreadcrumb);
const findGlLoadingIcon = () => wrapper.find(GlLoadingIcon);
beforeEach(() => {
mock = new MockAdapter(axios);
mock.onGet().reply(httpStatusCodes.OK, configData);
});
afterEach(() => {
mock.restore();
wrapper.destroy();
wrapper = null;
});
describe('loading icon', () => {
it('displays the icon while page config is being retrieved', async () => {
wrapper = createComponent();
await wrapper.vm.$nextTick();
expect(findGlLoadingIcon().exists()).toBe(true);
});
it('hides the icon once page config has being retrieved', async () => {
wrapper = createComponent();
wrapper.vm.$store.dispatch('page/receivePageConfigDataSuccess', configData);
await wrapper.vm.$nextTick();
expect(findGlLoadingIcon().exists()).toBe(false);
});
});
describe('contains the correct breadcrumbs', () => {
it('displays the "Report" title by default', () => {
wrapper = createComponent();
const {
config: { title },
} = initialState;
expect(findGlBreadcrumb().props('items')).toStrictEqual([{ text: title, href: '' }]);
});
describe('with a config specified', () => {
it('displays the group name and report title once retrieved', async () => {
wrapper = createComponent();
wrapper.vm.$store.dispatch('page/receivePageConfigDataSuccess', configData);
await wrapper.vm.$nextTick();
const { groupName, groupPath } = pageData;
const { title } = configData;
expect(findGlBreadcrumb().props('items')).toStrictEqual([
{ text: groupName, href: `/${groupPath}` },
{ text: title, href: '' },
]);
});
});
});
});
export const initialState = {
configEndpoint: '',
reportId: null,
groupName: null,
groupPath: null,
config: {
title: 'Report',
},
};
export const pageData = {
configEndpoint: 'foo_bar_endpoint',
reportId: 'foo_bar_id',
groupName: 'Foo Bar',
groupPath: 'foo_bar',
};
export const configData = {
title: 'Foo Bar Report',
};
import axios from 'axios';
import MockAdapter from 'axios-mock-adapter';
import httpStatusCodes from '~/lib/utils/http_status';
import createFlash from '~/flash';
import testAction from 'helpers/vuex_action_helper';
import * as actions from 'ee/analytics/reports/store/modules/page/actions';
import { initialState, pageData, configData } from 'ee_jest/analytics/reports/mock_data';
jest.mock('~/flash');
describe('Reports page actions', () => {
let state;
let mock;
beforeEach(() => {
state = initialState;
mock = new MockAdapter(axios);
});
afterEach(() => {
state = null;
mock.restore();
});
it.each`
action | type | payload
${'setInitialPageData'} | ${'SET_INITIAL_PAGE_DATA'} | ${pageData}
${'requestPageConfigData'} | ${'REQUEST_PAGE_CONFIG_DATA'} | ${null}
${'receivePageConfigDataSuccess'} | ${'RECEIVE_PAGE_CONFIG_DATA_SUCCESS'} | ${configData}
${'receivePageConfigDataError'} | ${'RECEIVE_PAGE_CONFIG_DATA_ERROR'} | ${null}
`('$action commits mutation $type with $payload', ({ action, type, payload }) => {
return testAction(
actions[action],
payload,
state,
[payload ? { type, payload } : { type }],
[],
);
});
describe('receivePageConfigDataError', () => {
it('displays an error message', () => {
actions.receivePageConfigDataError({ commit: jest.fn() });
expect(createFlash).toHaveBeenCalledWith(
'There was an error while fetching configuration data.',
);
});
});
describe('fetchPageConfigData', () => {
describe('success', () => {
beforeEach(() => {
mock.onGet().reply(httpStatusCodes.OK, configData);
});
it('dispatches the "requestPageConfigData" and "receivePageConfigDataSuccess" actions', () => {
return testAction(
actions.fetchPageConfigData,
null,
state,
[],
[
{ type: 'requestPageConfigData' },
{ type: 'receivePageConfigDataSuccess', payload: configData },
],
);
});
});
describe('failure', () => {
beforeEach(() => {
mock.onGet().reply(httpStatusCodes.NOT_FOUND);
});
it('dispatches the "requestPageConfigData" and "receivePageConfigDataError" actions', () => {
return testAction(
actions.fetchPageConfigData,
null,
state,
[],
[{ type: 'requestPageConfigData' }, { type: 'receivePageConfigDataError' }],
);
});
});
});
});
import * as types from 'ee/analytics/reports/store/modules/page/mutation_types';
import mutations from 'ee/analytics/reports/store/modules/page/mutations';
import { initialState, pageData, configData } from 'ee_jest/analytics/reports/mock_data';
describe('Reports page mutations', () => {
let state;
beforeEach(() => {
state = initialState;
});
afterEach(() => {
state = null;
});
it.each`
mutation | stateKey | value
${types.REQUEST_PAGE_CONFIG_DATA} | ${'isLoading'} | ${true}
${types.RECEIVE_PAGE_CONFIG_DATA_ERROR} | ${'isLoading'} | ${false}
`('$mutation will set $stateKey=$value', ({ mutation, stateKey, value }) => {
mutations[mutation](state);
expect(state[stateKey]).toEqual(value);
});
it.each`
mutation | payload | expectedState
${types.SET_INITIAL_PAGE_DATA} | ${pageData} | ${pageData}
${types.RECEIVE_PAGE_CONFIG_DATA_SUCCESS} | ${configData} | ${{ config: configData, isLoading: false }}
`(
'$mutation with payload $payload will update state with $expectedState',
({ mutation, payload, expectedState }) => {
mutations[mutation](state, payload);
expect(state).toMatchObject(expectedState);
},
);
});
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Gitlab::Analytics::Reports::ConfigLoader do
let(:report_id) { 'recent_merge_requests_by_group' }
shared_examples 'missing report_id' do
it 'raises ReportNotFoundError' do
expect { subject }.to raise_error(described_class::MissingReportError)
end
end
shared_examples 'missing series_id' do
it 'raises ReportNotFoundError' do
expect { subject }.to raise_error(described_class::MissingSeriesError)
end
end
describe '#find_report_by_id' do
subject { described_class.new.find_report_by_id!(report_id) }
context 'when unknown report_id is given' do
let(:report_id) { 'unknown_report_id' }
include_examples 'missing report_id'
end
context 'when nil report_id is given' do
let(:report_id) { nil }
include_examples 'missing report_id'
end
it 'loads the report configuration' do
expect(subject.title).to eq('Recent Issues (90 days)')
end
end
describe '#find_series_by_id' do
let(:series_id) { 'open_merge_requests' }
subject { described_class.new.find_series_by_id!(report_id, series_id) }
context 'when unknown report_id is given' do
let(:report_id) { 'unknown_report_id' }
include_examples 'missing report_id'
end
context 'when unknown series_id is given' do
let(:series_id) { 'unknown_series_id' }
include_examples 'missing series_id'
end
context 'when nil series_id is given' do
let(:series_id) { nil }
include_examples 'missing series_id'
end
it 'loads the report configuration' do
expect(subject.title).to eq('Merge Requests')
end
end
end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Gitlab::Analytics::Reports::ReportBuilder do
describe '#build' do
let(:config_path) { Rails.root.join(Gitlab::Analytics::Reports::ConfigLoader::DEFAULT_CONFIG).to_s }
let(:report_file) { Gitlab::Config::Loader::Yaml.new(File.read(config_path)).load! }
subject { described_class.build(report_file[:recent_merge_requests_by_group]) }
it 'builds the report object' do
expect(subject).to be_a_kind_of(Gitlab::Analytics::Reports::Report)
expect(subject.chart).to be_a_kind_of(Gitlab::Analytics::Reports::Chart)
expect(subject.chart.series.first).to be_a_kind_of(Gitlab::Analytics::Reports::Series)
end
end
end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Gitlab::Analytics::Reports::Report do
describe '#find_series_by_id' do
let(:series) { Gitlab::Analytics::Reports::Series.new(id: 'series_1', title: 'series title', data_retrieval_options: nil) }
let(:chart) { Gitlab::Analytics::Reports::Chart.new(type: 'bar', series: [series]) }
subject { described_class.new(id: 'id', title: 'title', chart: chart) }
it 'returns the series object' do
expect(subject.find_series_by_id('series_1')).to eq(series)
end
it 'returns nil when series cannot be found' do
expect(subject.find_series_by_id('unknown')).to be_nil
end
end
end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe API::Analytics::Reports do
let_it_be(:user) { create(:user) }
let_it_be(:report_id) { 'recent_merge_requests_by_group' }
shared_examples 'error response examples' do
context 'when `report_pages` feature flag is off' do
before do
stub_feature_flags(report_pages: false)
end
it 'returns 404, not found' do
api_call
expect(response).to have_gitlab_http_status(:not_found)
end
end
context 'when `report_pages` license is missing' do
before do
stub_feature_flags(report_pages: true)
stub_licensed_features(group_activity_analytics: false)
end
it 'returns 404, not found' do
api_call
expect(response).to have_gitlab_http_status(:not_found)
end
end
end
describe 'GET /analytics/reports/:id/chart' do
subject(:api_call) do
get api("/analytics/reports/#{report_id}/chart", user)
end
before do
stub_licensed_features(group_activity_analytics: true)
end
it 'is successful' do
api_call
expect(response).to have_gitlab_http_status(:ok)
expect(response.parsed_body['id']).to eq(report_id)
expect(response.parsed_body).to match_schema('analytics/reports/chart', dir: 'ee')
end
context 'when unknown report_id is given' do
let(:report_id) { 'unknown_report_id' }
it 'renders 404, not found' do
api_call
expect(response).to have_gitlab_http_status(:not_found)
expect(response.parsed_body['message']).to eq('404 Report(unknown_report_id) Not Found')
end
end
include_examples 'error response examples'
end
describe 'GET /analytics/series/:report_id/:series_id' do
let_it_be(:series_id) { 'some_series_id' }
subject(:api_call) do
get api("/analytics/series/#{report_id}/#{series_id}", user)
end
it 'is successful' do
api_call
expect(response).to have_gitlab_http_status(:ok)
expect(response.parsed_body['datasets'].size).to eq(1)
end
include_examples 'error response examples'
end
end
......@@ -10702,9 +10702,6 @@ msgstr ""
msgid "Generate new token"
msgstr ""
msgid "GenericReports|Report"
msgstr ""
msgid "Geo"
msgstr ""
......@@ -19901,9 +19898,6 @@ msgstr ""
msgid "Reporting"
msgstr ""
msgid "Reports"
msgstr ""
msgid "Reports|%{combinedString} and %{resolvedString}"
msgstr ""
......@@ -24029,9 +24023,6 @@ msgstr ""
msgid "There was an error when unsubscribing from this label."
msgstr ""
msgid "There was an error while fetching configuration data."
msgstr ""
msgid "There was an error while fetching value stream analytics data."
msgstr ""
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment