Commit 1e5ef1a2 authored by Ezekiel Kigbo's avatar Ezekiel Kigbo

Merge branch...

Merge branch '249529-productivity-analytics-use-similarity-search-option-for-graphql-projects-queries' into 'master'

PA: Use similarity search option for GraphQL projects queries

See merge request gitlab-org/gitlab!43539
parents 26cfa3c1 86665d7d
...@@ -210,6 +210,7 @@ export default { ...@@ -210,6 +210,7 @@ export default {
:key="currentGroup.id" :key="currentGroup.id"
class="js-projects-dropdown-filter project-select" class="js-projects-dropdown-filter project-select"
:group-id="currentGroup.id" :group-id="currentGroup.id"
:group-namespace="currentGroupPath"
:query-params="projectsQueryParams" :query-params="projectsQueryParams"
:multi-select="$options.multiProjectSelect" :multi-select="$options.multiProjectSelect"
:default-projects="selectedProjects" :default-projects="selectedProjects"
......
...@@ -4,7 +4,6 @@ import GroupsDropdownFilter from '../../shared/components/groups_dropdown_filter ...@@ -4,7 +4,6 @@ import GroupsDropdownFilter from '../../shared/components/groups_dropdown_filter
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import ProjectsDropdownFilter from '../../shared/components/projects_dropdown_filter.vue'; import ProjectsDropdownFilter from '../../shared/components/projects_dropdown_filter.vue';
import { accessLevelReporter, projectsPerPage } from '../constants'; import { accessLevelReporter, projectsPerPage } from '../constants';
import { SIMILARITY_ORDER, LAST_ACTIVITY_AT } from '../../shared/constants';
export default { export default {
components: { components: {
...@@ -44,10 +43,8 @@ export default { ...@@ -44,10 +43,8 @@ export default {
}, },
projectsQueryParams() { projectsQueryParams() {
return { return {
per_page: projectsPerPage, first: projectsPerPage,
with_shared: false, // exclude forks includeSubgroups: true,
order_by: this.glFeatures.analyticsSimilaritySearch ? SIMILARITY_ORDER : LAST_ACTIVITY_AT,
include_subgroups: true,
}; };
}, },
}, },
...@@ -59,13 +56,8 @@ export default { ...@@ -59,13 +56,8 @@ export default {
this.$emit('groupSelected', { groupId: id, groupNamespace: full_path }); this.$emit('groupSelected', { groupId: id, groupNamespace: full_path });
}, },
onProjectsSelected(selectedProjects) { onProjectsSelected(selectedProjects) {
let projectNamespace = null; const projectNamespace = selectedProjects[0]?.fullPath || null;
let projectId = null; const projectId = selectedProjects[0]?.id || null;
if (selectedProjects.length) {
projectNamespace = selectedProjects[0].path_with_namespace;
projectId = selectedProjects[0].id;
}
this.setProjectPath(projectNamespace); this.setProjectPath(projectNamespace);
this.$emit('projectSelected', { this.$emit('projectSelected', {
...@@ -98,6 +90,8 @@ export default { ...@@ -98,6 +90,8 @@ export default {
:default-projects="projects" :default-projects="projects"
:query-params="projectsQueryParams" :query-params="projectsQueryParams"
:group-id="groupId" :group-id="groupId"
:group-namespace="groupNamespace"
:use-graphql="true"
@selected="onProjectsSelected" @selected="onProjectsSelected"
/> />
</div> </div>
......
import Vue from 'vue'; import Vue from 'vue';
import VueApollo from 'vue-apollo';
import { mapState, mapActions } from 'vuex'; import { mapState, mapActions } from 'vuex';
import store from './store'; import store from './store';
import FilterDropdowns from './components/filter_dropdowns.vue'; import FilterDropdowns from './components/filter_dropdowns.vue';
import DateRange from '../shared/components/daterange.vue'; import DateRange from '../shared/components/daterange.vue';
import ProductivityAnalyticsApp from './components/app.vue'; import ProductivityAnalyticsApp from './components/app.vue';
import FilteredSearchProductivityAnalytics from './filtered_search_productivity_analytics'; import FilteredSearchProductivityAnalytics from './filtered_search_productivity_analytics';
import createDefaultClient from '~/lib/graphql';
import { parseBoolean } from '~/lib/utils/common_utils'; import { parseBoolean } from '~/lib/utils/common_utils';
import { getIdFromGraphQLId } from '~/graphql_shared/utils';
import { getLabelsEndpoint, getMilestonesEndpoint } from './utils'; import { getLabelsEndpoint, getMilestonesEndpoint } from './utils';
import { buildGroupFromDataset, buildProjectFromDataset } from '../shared/utils'; import { buildGroupFromDataset, buildProjectFromDataset } from '../shared/utils';
Vue.use(VueApollo);
const apolloProvider = new VueApollo({
defaultClient: createDefaultClient(),
});
export default () => { export default () => {
const container = document.getElementById('js-productivity-analytics'); const container = document.getElementById('js-productivity-analytics');
const groupProjectSelectContainer = container.querySelector('.js-group-project-select-container'); const groupProjectSelectContainer = container.querySelector('.js-group-project-select-container');
...@@ -65,6 +74,7 @@ export default () => { ...@@ -65,6 +74,7 @@ export default () => {
// eslint-disable-next-line no-new // eslint-disable-next-line no-new
new Vue({ new Vue({
el: groupProjectSelectContainer, el: groupProjectSelectContainer,
apolloProvider,
store, store,
created() { created() {
// let's not fetch any data by default since we might not have a valid group yet // let's not fetch any data by default since we might not have a valid group yet
...@@ -76,8 +86,8 @@ export default () => { ...@@ -76,8 +86,8 @@ export default () => {
this.initFilteredSearch({ this.initFilteredSearch({
groupNamespace: group.full_path, groupNamespace: group.full_path,
groupId: group.id, groupId: group.id,
projectNamespace: project ? project.path_with_namespace : null, projectNamespace: project?.path_with_namespace || null,
projectId: project ? project.id : null, projectId: container.dataset.projectId || null,
}); });
// let's fetch data now since we do have a valid group // let's fetch data now since we do have a valid group
...@@ -93,7 +103,12 @@ export default () => { ...@@ -93,7 +103,12 @@ export default () => {
this.initFilteredSearch({ groupNamespace, groupId }); this.initFilteredSearch({ groupNamespace, groupId });
}, },
onProjectSelected({ groupNamespace, groupId, projectNamespace, projectId }) { onProjectSelected({ groupNamespace, groupId, projectNamespace, projectId }) {
this.initFilteredSearch({ groupNamespace, groupId, projectNamespace, projectId }); this.initFilteredSearch({
groupNamespace,
groupId,
projectNamespace,
projectId: getIdFromGraphQLId(projectId),
});
}, },
initFilteredSearch({ groupNamespace, groupId, projectNamespace = '', projectId = null }) { initFilteredSearch({ groupNamespace, groupId, projectNamespace = '', projectId = null }) {
// let's unbind attached event handlers first and reset the template // let's unbind attached event handlers first and reset the template
......
...@@ -11,8 +11,10 @@ import { ...@@ -11,8 +11,10 @@ import {
} from '@gitlab/ui'; } from '@gitlab/ui';
import { n__, s__, __ } from '~/locale'; import { n__, s__, __ } from '~/locale';
import Api from '~/api'; import Api from '~/api';
import { getIdFromGraphQLId } from '~/graphql_shared/utils';
import { DATA_REFETCH_DELAY } from '../constants'; import { DATA_REFETCH_DELAY } from '../constants';
import { filterBySearchTerm } from '../utils'; import { filterBySearchTerm } from '../utils';
import getProjects from '../graphql/projects.query.graphql';
export default { export default {
name: 'ProjectsDropdownFilter', name: 'ProjectsDropdownFilter',
...@@ -30,6 +32,10 @@ export default { ...@@ -30,6 +32,10 @@ export default {
type: Number, type: Number,
required: true, required: true,
}, },
groupNamespace: {
type: String,
required: true,
},
multiSelect: { multiSelect: {
type: Boolean, type: Boolean,
required: false, required: false,
...@@ -50,6 +56,11 @@ export default { ...@@ -50,6 +56,11 @@ export default {
required: false, required: false,
default: () => [], default: () => [],
}, },
useGraphql: {
type: Boolean,
required: false,
default: false,
},
}, },
data() { data() {
return { return {
...@@ -121,6 +132,31 @@ export default { ...@@ -121,6 +132,31 @@ export default {
}, },
fetchData() { fetchData() {
this.loading = true; this.loading = true;
if (this.useGraphql) {
return this.$apollo
.query({
query: getProjects,
variables: {
groupFullPath: this.groupNamespace,
search: this.searchTerm,
...this.queryParams,
},
})
.then(response => {
const {
data: {
group: {
projects: { nodes },
},
},
} = response;
this.loading = false;
this.projects = nodes;
});
}
return Api.groupProjects(this.groupId, this.searchTerm, this.queryParams, projects => { return Api.groupProjects(this.groupId, this.searchTerm, this.queryParams, projects => {
this.projects = projects; this.projects = projects;
this.loading = false; this.loading = false;
...@@ -129,6 +165,11 @@ export default { ...@@ -129,6 +165,11 @@ export default {
isProjectSelected(id) { isProjectSelected(id) {
return this.selectedProjects ? this.selectedProjectIds.includes(id) : false; return this.selectedProjects ? this.selectedProjectIds.includes(id) : false;
}, },
getEntityId(project) {
if (this.useGraphql) return getIdFromGraphQLId(project.id);
return project?.id || null;
},
}, },
}; };
</script> </script>
...@@ -143,8 +184,8 @@ export default { ...@@ -143,8 +184,8 @@ export default {
<div class="gl-display-flex gl-flex-fill-1"> <div class="gl-display-flex gl-flex-fill-1">
<gl-avatar <gl-avatar
v-if="isOnlyOneProjectSelected" v-if="isOnlyOneProjectSelected"
:src="selectedProjects[0].avatar_url" :src="useGraphql ? selectedProjects[0].avatarUrl : selectedProjects[0].avatar_url"
:entity-id="selectedProjects[0].id" :entity-id="getEntityId(selectedProjects[0])"
:entity-name="selectedProjects[0].name" :entity-name="selectedProjects[0].name"
:size="16" :size="16"
shape="rect" shape="rect"
...@@ -170,9 +211,9 @@ export default { ...@@ -170,9 +211,9 @@ export default {
class="gl-mr-2 vertical-align-middle" class="gl-mr-2 vertical-align-middle"
:alt="project.name" :alt="project.name"
:size="16" :size="16"
:entity-id="project.id" :entity-id="getEntityId(project)"
:entity-name="project.name" :entity-name="project.name"
:src="project.avatar_url" :src="useGraphql ? project.avatarUrl : project.avatar_url"
shape="rect" shape="rect"
/> />
{{ project.name }} {{ project.name }}
......
query getGroupProjects(
$groupFullPath: ID!
$search: String!
$first: Int!
$includeSubgroups: Boolean = false
) {
group(fullPath: $groupFullPath) {
projects(
search: $search
first: $first
includeSubgroups: $includeSubgroups
sort: SIMILARITY
) {
nodes {
id
name
avatarUrl
fullPath
}
}
}
}
...@@ -39,11 +39,11 @@ export const buildGroupFromDataset = dataset => { ...@@ -39,11 +39,11 @@ export const buildGroupFromDataset = dataset => {
* @returns {Object} - A project object * @returns {Object} - A project object
*/ */
export const buildProjectFromDataset = dataset => { export const buildProjectFromDataset = dataset => {
const { projectId, projectName, projectPathWithNamespace, projectAvatarUrl } = dataset; const { projectGid, projectName, projectPathWithNamespace, projectAvatarUrl } = dataset;
if (projectId) { if (projectGid) {
return { return {
id: Number(projectId), id: projectGid,
name: projectName, name: projectName,
path_with_namespace: projectPathWithNamespace, path_with_namespace: projectPathWithNamespace,
avatar_url: projectAvatarUrl, avatar_url: projectAvatarUrl,
......
...@@ -66,6 +66,7 @@ module Analytics ...@@ -66,6 +66,7 @@ module Analytics
def project_data_attributes def project_data_attributes
{ {
id: project.id, id: project.id,
gid: project.to_gid.to_s,
name: project.name, name: project.name,
path_with_namespace: project.path_with_namespace, path_with_namespace: project.path_with_namespace,
avatar_url: project.avatar_url avatar_url: project.avatar_url
......
...@@ -21,7 +21,7 @@ describe('FilterDropdowns component', () => { ...@@ -21,7 +21,7 @@ describe('FilterDropdowns component', () => {
const groupId = 1; const groupId = 1;
const groupNamespace = 'gitlab-org'; const groupNamespace = 'gitlab-org';
const projectPath = 'gitlab-org/gitlab-test'; const projectPath = 'gitlab-org/gitlab-test';
const projectId = 10; const projectId = 'gid://gitlab/Project/1';
beforeEach(() => { beforeEach(() => {
const { const {
...@@ -41,6 +41,7 @@ describe('FilterDropdowns component', () => { ...@@ -41,6 +41,7 @@ describe('FilterDropdowns component', () => {
...modules, ...modules,
}, },
}); });
wrapper = shallowMount(FilterDropdowns, { wrapper = shallowMount(FilterDropdowns, {
localVue, localVue,
store: mockStore, store: mockStore,
...@@ -71,6 +72,7 @@ describe('FilterDropdowns component', () => { ...@@ -71,6 +72,7 @@ describe('FilterDropdowns component', () => {
describe('with a group selected', () => { describe('with a group selected', () => {
beforeEach(() => { beforeEach(() => {
wrapper.vm.groupId = groupId; wrapper.vm.groupId = groupId;
mockStore.state.filters.groupNamespace = groupNamespace;
}); });
it('renders the projects dropdown', () => { it('renders the projects dropdown', () => {
...@@ -107,7 +109,7 @@ describe('FilterDropdowns component', () => { ...@@ -107,7 +109,7 @@ describe('FilterDropdowns component', () => {
describe('when the list of selected projects is not empty', () => { describe('when the list of selected projects is not empty', () => {
beforeEach(() => { beforeEach(() => {
mockStore.state.filters.groupNamespace = groupNamespace; mockStore.state.filters.groupNamespace = groupNamespace;
wrapper.vm.onProjectsSelected([{ id: projectId, path_with_namespace: `${projectPath}` }]); wrapper.vm.onProjectsSelected([{ id: projectId, fullPath: `${projectPath}` }]);
}); });
it('invokes setProjectPath action', () => { it('invokes setProjectPath action', () => {
......
...@@ -22,6 +22,7 @@ const subGroupDataset = { ...@@ -22,6 +22,7 @@ const subGroupDataset = {
const projectDataset = { const projectDataset = {
projectId: '1', projectId: '1',
projectGid: 'gid://gitlab/Project/1',
projectName: 'My Project', projectName: 'My Project',
projectPathWithNamespace: 'my-group/my-project', projectPathWithNamespace: 'my-group/my-project',
}; };
...@@ -66,7 +67,7 @@ describe('buildProjectFromDataset', () => { ...@@ -66,7 +67,7 @@ describe('buildProjectFromDataset', () => {
it('returns a project object when the projectId is given', () => { it('returns a project object when the projectId is given', () => {
expect(buildProjectFromDataset(projectDataset)).toEqual({ expect(buildProjectFromDataset(projectDataset)).toEqual({
id: 1, id: 'gid://gitlab/Project/1',
name: 'My Project', name: 'My Project',
path_with_namespace: 'my-group/my-project', path_with_namespace: 'my-group/my-project',
avatar_url: undefined, avatar_url: undefined,
......
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