diff options
Diffstat (limited to 'app/assets')
13 files changed, 157 insertions, 130 deletions
diff --git a/app/assets/javascripts/search/sidebar/components/checkbox_filter.vue b/app/assets/javascripts/search/sidebar/components/checkbox_filter.vue index b580d58b21b..36cbe9ce693 100644 --- a/app/assets/javascripts/search/sidebar/components/checkbox_filter.vue +++ b/app/assets/javascripts/search/sidebar/components/checkbox_filter.vue @@ -1,6 +1,6 @@ <script> import { GlFormCheckboxGroup, GlFormCheckbox } from '@gitlab/ui'; -import { mapState, mapActions } from 'vuex'; +import { mapState, mapActions, mapGetters } from 'vuex'; import { intersection } from 'lodash'; import { NAV_LINK_COUNT_DEFAULT_CLASSES, LABEL_DEFAULT_CLASSES } from '../constants'; import { formatSearchResultCount } from '../../store/utils'; @@ -12,31 +12,29 @@ export default { GlFormCheckbox, }, props: { - filterData: { + filtersData: { type: Object, required: true, }, }, computed: { ...mapState(['query']), + ...mapGetters(['queryLangugageFilters']), scope() { return this.query.scope; }, - queryFilters() { - return this.query[this.filterData?.filterParam] || []; - }, dataFilters() { - return Object.values(this.filterData?.filters || []); + return Object.values(this.filtersData?.filters || []); }, flatDataFilterValues() { return this.dataFilters.map(({ value }) => value); }, selectedFilter: { get() { - return intersection(this.flatDataFilterValues, this.queryFilters); + return intersection(this.flatDataFilterValues, this.queryLangugageFilters); }, set(value) { - this.setQuery({ key: this.filterData?.filterParam, value }); + this.setQuery({ key: this.filtersData?.filterParam, value }); }, }, labelCountClasses() { @@ -56,7 +54,7 @@ export default { <template> <div class="gl-mx-5"> - <h5 class="gl-mt-0">{{ filterData.header }}</h5> + <h5 class="gl-mt-0">{{ filtersData.header }}</h5> <gl-form-checkbox-group v-model="selectedFilter"> <gl-form-checkbox v-for="f in dataFilters" diff --git a/app/assets/javascripts/search/sidebar/components/language_filter.vue b/app/assets/javascripts/search/sidebar/components/language_filter.vue index 26ce204cb5c..e5306cd7b79 100644 --- a/app/assets/javascripts/search/sidebar/components/language_filter.vue +++ b/app/assets/javascripts/search/sidebar/components/language_filter.vue @@ -27,10 +27,15 @@ export default { apply: __('Apply'), showingMax: sprintf(s__('GlobalSearch|Showing top %{maxItems}'), { maxItems: MAX_ITEM_LENGTH }), loadError: s__('GlobalSearch|Aggregations load error.'), + reset: s__('GlobalSearch|Reset filters'), }, computed: { ...mapState(['aggregations', 'sidebarDirty']), - ...mapGetters(['langugageAggregationBuckets']), + ...mapGetters([ + 'langugageAggregationBuckets', + 'currentUrlQueryHasLanguageFilters', + 'queryLangugageFilters', + ]), hasBuckets() { return this.langugageAggregationBuckets.length > 0; }, @@ -55,18 +60,33 @@ export default { dividerClasses() { return [...HR_DEFAULT_CLASSES, ...ONLY_SHOW_MD]; }, + hasQueryFilters() { + return this.queryLangugageFilters.length > 0; + }, }, async created() { await this.fetchLanguageAggregation(); }, methods: { - ...mapActions(['applyQuery', 'fetchLanguageAggregation']), + ...mapActions([ + 'applyQuery', + 'resetLanguageQuery', + 'resetLanguageQueryWithRedirect', + 'fetchLanguageAggregation', + ]), onShowMore() { this.showAll = true; }, trimBuckets(length) { return this.langugageAggregationBuckets.slice(0, length); }, + cleanResetFilters() { + if (this.currentUrlQueryHasLanguageFilters) { + return this.resetLanguageQueryWithRedirect(); + } + this.showAll = false; + return this.resetLanguageQuery(); + }, }, HR_DEFAULT_CLASSES, }; @@ -84,7 +104,7 @@ export default { class="gl-overflow-x-hidden gl-overflow-y-auto" :class="{ 'language-filter-max-height': showAll }" > - <checkbox-filter class="gl-px-5" :filter-data="filtersData" /> + <checkbox-filter :filters-data="filtersData" /> <span v-if="showAll && hasOverMax" data-testid="has-over-max-text">{{ $options.i18n.showingMax }}</span> @@ -106,7 +126,9 @@ export default { </div> <div v-if="!aggregations.error"> <hr :class="$options.HR_DEFAULT_CLASSES" /> - <div class="gl-display-flex gl-align-items-center gl-mt-4 gl-mx-5 gl-px-5"> + <div + class="gl-display-flex gl-align-items-center gl-justify-content-space-between gl-mt-4 gl-mx-5" + > <gl-button category="primary" variant="confirm" @@ -116,6 +138,16 @@ export default { > {{ $options.i18n.apply }} </gl-button> + <gl-button + category="tertiary" + variant="link" + size="small" + :disabled="!hasQueryFilters && !sidebarDirty" + data-testid="reset-button" + @click="cleanResetFilters" + > + {{ $options.i18n.reset }} + </gl-button> </div> </div> </gl-form> diff --git a/app/assets/javascripts/search/sidebar/utils.js b/app/assets/javascripts/search/sidebar/utils.js index 5c08ad2f959..4357d6202df 100644 --- a/app/assets/javascripts/search/sidebar/utils.js +++ b/app/assets/javascripts/search/sidebar/utils.js @@ -1,20 +1,17 @@ import { languageFilterData } from '~/search/sidebar/constants/language_filter_data'; -export const convertFiltersData = (rawBuckets) => { - return rawBuckets.reduce( - (acc, bucket) => { - return { - ...acc, - filters: { - ...acc.filters, - [bucket.key.toUpperCase()]: { - label: bucket.key, - value: bucket.key, - count: bucket.count, - }, +export const convertFiltersData = (rawBuckets) => + rawBuckets.reduce( + (acc, bucket) => ({ + ...acc, + filters: { + ...acc.filters, + [bucket.key.toUpperCase()]: { + label: bucket.key, + value: bucket.key, + count: bucket.count, }, - }; - }, + }, + }), { ...languageFilterData, filters: {} }, ); -}; diff --git a/app/assets/javascripts/search/store/actions.js b/app/assets/javascripts/search/store/actions.js index fc0817be882..667c56cc5ad 100644 --- a/app/assets/javascripts/search/store/actions.js +++ b/app/assets/javascripts/search/store/actions.js @@ -4,6 +4,7 @@ import axios from '~/lib/utils/axios_utils'; import { visitUrl, setUrlParams } from '~/lib/utils/url_utility'; import { logError } from '~/lib/logger'; import { __ } from '~/locale'; +import { languageFilterData } from '~/search/sidebar/constants/language_filter_data'; import { GROUPS_LOCAL_STORAGE_KEY, PROJECTS_LOCAL_STORAGE_KEY, SIDEBAR_PARAMS } from './constants'; import * as types from './mutation_types'; import { @@ -105,7 +106,17 @@ export const applyQuery = ({ state }) => { }; export const resetQuery = ({ state }) => { - visitUrl(setUrlParams({ ...state.query, page: null, state: null, confidential: null })); + visitUrl( + setUrlParams({ ...state.query, page: null, state: null, confidential: null }, undefined, true), + ); +}; + +export const resetLanguageQueryWithRedirect = ({ state }) => { + visitUrl(setUrlParams({ ...state.query, language: null }, undefined, true)); +}; + +export const resetLanguageQuery = ({ commit }) => { + commit(types.SET_QUERY, { key: languageFilterData?.filterParam, value: [] }); }; export const fetchSidebarCount = ({ commit, state }) => { diff --git a/app/assets/javascripts/search/store/getters.js b/app/assets/javascripts/search/store/getters.js index 0278239c144..853c83b07ee 100644 --- a/app/assets/javascripts/search/store/getters.js +++ b/app/assets/javascripts/search/store/getters.js @@ -1,4 +1,6 @@ +import { has } from 'lodash'; import { languageFilterData } from '~/search/sidebar/constants/language_filter_data'; + import { GROUPS_LOCAL_STORAGE_KEY, PROJECTS_LOCAL_STORAGE_KEY } from './constants'; export const frequentGroups = (state) => { @@ -16,3 +18,11 @@ export const langugageAggregationBuckets = (state) => { )?.buckets || [] ); }; + +export const queryLangugageFilters = (state) => { + return state.query[languageFilterData.filterParam] || []; +}; + +export const currentUrlQueryHasLanguageFilters = (state) => + has(state.urlQuery, languageFilterData.filterParam) && + state.urlQuery[languageFilterData.filterParam]?.length > 0; diff --git a/app/assets/javascripts/search/store/mutations.js b/app/assets/javascripts/search/store/mutations.js index f9fd69d2211..b2f9f5ab225 100644 --- a/app/assets/javascripts/search/store/mutations.js +++ b/app/assets/javascripts/search/store/mutations.js @@ -24,7 +24,7 @@ export default { state.projects = []; }, [types.SET_QUERY](state, { key, value }) { - state.query[key] = value; + state.query = { ...state.query, [key]: value }; }, [types.SET_SIDEBAR_DIRTY](state, value) { state.sidebarDirty = value; diff --git a/app/assets/javascripts/search/store/utils.js b/app/assets/javascripts/search/store/utils.js index acb99c60426..1e6619ca6d5 100644 --- a/app/assets/javascripts/search/store/utils.js +++ b/app/assets/javascripts/search/store/utils.js @@ -1,3 +1,4 @@ +import { isEqual } from 'lodash'; import AccessorUtilities from '~/lib/utils/accessor'; import { formatNumber } from '~/locale'; import { joinPaths } from '~/lib/utils/url_utility'; @@ -94,6 +95,10 @@ export const isSidebarDirty = (currentQuery, urlQuery) => { const userAddedParam = !urlQuery[param] && currentQuery[param]; const userChangedExistingParam = urlQuery[param] && urlQuery[param] !== currentQuery[param]; + if (Array.isArray(currentQuery[param]) || Array.isArray(urlQuery[param])) { + return !isEqual(currentQuery[param], urlQuery[param]); + } + return userAddedParam || userChangedExistingParam; }); }; diff --git a/app/assets/javascripts/vue_merge_request_widget/components/approvals/approvals.vue b/app/assets/javascripts/vue_merge_request_widget/components/approvals/approvals.vue index 4b65d6fd9ac..17fb2daaf17 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/approvals/approvals.vue +++ b/app/assets/javascripts/vue_merge_request_widget/components/approvals/approvals.vue @@ -5,6 +5,7 @@ import { BV_SHOW_MODAL } from '~/lib/utils/constants'; import { HTTP_STATUS_UNAUTHORIZED } from '~/lib/utils/http_status'; import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; import { s__, __ } from '~/locale'; +import { getIdFromGraphQLId } from '~/graphql_shared/utils'; import eventHub from '../../event_hub'; import approvalsMixin from '../../mixins/approvals'; import MrWidgetContainer from '../mr_widget_container.vue'; @@ -12,7 +13,7 @@ import MrWidgetIcon from '../mr_widget_icon.vue'; import { INVALID_RULES_DOCS_PATH } from '../../constants'; import ApprovalsSummary from './approvals_summary.vue'; import ApprovalsSummaryOptional from './approvals_summary_optional.vue'; -import { FETCH_LOADING, FETCH_ERROR, APPROVE_ERROR, UNAPPROVE_ERROR } from './messages'; +import { FETCH_LOADING, APPROVE_ERROR, UNAPPROVE_ERROR } from './messages'; import { humanizeInvalidApproversRules } from './humanized_text'; export default { @@ -59,10 +60,8 @@ export default { }, data() { return { - fetchingApprovals: true, hasApprovalAuthError: false, isApproving: false, - updatedCount: 0, }; }, computed: { @@ -70,7 +69,7 @@ export default { return this.mr.approvalsWidgetType === 'base'; }, isApproved() { - return Boolean(this.approvals.approved); + return Boolean(this.approvals.approved || this.approvedBy.length); }, isOptional() { return this.isOptionalDefault !== null ? this.isOptionalDefault : !this.approvedBy.length; @@ -78,26 +77,25 @@ export default { hasAction() { return Boolean(this.action); }, - approvals() { - return this.mr.approvals || {}; - }, invalidRules() { - return this.approvals.invalid_approvers_rules || []; + return this.approvals.approvalState?.invalidApproversRules || []; }, hasInvalidRules() { - return this.approvals.merge_request_approvers_available && this.invalidRules.length; + return this.mr.mergeRequestApproversAvailable && this.invalidRules.length; }, invalidRulesText() { return humanizeInvalidApproversRules(this.invalidRules); }, approvedBy() { - return this.approvals.approved_by ? this.approvals.approved_by.map((x) => x.user) : []; + return this.approvals.approvedBy?.nodes || []; }, userHasApproved() { - return Boolean(this.approvals.user_has_approved); + return this.approvedBy.some( + (approver) => getIdFromGraphQLId(approver.id) === gon.current_user_id, + ); }, userCanApprove() { - return Boolean(this.approvals.user_can_approve); + return Boolean(this.approvals.userPermissions.canApprove); }, showApprove() { return !this.userHasApproved && this.userCanApprove && this.mr.isOpen; @@ -135,19 +133,6 @@ export default { : this.$options.i18n.invalidRuleSingular; }, }, - created() { - this.refreshApprovals() - .then(() => { - this.fetchingApprovals = false; - }) - .catch(() => - this.alerts.push( - createAlert({ - message: FETCH_ERROR, - }), - ), - ); - }, methods: { approve() { if (this.requirePasswordToApprove) { @@ -196,16 +181,14 @@ export default { this.isApproving = true; this.clearError(); return serviceFn() - .then((data) => { - this.mr.setApprovals(data); - this.updatedCount += 1; - + .then(() => { if (!window.gon?.features?.realtimeMrStatusChange) { eventHub.$emit('MRWidgetUpdateRequested'); eventHub.$emit('ApprovalUpdated'); } - this.$emit('updated'); + // TODO: Remove this line when we move to Apollo subscriptions + this.$apollo.queries.approvals.refetch(); }) .catch(errFn) .then(() => { @@ -230,7 +213,7 @@ export default { <mr-widget-container> <div class="js-mr-approvals d-flex align-items-start align-items-md-center"> <mr-widget-icon name="approval" /> - <div v-if="fetchingApprovals">{{ $options.FETCH_LOADING }}</div> + <div v-if="$apollo.queries.approvals.loading">{{ $options.FETCH_LOADING }}</div> <template v-else> <div class="gl-display-flex gl-flex-direction-column"> <div class="gl-display-flex gl-flex-direction-row gl-align-items-center"> @@ -252,9 +235,7 @@ export default { /> <approvals-summary v-else - :project-path="mr.targetProjectFullPath" - :iid="`${mr.iid}`" - :updated-count="updatedCount" + :approval-state="approvals" :multiple-approval-rules-available="mr.multipleApprovalRulesAvailable" /> </div> diff --git a/app/assets/javascripts/vue_merge_request_widget/components/approvals/approvals_summary.vue b/app/assets/javascripts/vue_merge_request_widget/components/approvals/approvals_summary.vue index 697d953874c..2af033bb80f 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/approvals/approvals_summary.vue +++ b/app/assets/javascripts/vue_merge_request_widget/components/approvals/approvals_summary.vue @@ -1,5 +1,4 @@ <script> -import { GlSkeletonLoader } from '@gitlab/ui'; import { toNounSeriesText } from '~/lib/utils/grammar'; import { n__, sprintf } from '~/locale'; import { @@ -10,49 +9,21 @@ import { import UserAvatarList from '~/vue_shared/components/user_avatar/user_avatar_list.vue'; import { getIdFromGraphQLId } from '~/graphql_shared/utils'; import { getApprovalRuleNamesLeft } from 'ee_else_ce/vue_merge_request_widget/mappers'; -import approvedByQuery from 'ee_else_ce/vue_merge_request_widget/components/approvals/queries/approved_by.query.graphql'; export default { - apollo: { - approvalState: { - query: approvedByQuery, - variables() { - return { - projectPath: this.projectPath, - iid: this.iid, - }; - }, - update: (data) => data.project.mergeRequest, - }, - }, components: { - GlSkeletonLoader, UserAvatarList, }, props: { - projectPath: { - type: String, - required: true, - }, - iid: { - type: String, - required: true, - }, - updatedCount: { - type: Number, - required: false, - default: 0, - }, multipleApprovalRulesAvailable: { type: Boolean, required: false, default: false, }, - }, - data() { - return { - approvalState: {}, - }; + approvalState: { + type: Object, + required: true, + }, }, computed: { approvers() { @@ -134,37 +105,20 @@ export default { return gon.current_user_id; }, }, - watch: { - updatedCount() { - this.$apollo.queries.approvalState.refetch(); - }, - }, }; </script> <template> <div data-qa-selector="approvals_summary_content"> - <div - v-if="$apollo.queries.approvalState.loading" - class="gl-display-inline-block gl-vertical-align-middle" - style="width: 132px; height: 24px" - > - <gl-skeleton-loader :width="132" :height="24"> - <rect width="100" height="24" x="0" y="0" rx="4" /> - <circle cx="120" cy="12" r="12" /> - </gl-skeleton-loader> - </div> - <template v-else> - <span class="gl-font-weight-bold">{{ approvalLeftMessage }}</span> - <template v-if="hasApprovers"> - <span v-if="approvalLeftMessage">{{ message }}</span> - <span v-else class="gl-font-weight-bold">{{ message }}</span> - <user-avatar-list - class="gl-display-inline-block gl-vertical-align-middle gl-pt-1" - :img-size="24" - :items="approvers" - /> - </template> + <span class="gl-font-weight-bold">{{ approvalLeftMessage }}</span> + <template v-if="hasApprovers"> + <span v-if="approvalLeftMessage">{{ message }}</span> + <span v-else class="gl-font-weight-bold">{{ message }}</span> + <user-avatar-list + class="gl-display-inline-block gl-vertical-align-middle gl-pt-1" + :img-size="24" + :items="approvers" + /> </template> </div> </template> diff --git a/app/assets/javascripts/vue_merge_request_widget/components/approvals/queries/approved_by.query.graphql b/app/assets/javascripts/vue_merge_request_widget/components/approvals/queries/approvals.query.graphql index c8cae6a8885..437ae578cd0 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/approvals/queries/approved_by.query.graphql +++ b/app/assets/javascripts/vue_merge_request_widget/components/approvals/queries/approvals.query.graphql @@ -1,3 +1,5 @@ +#import "~/graphql_shared/fragments/user.fragment.graphql" + query approvedBy($projectPath: ID!, $iid: String!) { project(fullPath: $projectPath) { id @@ -5,12 +7,12 @@ query approvedBy($projectPath: ID!, $iid: String!) { id approvedBy { nodes { - id - name - avatarUrl - webUrl + ...User } } + userPermissions { + canApprove + } } } } diff --git a/app/assets/javascripts/vue_merge_request_widget/index.js b/app/assets/javascripts/vue_merge_request_widget/index.js index 8d596465970..c66641310ee 100644 --- a/app/assets/javascripts/vue_merge_request_widget/index.js +++ b/app/assets/javascripts/vue_merge_request_widget/index.js @@ -13,7 +13,18 @@ Vue.use(Translate); Vue.use(VueApollo); const apolloProvider = new VueApollo({ - defaultClient: createDefaultClient(), + defaultClient: createDefaultClient( + {}, + { + cacheConfig: { + typePolicies: { + MergeRequestApprovalState: { + merge: true, + }, + }, + }, + }, + ), }); export default () => { diff --git a/app/assets/javascripts/vue_merge_request_widget/mixins/approvals.js b/app/assets/javascripts/vue_merge_request_widget/mixins/approvals.js index 7d0871f696b..a43c784db28 100644 --- a/app/assets/javascripts/vue_merge_request_widget/mixins/approvals.js +++ b/app/assets/javascripts/vue_merge_request_widget/mixins/approvals.js @@ -1,7 +1,34 @@ +import { createAlert } from '~/flash'; +import approvedByQuery from 'ee_else_ce/vue_merge_request_widget/components/approvals/queries/approvals.query.graphql'; +import { FETCH_ERROR } from '../components/approvals/messages'; + export default { + apollo: { + approvals: { + query: approvedByQuery, + variables() { + return { + projectPath: this.mr.targetProjectFullPath, + iid: `${this.mr.iid}`, + }; + }, + update: (data) => data.project.mergeRequest, + result({ data }) { + const { mergeRequest } = data.project; + + this.mr.setApprovals(mergeRequest); + }, + error() { + createAlert({ + message: FETCH_ERROR, + }); + }, + }, + }, data() { return { alerts: [], + approvals: {}, }; }, methods: { diff --git a/app/assets/javascripts/vue_merge_request_widget/stores/mr_widget_store.js b/app/assets/javascripts/vue_merge_request_widget/stores/mr_widget_store.js index f6a7ef58c10..827080d9866 100644 --- a/app/assets/javascripts/vue_merge_request_widget/stores/mr_widget_store.js +++ b/app/assets/javascripts/vue_merge_request_widget/stores/mr_widget_store.js @@ -356,12 +356,11 @@ export default class MergeRequestStore { initApprovals() { this.isApproved = this.isApproved || false; - this.approvals = this.approvals || null; } setApprovals(data) { - this.approvals = data; this.isApproved = data.approved || false; + this.approvals = true; this.setState(); } |