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