diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2021-10-20 11:43:02 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2021-10-20 11:43:02 +0300 |
commit | d9ab72d6080f594d0b3cae15f14b3ef2c6c638cb (patch) | |
tree | 2341ef426af70ad1e289c38036737e04b0aa5007 /app/assets/javascripts/analytics | |
parent | d6e514dd13db8947884cd58fe2a9c2a063400a9b (diff) |
Add latest changes from gitlab-org/gitlab@14-4-stable-eev14.4.0-rc42
Diffstat (limited to 'app/assets/javascripts/analytics')
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, + }; + }); +}; |