summaryrefslogtreecommitdiff
path: root/app/assets/javascripts/analytics
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2021-10-20 08:43:02 +0000
committerGitLab Bot <gitlab-bot@gitlab.com>2021-10-20 08:43:02 +0000
commitd9ab72d6080f594d0b3cae15f14b3ef2c6c638cb (patch)
tree2341ef426af70ad1e289c38036737e04b0aa5007 /app/assets/javascripts/analytics
parentd6e514dd13db8947884cd58fe2a9c2a063400a9b (diff)
downloadgitlab-ce-d9ab72d6080f594d0b3cae15f14b3ef2c6c638cb.tar.gz
Add latest changes from gitlab-org/gitlab@14-4-stable-eev14.4.0-rc42
Diffstat (limited to 'app/assets/javascripts/analytics')
-rw-r--r--app/assets/javascripts/analytics/shared/components/projects_dropdown_filter.vue91
-rw-r--r--app/assets/javascripts/analytics/shared/constants.js1
-rw-r--r--app/assets/javascripts/analytics/shared/utils.js62
3 files changed, 132 insertions, 22 deletions
diff --git a/app/assets/javascripts/analytics/shared/components/projects_dropdown_filter.vue b/app/assets/javascripts/analytics/shared/components/projects_dropdown_filter.vue
index a490111e13b..0bdb45d35c9 100644
--- a/app/assets/javascripts/analytics/shared/components/projects_dropdown_filter.vue
+++ b/app/assets/javascripts/analytics/shared/components/projects_dropdown_filter.vue
@@ -15,6 +15,8 @@ import { DEFAULT_DEBOUNCE_AND_THROTTLE_MS } from '~/lib/utils/constants';
import { n__, s__, __ } from '~/locale';
import getProjects from '../graphql/projects.query.graphql';
+const sortByProjectName = (projects = []) => projects.sort((a, b) => a.name.localeCompare(b.name));
+
export default {
name: 'ProjectsDropdownFilter',
components: {
@@ -88,6 +90,9 @@ export default {
selectedProjectIds() {
return this.selectedProjects.map((p) => p.id);
},
+ hasSelectedProjects() {
+ return Boolean(this.selectedProjects.length);
+ },
availableProjects() {
return filterBySearchTerm(this.projects, this.searchTerm);
},
@@ -95,6 +100,12 @@ export default {
const { loading, availableProjects } = this;
return !loading && !availableProjects.length;
},
+ selectedItems() {
+ return sortByProjectName(this.selectedProjects);
+ },
+ unselectedItems() {
+ return this.availableProjects.filter(({ id }) => !this.selectedProjectIds.includes(id));
+ },
},
watch: {
searchTerm() {
@@ -105,44 +116,53 @@ export default {
this.search();
},
methods: {
+ handleUpdatedSelectedProjects() {
+ this.$emit('selected', this.selectedProjects);
+ },
search: debounce(function debouncedSearch() {
this.fetchData();
}, DEFAULT_DEBOUNCE_AND_THROTTLE_MS),
- getSelectedProjects(selectedProject, isMarking) {
- return isMarking
+ getSelectedProjects(selectedProject, isSelected) {
+ return isSelected
? this.selectedProjects.concat([selectedProject])
: this.selectedProjects.filter((project) => project.id !== selectedProject.id);
},
singleSelectedProject(selectedObj, isMarking) {
return isMarking ? [selectedObj] : [];
},
- setSelectedProjects(selectedObj, isMarking) {
+ setSelectedProjects(project) {
this.selectedProjects = this.multiSelect
- ? this.getSelectedProjects(selectedObj, isMarking)
- : this.singleSelectedProject(selectedObj, isMarking);
+ ? this.getSelectedProjects(project, !this.isProjectSelected(project))
+ : this.singleSelectedProject(project, !this.isProjectSelected(project));
},
- onClick({ project, isSelected }) {
- this.setSelectedProjects(project, !isSelected);
- this.$emit('selected', this.selectedProjects);
+ onClick(project) {
+ this.setSelectedProjects(project);
+ this.handleUpdatedSelectedProjects();
},
- onMultiSelectClick({ project, isSelected }) {
- this.setSelectedProjects(project, !isSelected);
+ onMultiSelectClick(project) {
+ this.setSelectedProjects(project);
this.isDirty = true;
},
- onSelected(ev) {
+ onSelected(project) {
if (this.multiSelect) {
- this.onMultiSelectClick(ev);
+ this.onMultiSelectClick(project);
} else {
- this.onClick(ev);
+ this.onClick(project);
}
},
onHide() {
if (this.multiSelect && this.isDirty) {
- this.$emit('selected', this.selectedProjects);
+ this.handleUpdatedSelectedProjects();
}
this.searchTerm = '';
this.isDirty = false;
},
+ onClearAll() {
+ if (this.hasSelectedProjects) {
+ this.isDirty = true;
+ }
+ this.selectedProjects = [];
+ },
fetchData() {
this.loading = true;
@@ -168,8 +188,8 @@ export default {
this.projects = nodes;
});
},
- isProjectSelected(id) {
- return this.selectedProjects ? this.selectedProjectIds.includes(id) : false;
+ isProjectSelected(project) {
+ return this.selectedProjectIds.includes(project.id);
},
getEntityId(project) {
return getIdFromGraphQLId(project.id);
@@ -182,6 +202,10 @@ export default {
ref="projectsDropdown"
class="dropdown dropdown-projects"
toggle-class="gl-shadow-none"
+ :show-clear-all="hasSelectedProjects"
+ show-highlighted-items-title
+ highlighted-items-title-class="gl-p-3"
+ @clear-all.stop="onClearAll"
@hide="onHide"
>
<template #button-content>
@@ -204,14 +228,37 @@ export default {
<gl-dropdown-section-header>{{ __('Projects') }}</gl-dropdown-section-header>
<gl-search-box-by-type v-model.trim="searchTerm" />
</template>
+ <template #highlighted-items>
+ <gl-dropdown-item
+ v-for="project in selectedItems"
+ :key="project.id"
+ is-check-item
+ :is-checked="isProjectSelected(project)"
+ @click.native.capture.stop="onSelected(project)"
+ >
+ <div class="gl-display-flex">
+ <gl-avatar
+ class="gl-mr-2 gl-vertical-align-middle"
+ :alt="project.name"
+ :size="16"
+ :entity-id="getEntityId(project)"
+ :entity-name="project.name"
+ :src="project.avatarUrl"
+ shape="rect"
+ />
+ <div>
+ <div data-testid="project-name">{{ project.name }}</div>
+ <div class="gl-text-gray-500" data-testid="project-full-path">
+ {{ project.fullPath }}
+ </div>
+ </div>
+ </div>
+ </gl-dropdown-item>
+ </template>
<gl-dropdown-item
- v-for="project in availableProjects"
+ v-for="project in unselectedItems"
:key="project.id"
- :is-check-item="true"
- :is-checked="isProjectSelected(project.id)"
- @click.native.capture.stop="
- onSelected({ project, isSelected: isProjectSelected(project.id) })
- "
+ @click.native.capture.stop="onSelected(project)"
>
<div class="gl-display-flex">
<gl-avatar
diff --git a/app/assets/javascripts/analytics/shared/constants.js b/app/assets/javascripts/analytics/shared/constants.js
index 44d9b4b4262..c06bd34f86f 100644
--- a/app/assets/javascripts/analytics/shared/constants.js
+++ b/app/assets/javascripts/analytics/shared/constants.js
@@ -9,4 +9,5 @@ export const dateFormats = {
isoDate,
defaultDate: mediumDate,
defaultDateTime: 'mmm d, yyyy h:MMtt',
+ month: 'mmmm',
};
diff --git a/app/assets/javascripts/analytics/shared/utils.js b/app/assets/javascripts/analytics/shared/utils.js
index 52901d4c5bb..f55ef99964e 100644
--- a/app/assets/javascripts/analytics/shared/utils.js
+++ b/app/assets/javascripts/analytics/shared/utils.js
@@ -1,4 +1,5 @@
import dateFormat from 'dateformat';
+import { urlQueryToFilter } from '~/vue_shared/components/filtered_search_bar/filtered_search_utils';
import { dateFormats } from './constants';
export const filterBySearchTerm = (data = [], searchTerm = '', filterByKey = 'name') => {
@@ -7,3 +8,64 @@ export const filterBySearchTerm = (data = [], searchTerm = '', filterByKey = 'na
};
export const toYmd = (date) => dateFormat(date, dateFormats.isoDate);
+
+/**
+ * Takes a url and extracts query parameters used for the shared
+ * filter bar
+ *
+ * @param {string} url The URL to extract query parameters from
+ * @returns {Object}
+ */
+export const extractFilterQueryParameters = (url = '') => {
+ const {
+ source_branch_name = null,
+ target_branch_name = null,
+ author_username = null,
+ milestone_title = null,
+ assignee_username = [],
+ label_name = [],
+ } = urlQueryToFilter(url);
+
+ return {
+ selectedSourceBranch: source_branch_name,
+ selectedTargetBranch: target_branch_name,
+ selectedAuthor: author_username,
+ selectedMilestone: milestone_title,
+ selectedAssigneeList: assignee_username,
+ selectedLabelList: label_name,
+ };
+};
+
+/**
+ * Takes a url and extracts sorting and pagination query parameters into an object
+ *
+ * @param {string} url The URL to extract query parameters from
+ * @returns {Object}
+ */
+export const extractPaginationQueryParameters = (url = '') => {
+ const { sort, direction, page } = urlQueryToFilter(url);
+ return {
+ sort: sort?.value || null,
+ direction: direction?.value || null,
+ page: page?.value || null,
+ };
+};
+
+export const getDataZoomOption = ({
+ totalItems = 0,
+ maxItemsPerPage = 40,
+ dataZoom = [{ type: 'slider', bottom: 10, start: 0 }],
+}) => {
+ if (totalItems <= maxItemsPerPage) {
+ return {};
+ }
+
+ const intervalEnd = Math.ceil((maxItemsPerPage / totalItems) * 100);
+
+ return dataZoom.map((item) => {
+ return {
+ ...item,
+ end: intervalEnd,
+ };
+ });
+};