Commit 6d043f42 authored by Clement Ho's avatar Clement Ho

Merge branch '37437-remove-dormant-code-analytics-related-code' into 'master'

Remove FE dormant code analytics related code

See merge request gitlab-org/gitlab!21661
parents d31dff94 4a93ab1f
<script>
import { GlEmptyState } from '@gitlab/ui';
import { mapActions, mapState } from 'vuex';
import GroupsDropdownFilter from '../../shared/components/groups_dropdown_filter.vue';
import ProjectsDropdownFilter from '../../shared/components/projects_dropdown_filter.vue';
import FileQuantityDropdown from './file_quantity_dropdown.vue';
import { featureAccessLevel } from '~/pages/projects/shared/permissions/constants';
import { PROJECTS_PER_PAGE, DEFAULT_FILE_QUANTITY } from '../constants';
import createStore from '../store';
import { LAST_ACTIVITY_AT } from '../../shared/constants';
export default {
name: 'CodeAnalytics',
store: createStore(),
components: {
GlEmptyState,
GroupsDropdownFilter,
ProjectsDropdownFilter,
FileQuantityDropdown,
},
props: {
emptyStateSvgPath: {
type: String,
required: true,
},
},
data() {
return {
multiProjectSelect: false,
};
},
computed: {
...mapState(['selectedGroup', 'selectedProject', 'selectedFileQuantity']),
displayFileQuantityFilter() {
return this.selectedGroup && this.selectedProject;
},
},
mounted() {
this.setSelectedFileQuantity(DEFAULT_FILE_QUANTITY);
},
methods: {
...mapActions(['setSelectedGroup', 'setSelectedProject', 'setSelectedFileQuantity']),
onGroupSelect(group) {
this.setSelectedGroup(group);
},
onProjectSelect(projects) {
const project = projects.length ? projects[0] : null;
this.setSelectedProject(project);
},
onFileQuantitySelect(fileQuantity) {
this.setSelectedFileQuantity(fileQuantity);
},
},
groupsQueryParams: {
min_access_level: featureAccessLevel.EVERYONE,
},
projectsQueryParams: {
per_page: PROJECTS_PER_PAGE,
with_shared: false,
order_by: LAST_ACTIVITY_AT,
},
};
</script>
<template>
<div>
<div class="page-title-holder d-flex align-items-center">
<h3 class="page-title">{{ __('Code Analytics') }}</h3>
</div>
<div class="mw-100">
<div
class="mt-3 py-2 px-3 d-flex bg-gray-light border-top border-bottom flex-column flex-md-row justify-content-start"
>
<groups-dropdown-filter
class="dropdown-select"
:query-params="$options.groupsQueryParams"
@selected="onGroupSelect"
/>
<projects-dropdown-filter
v-if="selectedGroup"
:key="selectedGroup.id"
class="ml-md-1 mt-1 mt-md-0 dropdown-select"
:group-id="selectedGroup.id"
:query-params="$options.projectsQueryParams"
:multi-select="multiProjectSelect"
@selected="onProjectSelect"
/>
<div
v-if="displayFileQuantityFilter"
class="ml-0 ml-md-auto mt-2 mt-md-0 d-flex flex-column flex-md-row align-items-md-center justify-content-md-end"
>
<label class="text-bold mb-0 mr-2">{{ s__('CodeAnalytics|Max files') }}</label>
<file-quantity-dropdown
:selected="selectedFileQuantity"
@selected="onFileQuantitySelect"
/>
</div>
</div>
</div>
<gl-empty-state
:title="__('Identify the most frequently changed files in your repository')"
:description="
__(
'Identify areas of the codebase associated with a lot of churn, which can indicate potential code hotspots.',
)
"
:svg-path="emptyStateSvgPath"
/>
</div>
</template>
<script>
import { GlDropdown, GlDropdownItem } from '@gitlab/ui';
import { FILE_QUANTITIES } from '../constants';
export default {
components: {
GlDropdown,
GlDropdownItem,
},
props: {
selected: {
type: Number,
required: true,
},
fileQuantityOptions: {
type: Array,
required: false,
default: () => FILE_QUANTITIES,
},
},
computed: {
selectedFileQuantityText() {
return this.selected.toString();
},
},
methods: {
onSelect(fileQuantity) {
this.$emit('selected', fileQuantity);
},
},
};
</script>
<template>
<gl-dropdown
toggle-class="dropdown-menu-toggle w-100"
menu-class="w-100 mw-100"
:text="selectedFileQuantityText"
>
<gl-dropdown-item
v-for="option in fileQuantityOptions"
:key="option"
class="w-100"
@click="onSelect(option)"
>{{ option }}</gl-dropdown-item
>
</gl-dropdown>
</template>
export const PROJECTS_PER_PAGE = 50;
export const FILE_QUANTITIES = [25, 50, 100, 250, 500];
export const DEFAULT_FILE_QUANTITY = FILE_QUANTITIES[2];
import Vue from 'vue';
import CodeAnalytics from './components/app.vue';
export default () => {
const el = document.querySelector('#js-code-analytics-app');
const { emptyStateSvgPath } = el.dataset;
return new Vue({
el,
name: 'CodeAnalyticsApp',
components: {
CodeAnalytics,
},
render: createElement =>
createElement(CodeAnalytics, {
props: {
emptyStateSvgPath,
},
}),
});
};
import * as types from './mutation_types';
export const setSelectedGroup = ({ commit }, group) => commit(types.SET_SELECTED_GROUP, group);
export const setSelectedProject = ({ commit }, project) =>
commit(types.SET_SELECTED_PROJECT, project);
export const setSelectedFileQuantity = ({ commit }, fileQuantity) =>
commit(types.SET_SELECTED_FILE_QUANTITY, fileQuantity);
// prevent babel-plugin-rewire from generating an invalid default during karma tests
export default () => {};
import Vue from 'vue';
import Vuex from 'vuex';
import * as actions from './actions';
import mutations from './mutations';
import state from './state';
Vue.use(Vuex);
export default () =>
new Vuex.Store({
actions,
mutations,
state,
});
export const SET_SELECTED_GROUP = 'SET_SELECTED_GROUP';
export const SET_SELECTED_PROJECT = 'SET_SELECTED_PROJECT';
export const SET_SELECTED_FILE_QUANTITY = 'SET_SELECTED_FILE_QUANTITY';
import * as types from './mutation_types';
export default {
[types.SET_SELECTED_GROUP](state, group) {
state.selectedGroup = group;
state.selectedProject = null;
},
[types.SET_SELECTED_PROJECT](state, project) {
state.selectedProject = project;
},
[types.SET_SELECTED_FILE_QUANTITY](state, fileQuantity) {
state.selectedFileQuantity = fileQuantity;
},
};
export default {
selectedGroup: null,
selectedProject: null,
selectedFileQuantity: null,
};
import initCodeAnalyticsApp from 'ee/analytics/code_analytics/index';
document.addEventListener('DOMContentLoaded', initCodeAnalyticsApp);
- page_title _("Code Analytics") - page_title _("Code Analytics")
#js-code-analytics-app{ data: { "empty-state-svg-path" => image_path("illustrations/monitoring/getting_started.svg")} }
...@@ -31,18 +31,6 @@ ...@@ -31,18 +31,6 @@
= link_to analytics_cycle_analytics_path do = link_to analytics_cycle_analytics_path do
%strong.fly-out-top-item-name %strong.fly-out-top-item-name
= _('Cycle Analytics') = _('Cycle Analytics')
- if Gitlab::Analytics.code_analytics_enabled?
= nav_link(controller: :code_analytics) do
= link_to analytics_code_analytics_path, class: 'qa-sidebar-code-analytics' do
.nav-icon-container
= sprite_icon('code')
%span.nav-item-name
= _('Code Analytics')
%ul.sidebar-sub-level-items.is-fly-out-only
= nav_link(controller: :code_analytics, html_options: { class: "fly-out-top-item qa-sidebar-code-analytics-fly-out" } ) do
= link_to analytics_code_analytics_path do
%strong.fly-out-top-item-name
= _('Code Analytics')
= render_ce 'layouts/nav/sidebar/instance_statistics_links' = render_ce 'layouts/nav/sidebar/instance_statistics_links'
......
import Vuex from 'vuex';
import { createLocalVue, shallowMount } from '@vue/test-utils';
import { GlEmptyState } from '@gitlab/ui';
import Component from 'ee/analytics/code_analytics/components/app.vue';
import GroupsDropdownFilter from 'ee/analytics/shared/components/groups_dropdown_filter.vue';
import ProjectsDropdownFilter from 'ee/analytics/shared/components/projects_dropdown_filter.vue';
import FileQuantityDropdown from 'ee/analytics/code_analytics/components/file_quantity_dropdown.vue';
import { group, project, DEFAULT_FILE_QUANTITY } from '../mock_data';
const emptyStateTitle = 'Identify the most frequently changed files in your repository';
const emptyStateDescription =
'Identify areas of the codebase associated with a lot of churn, which can indicate potential code hotspots.';
const emptyStateSvgPath = 'path/to/empty/state';
const localVue = createLocalVue();
localVue.use(Vuex);
let wrapper;
const createComponent = (opts = {}) =>
shallowMount(Component, {
localVue,
sync: false,
propsData: {
emptyStateSvgPath,
},
...opts,
});
describe('Code Analytics component', () => {
beforeEach(() => {
wrapper = createComponent();
});
afterEach(() => {
wrapper.destroy();
});
describe('mounted', () => {
const actionSpies = {
setSelectedFileQuantity: jest.fn(),
};
beforeEach(() => {
wrapper = createComponent({ methods: actionSpies });
});
it('dispatches setSelectedFileQuantity with DEFAULT_FILE_QUANTITY', () => {
expect(actionSpies.setSelectedFileQuantity).toHaveBeenCalledWith(DEFAULT_FILE_QUANTITY);
});
});
describe('methods', () => {
describe('onProjectSelect', () => {
it('sets the project to null if no projects are submitted', () => {
wrapper.vm.onProjectSelect([]);
expect(wrapper.vm.$store.state.selectedProject).toBe(null);
});
it('sets the project correctly when submitted', () => {
wrapper.vm.onProjectSelect([project]);
expect(wrapper.vm.$store.state.selectedProject).toBe(project);
});
});
});
describe('displays the components as required', () => {
describe('before a group has been selected', () => {
it('displays an empty state', () => {
const emptyState = wrapper.find(GlEmptyState);
expect(emptyState.exists()).toBeTruthy();
expect(emptyState.props('title')).toBe(emptyStateTitle);
expect(emptyState.props('description')).toBe(emptyStateDescription);
expect(emptyState.props('svgPath')).toBe(emptyStateSvgPath);
});
it('shows the groups filter', () => {
expect(wrapper.find(GroupsDropdownFilter).exists()).toBeTruthy();
});
it('does not show the projects filter', () => {
expect(wrapper.find(ProjectsDropdownFilter).exists()).toBeFalsy();
});
it('does not show the file quantity filter', () => {
expect(wrapper.find(FileQuantityDropdown).exists()).toBeFalsy();
});
});
describe('after a group has been selected', () => {
beforeEach(() => {
wrapper.vm.$store.state.selectedGroup = group;
});
describe('with no project selected', () => {
beforeEach(() => {
wrapper.vm.$store.state.selectedProject = null;
});
it('still displays an empty state', () => {
const emptyState = wrapper.find(GlEmptyState);
expect(emptyState.exists()).toBeTruthy();
expect(emptyState.props('title')).toBe(emptyStateTitle);
expect(emptyState.props('description')).toBe(emptyStateDescription);
expect(emptyState.props('svgPath')).toBe(emptyStateSvgPath);
});
it('still shows the groups filter', () => {
expect(wrapper.find(GroupsDropdownFilter).exists()).toBeTruthy();
});
it('shows the projects filter', () => {
expect(wrapper.find(ProjectsDropdownFilter).exists()).toBeTruthy();
});
it('does not show the file quantity filter', () => {
expect(wrapper.find(FileQuantityDropdown).exists()).toBeFalsy();
});
});
describe('with a project selected', () => {
beforeEach(() => {
wrapper.vm.$store.state.selectedProject = project;
});
// This is until the empty state is replaced in a future iteration
// https://gitlab.com/gitlab-org/gitlab/merge_requests/18395
it('still displays an empty state', () => {
const emptyState = wrapper.find(GlEmptyState);
expect(emptyState.exists()).toBeTruthy();
expect(emptyState.props('title')).toBe(emptyStateTitle);
expect(emptyState.props('description')).toBe(emptyStateDescription);
expect(emptyState.props('svgPath')).toBe(emptyStateSvgPath);
});
it('still shows the groups filter', () => {
expect(wrapper.find(GroupsDropdownFilter).exists()).toBeTruthy();
});
it('shows the projects filter', () => {
expect(wrapper.find(ProjectsDropdownFilter).exists()).toBeTruthy();
});
it('shows the file quantity filter', () => {
expect(wrapper.find(FileQuantityDropdown).exists()).toBeTruthy();
});
});
});
});
});
import { shallowMount } from '@vue/test-utils';
import { GlDropdownItem } from '@gitlab/ui';
import FileQuantityDropdown from 'ee/analytics/code_analytics/components/file_quantity_dropdown.vue';
import { DEFAULT_FILE_QUANTITY } from '../mock_data';
describe('FileQuantityDropdown component', () => {
let wrapper;
const createComponent = (props = {}) =>
shallowMount(FileQuantityDropdown, {
propsData: {
selected: DEFAULT_FILE_QUANTITY,
...props,
},
});
const findDropdownElements = () => wrapper.findAll(GlDropdownItem);
const findFirstDropdownElement = () => findDropdownElements().at(0);
beforeEach(() => {
wrapper = createComponent();
});
afterEach(() => {
wrapper.destroy();
});
describe('the default behaviour acts as expected', () => {
it('renders the default dropdown items', () => {
expect(findDropdownElements().length).toBe(5);
});
it('displays the correct label for the first dropdown item', () => {
expect(findFirstDropdownElement().text()).toBe('25');
});
it('emits the "selected" event with the selected item value', () => {
findFirstDropdownElement().vm.$emit('click');
expect(wrapper.emitted().selected[0]).toEqual([25]);
});
});
describe('the component renders the correct dropdown text when selected is passed through', () => {
beforeEach(() => {
wrapper = createComponent({ selected: 250, fileQuantityOptions: [100, 250, 500] });
});
afterEach(() => {
wrapper.destroy();
});
it('renders the default dropdown items', () => {
expect(findDropdownElements().length).toBe(3);
});
it('displays the correct label for the first dropdown item', () => {
expect(findFirstDropdownElement().text()).toBe('100');
});
it('emits the "selected" event with the selected item value', () => {
findFirstDropdownElement().vm.$emit('click');
expect(wrapper.emitted().selected[0]).toEqual([100]);
});
});
});
export const group = {
id: 1,
name: 'foo',
path: 'foo',
avatar_url: 'host/images/group/image.svg',
};
export const project = {
id: 1,
name: 'bar',
path: 'bar',
avatar_url: 'host/images/project/image.svg',
};
export const DEFAULT_FILE_QUANTITY = 100;
import testAction from 'helpers/vuex_action_helper';
import * as actions from 'ee/analytics/code_analytics/store/actions';
import * as types from 'ee/analytics/code_analytics/store/mutation_types';
import { group, project } from '../mock_data';
describe('Cycle analytics actions', () => {
let state;
beforeEach(() => {
state = {};
});
it.each`
action | type | stateKey | payload
${'setSelectedGroup'} | ${types.SET_SELECTED_GROUP} | ${'selectedGroup'} | ${group.name}
${'setSelectedProject'} | ${types.SET_SELECTED_PROJECT} | ${'selectedProject'} | ${project}
${'setSelectedFileQuantity'} | ${types.SET_SELECTED_FILE_QUANTITY} | ${'selectedFileQuantity'} | ${250}
`('$action should set $stateKey with $payload and type $type', ({ action, type, payload }) => {
testAction(
actions[action],
payload,
state,
[
{
type,
payload,
},
],
[],
);
});
});
import mutations from 'ee/analytics/code_analytics/store/mutations';
import * as types from 'ee/analytics/code_analytics/store/mutation_types';
import { group, project } from '../mock_data';
describe('Cycle analytics mutations', () => {
let state;
beforeEach(() => {
state = {};
});
afterEach(() => {
state = {};
});
it.each`
mutation | payload | expectedState
${types.SET_SELECTED_GROUP} | ${group.name} | ${{ selectedGroup: group.name, selectedProject: null }}
${types.SET_SELECTED_PROJECT} | ${project} | ${{ selectedProject: project }}
${types.SET_SELECTED_FILE_QUANTITY} | ${250} | ${{ selectedFileQuantity: 250 }}
`(
'$mutation with payload $payload will update state with $expectedState',
({ mutation, payload, expectedState }) => {
mutations[mutation](state, payload);
expect(state).toMatchObject(expectedState);
},
);
});
...@@ -4432,9 +4432,6 @@ msgstr "" ...@@ -4432,9 +4432,6 @@ msgstr ""
msgid "Code owners" msgid "Code owners"
msgstr "" msgstr ""
msgid "CodeAnalytics|Max files"
msgstr ""
msgid "CodeOwner|Pattern" msgid "CodeOwner|Pattern"
msgstr "" msgstr ""
...@@ -9513,12 +9510,6 @@ msgstr "" ...@@ -9513,12 +9510,6 @@ msgstr ""
msgid "Identifier" msgid "Identifier"
msgstr "" msgstr ""
msgid "Identify areas of the codebase associated with a lot of churn, which can indicate potential code hotspots."
msgstr ""
msgid "Identify the most frequently changed files in your repository"
msgstr ""
msgid "Identities" msgid "Identities"
msgstr "" 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