From 3cccd102ba543e02725d247893729e5c73b38295 Mon Sep 17 00:00:00 2001 From: GitLab Bot Date: Wed, 20 Apr 2022 10:00:54 +0000 Subject: Add latest changes from gitlab-org/gitlab@14-10-stable-ee --- .../releases/components/app_edit_new.vue | 2 +- .../javascripts/releases/components/app_index.vue | 287 ++++++++++++++++----- .../components/app_index_apollo_client.vue | 275 -------------------- .../releases/components/release_block_footer.vue | 2 + .../releases/components/releases_pagination.vue | 20 +- .../releases_pagination_apollo_client.vue | 37 --- .../releases/components/releases_sort.vue | 71 +++-- .../components/releases_sort_apollo_client.vue | 91 ------- .../graphql/queries/all_releases.query.graphql | 92 ++++++- app/assets/javascripts/releases/mount_index.js | 52 ++-- .../releases/stores/modules/index/actions.js | 65 ----- .../releases/stores/modules/index/index.js | 10 - .../stores/modules/index/mutation_types.js | 4 - .../releases/stores/modules/index/mutations.js | 44 ---- .../releases/stores/modules/index/state.js | 24 -- 15 files changed, 382 insertions(+), 694 deletions(-) delete mode 100644 app/assets/javascripts/releases/components/app_index_apollo_client.vue delete mode 100644 app/assets/javascripts/releases/components/releases_pagination_apollo_client.vue delete mode 100644 app/assets/javascripts/releases/components/releases_sort_apollo_client.vue delete mode 100644 app/assets/javascripts/releases/stores/modules/index/actions.js delete mode 100644 app/assets/javascripts/releases/stores/modules/index/index.js delete mode 100644 app/assets/javascripts/releases/stores/modules/index/mutation_types.js delete mode 100644 app/assets/javascripts/releases/stores/modules/index/mutations.js delete mode 100644 app/assets/javascripts/releases/stores/modules/index/state.js (limited to 'app/assets/javascripts/releases') diff --git a/app/assets/javascripts/releases/components/app_edit_new.vue b/app/assets/javascripts/releases/components/app_edit_new.vue index 39140216bc5..8365e6a5ab0 100644 --- a/app/assets/javascripts/releases/components/app_edit_new.vue +++ b/app/assets/javascripts/releases/components/app_edit_new.vue @@ -185,7 +185,7 @@ export default { -import { GlEmptyState, GlLink, GlButton } from '@gitlab/ui'; -import { mapState, mapActions } from 'vuex'; -import { getParameterByName } from '~/lib/utils/url_utility'; +import { GlButton } from '@gitlab/ui'; +import createFlash from '~/flash'; +import { historyPushState } from '~/lib/utils/common_utils'; +import { scrollUp } from '~/lib/utils/scroll_utils'; +import { setUrlParams, getParameterByName } from '~/lib/utils/url_utility'; import { __ } from '~/locale'; +import { PAGE_SIZE, DEFAULT_SORT } from '~/releases/constants'; +import { convertAllReleasesGraphQLResponse } from '~/releases/util'; +import allReleasesQuery from '../graphql/queries/all_releases.query.graphql'; import ReleaseBlock from './release_block.vue'; import ReleaseSkeletonLoader from './release_skeleton_loader.vue'; +import ReleasesEmptyState from './releases_empty_state.vue'; import ReleasesPagination from './releases_pagination.vue'; import ReleasesSort from './releases_sort.vue'; export default { - name: 'ReleasesApp', + name: 'ReleasesIndexApp', components: { - GlEmptyState, - GlLink, GlButton, ReleaseBlock, - ReleasesPagination, ReleaseSkeletonLoader, + ReleasesEmptyState, + ReleasesPagination, ReleasesSort, }, + inject: { + projectPath: { + default: '', + }, + newReleasePath: { + default: '', + }, + }, + apollo: { + /** + * The same query as `fullGraphqlResponse`, except that it limits its + * results to a single item. This causes this request to complete much more + * quickly than `fullGraphqlResponse`, which allows the page to show + * meaningful content to the user much earlier. + */ + singleGraphqlResponse: { + query: allReleasesQuery, + // This trick only works when paginating _forward_. + // When paginating backwards, limiting the query to a single item loads + // the _last_ item in the page, which is not useful for our purposes. + skip() { + return !this.includeSingleQuery; + }, + variables() { + return { + ...this.queryVariables, + first: 1, + }; + }, + update(data) { + return { data }; + }, + error() { + this.singleRequestError = true; + }, + }, + fullGraphqlResponse: { + query: allReleasesQuery, + variables() { + return this.queryVariables; + }, + update(data) { + return { data }; + }, + error(error) { + this.fullRequestError = true; + + createFlash({ + message: this.$options.i18n.errorMessage, + captureError: true, + error, + }); + }, + }, + }, + data() { + return { + singleRequestError: false, + fullRequestError: false, + cursors: { + before: getParameterByName('before'), + after: getParameterByName('after'), + }, + sort: DEFAULT_SORT, + }; + }, computed: { - ...mapState('index', [ - 'documentationPath', - 'illustrationPath', - 'newReleasePath', - 'isLoading', - 'releases', - 'hasError', - ]), - shouldRenderEmptyState() { - return !this.releases.length && !this.hasError && !this.isLoading; + queryVariables() { + let paginationParams = { first: PAGE_SIZE }; + if (this.cursors.after) { + paginationParams = { + after: this.cursors.after, + first: PAGE_SIZE, + }; + } else if (this.cursors.before) { + paginationParams = { + before: this.cursors.before, + last: PAGE_SIZE, + }; + } + + return { + fullPath: this.projectPath, + ...paginationParams, + sort: this.sort, + }; + }, + /** + * @returns {Boolean} Whether or not to request/include + * the results of the single-item query + */ + includeSingleQuery() { + return Boolean(!this.cursors.before || this.cursors.after); + }, + isSingleRequestLoading() { + return this.$apollo.queries.singleGraphqlResponse.loading; }, - shouldRenderSuccessState() { - return this.releases.length && !this.isLoading && !this.hasError; + isFullRequestLoading() { + return this.$apollo.queries.fullGraphqlResponse.loading; + }, + /** + * @returns {Boolean} `true` if the `singleGraphqlResponse` + * query has finished loading without errors + */ + isSingleRequestLoaded() { + return Boolean(!this.isSingleRequestLoading && this.singleGraphqlResponse?.data.project); + }, + /** + * @returns {Boolean} `true` if the `fullGraphqlResponse` + * query has finished loading without errors + */ + isFullRequestLoaded() { + return Boolean(!this.isFullRequestLoading && this.fullGraphqlResponse?.data.project); + }, + releases() { + if (this.isFullRequestLoaded) { + return convertAllReleasesGraphQLResponse(this.fullGraphqlResponse).data; + } + + if (this.isSingleRequestLoaded && this.includeSingleQuery) { + return convertAllReleasesGraphQLResponse(this.singleGraphqlResponse).data; + } + + return []; }, - emptyStateText() { - return __( - "Releases are based on Git tags and mark specific points in a project's development history. They can contain information about the type of changes and can also deliver binaries, like compiled versions of your software.", + pageInfo() { + if (!this.isFullRequestLoaded) { + return { + hasPreviousPage: false, + hasNextPage: false, + }; + } + + return this.fullGraphqlResponse.data.project.releases.pageInfo; + }, + shouldRenderEmptyState() { + return this.isFullRequestLoaded && this.releases.length === 0; + }, + shouldRenderLoadingIndicator() { + return ( + (this.isSingleRequestLoading && !this.singleRequestError && !this.isFullRequestLoaded) || + (this.isFullRequestLoading && !this.fullRequestError) ); }, + shouldRenderPagination() { + return this.isFullRequestLoaded && !this.shouldRenderEmptyState; + }, }, created() { - this.fetchReleases(); + this.updateQueryParamsFromUrl(); - window.addEventListener('popstate', this.fetchReleases); + window.addEventListener('popstate', this.updateQueryParamsFromUrl); + }, + destroyed() { + window.removeEventListener('popstate', this.updateQueryParamsFromUrl); }, methods: { - ...mapActions('index', { - fetchReleasesStoreAction: 'fetchReleases', - }), - fetchReleases() { - this.fetchReleasesStoreAction({ - before: getParameterByName('before'), - after: getParameterByName('after'), - }); + getReleaseKey(release, index) { + return [release.tagName, release.name, index].join('|'); + }, + updateQueryParamsFromUrl() { + this.cursors.before = getParameterByName('before'); + this.cursors.after = getParameterByName('after'); + }, + onPaginationButtonPress() { + this.updateQueryParamsFromUrl(); + + // In some cases, Apollo Client is able to pull its results from the cache instead of making + // a new network request. In these cases, the page's content gets swapped out immediately without + // changing the page's scroll, leaving the user looking at the bottom of the new page. + // To make the experience consistent, regardless of how the data is sourced, we manually + // scroll to the top of the page every time a pagination button is pressed. + scrollUp(); + }, + onSortChanged(newSort) { + if (this.sort === newSort) { + return; + } + + // Remove the "before" and "after" query parameters from the URL, + // effectively placing the user back on page 1 of the results. + // This prevents the frontend from requesting the results sorted + // by one field (e.g. `released_at`) while using a pagination cursor + // intended for a different field (e.g.) `created_at`). + // For more details, see the MR that introduced this change: + // https://gitlab.com/gitlab-org/gitlab/-/merge_requests/63434 + historyPushState( + setUrlParams({ + before: null, + after: null, + }), + ); + + this.updateQueryParamsFromUrl(); + + this.sort = newSort; }, }, + i18n: { + newRelease: __('New release'), + errorMessage: __('An error occurred while fetching the releases. Please try again.'), + }, }; diff --git a/app/assets/javascripts/releases/components/release_block_footer.vue b/app/assets/javascripts/releases/components/release_block_footer.vue index cb795b3cba7..91d6d0911a4 100644 --- a/app/assets/javascripts/releases/components/release_block_footer.vue +++ b/app/assets/javascripts/releases/components/release_block_footer.vue @@ -104,9 +104,11 @@ export default {
{{ __('by') }}  diff --git a/app/assets/javascripts/releases/components/releases_pagination.vue b/app/assets/javascripts/releases/components/releases_pagination.vue index fddf85ead1e..52ad991d61a 100644 --- a/app/assets/javascripts/releases/components/releases_pagination.vue +++ b/app/assets/javascripts/releases/components/releases_pagination.vue @@ -1,26 +1,24 @@ - diff --git a/app/assets/javascripts/releases/components/releases_sort.vue b/app/assets/javascripts/releases/components/releases_sort.vue index d4210dad19c..0f14b579da0 100644 --- a/app/assets/javascripts/releases/components/releases_sort.vue +++ b/app/assets/javascripts/releases/components/releases_sort.vue @@ -1,7 +1,17 @@ - - diff --git a/app/assets/javascripts/releases/graphql/queries/all_releases.query.graphql b/app/assets/javascripts/releases/graphql/queries/all_releases.query.graphql index 7f67f7d11a3..bda7ac52a47 100644 --- a/app/assets/javascripts/releases/graphql/queries/all_releases.query.graphql +++ b/app/assets/javascripts/releases/graphql/queries/all_releases.query.graphql @@ -1,12 +1,4 @@ -#import "../fragments/release.fragment.graphql" - -# This query is identical to -# `app/graphql/queries/releases/all_releases.query.graphql`. -# These two queries should be kept in sync. -# When the `releases_index_apollo_client` feature flag is -# removed, this query should be removed entirely. - -query allReleasesDeprecated( +query allReleases( $fullPath: ID! $first: Int $last: Int @@ -20,7 +12,87 @@ query allReleasesDeprecated( releases(first: $first, last: $last, before: $before, after: $after, sort: $sort) { __typename nodes { - ...Release + __typename + name + tagName + tagPath + descriptionHtml + releasedAt + createdAt + upcomingRelease + assets { + __typename + count + sources { + __typename + nodes { + __typename + format + url + } + } + links { + __typename + nodes { + __typename + id + name + url + directAssetUrl + linkType + external + } + } + } + evidences { + __typename + nodes { + __typename + id + filepath + collectedAt + sha + } + } + links { + __typename + editUrl + selfUrl + openedIssuesUrl + closedIssuesUrl + openedMergeRequestsUrl + mergedMergeRequestsUrl + closedMergeRequestsUrl + } + commit { + __typename + id + sha + webUrl + title + } + author { + __typename + id + webUrl + avatarUrl + username + } + milestones { + __typename + nodes { + __typename + id + title + description + webPath + stats { + __typename + totalIssuesCount + closedIssuesCount + } + } + } } pageInfo { __typename diff --git a/app/assets/javascripts/releases/mount_index.js b/app/assets/javascripts/releases/mount_index.js index 86fa72d1496..afb8ab461cd 100644 --- a/app/assets/javascripts/releases/mount_index.js +++ b/app/assets/javascripts/releases/mount_index.js @@ -1,50 +1,32 @@ import Vue from 'vue'; import VueApollo from 'vue-apollo'; -import Vuex from 'vuex'; import createDefaultClient from '~/lib/graphql'; import ReleaseIndexApp from './components/app_index.vue'; -import ReleaseIndexApollopClientApp from './components/app_index_apollo_client.vue'; -import createStore from './stores'; -import createIndexModule from './stores/modules/index'; export default () => { const el = document.getElementById('js-releases-page'); - if (window.gon?.features?.releasesIndexApolloClient) { - Vue.use(VueApollo); + Vue.use(VueApollo); - const apolloProvider = new VueApollo({ - defaultClient: createDefaultClient( - {}, - { - // This page attempts to decrease the perceived loading time - // by sending two requests: one request for the first item only (which - // completes relatively quickly), and one for all the items (which is slower). - // By default, Apollo Client batches these requests together, which defeats - // the purpose of making separate requests. So we explicitly - // disable batching on this page. - batchMax: 1, - }, - ), - }); - - return new Vue({ - el, - apolloProvider, - provide: { ...el.dataset }, - render: (h) => h(ReleaseIndexApollopClientApp), - }); - } - - Vue.use(Vuex); + const apolloProvider = new VueApollo({ + defaultClient: createDefaultClient( + {}, + { + // This page attempts to decrease the perceived loading time + // by sending two requests: one request for the first item only (which + // completes relatively quickly), and one for all the items (which is slower). + // By default, Apollo Client batches these requests together, which defeats + // the purpose of making separate requests. So we explicitly + // disable batching on this page. + batchMax: 1, + }, + ), + }); return new Vue({ el, - store: createStore({ - modules: { - index: createIndexModule(el.dataset), - }, - }), + apolloProvider, + provide: { ...el.dataset }, render: (h) => h(ReleaseIndexApp), }); }; diff --git a/app/assets/javascripts/releases/stores/modules/index/actions.js b/app/assets/javascripts/releases/stores/modules/index/actions.js deleted file mode 100644 index d3bb11cab30..00000000000 --- a/app/assets/javascripts/releases/stores/modules/index/actions.js +++ /dev/null @@ -1,65 +0,0 @@ -import createFlash from '~/flash'; -import { __ } from '~/locale'; -import { PAGE_SIZE } from '~/releases/constants'; -import allReleasesQuery from '~/releases/graphql/queries/all_releases.query.graphql'; -import { gqClient, convertAllReleasesGraphQLResponse } from '~/releases/util'; -import * as types from './mutation_types'; - -/** - * Gets a paginated list of releases from the GraphQL endpoint - * - * @param {Object} vuexParams - * @param {Object} actionParams - * @param {String} [actionParams.before] A GraphQL cursor. If provided, - * the items returned will proceed the provided cursor. - * @param {String} [actionParams.after] A GraphQL cursor. If provided, - * the items returned will follow the provided cursor. - */ -export const fetchReleases = ({ dispatch, commit, state }, { before, after }) => { - commit(types.REQUEST_RELEASES); - - const { sort, orderBy } = state.sorting; - const orderByParam = orderBy === 'created_at' ? 'created' : orderBy; - const sortParams = `${orderByParam}_${sort}`.toUpperCase(); - - let paginationParams; - if (!before && !after) { - paginationParams = { first: PAGE_SIZE }; - } else if (before && !after) { - paginationParams = { last: PAGE_SIZE, before }; - } else if (!before && after) { - paginationParams = { first: PAGE_SIZE, after }; - } else { - throw new Error( - 'Both a `before` and an `after` parameter were provided to fetchReleases. These parameters cannot be used together.', - ); - } - - gqClient - .query({ - query: allReleasesQuery, - variables: { - fullPath: state.projectPath, - sort: sortParams, - ...paginationParams, - }, - }) - .then((response) => { - const { data, paginationInfo: pageInfo } = convertAllReleasesGraphQLResponse(response); - - commit(types.RECEIVE_RELEASES_SUCCESS, { - data, - pageInfo, - }); - }) - .catch(() => dispatch('receiveReleasesError')); -}; - -export const receiveReleasesError = ({ commit }) => { - commit(types.RECEIVE_RELEASES_ERROR); - createFlash({ - message: __('An error occurred while fetching the releases. Please try again.'), - }); -}; - -export const setSorting = ({ commit }, data) => commit(types.SET_SORTING, data); diff --git a/app/assets/javascripts/releases/stores/modules/index/index.js b/app/assets/javascripts/releases/stores/modules/index/index.js deleted file mode 100644 index d5ca191153a..00000000000 --- a/app/assets/javascripts/releases/stores/modules/index/index.js +++ /dev/null @@ -1,10 +0,0 @@ -import * as actions from './actions'; -import mutations from './mutations'; -import createState from './state'; - -export default (initialState) => ({ - namespaced: true, - actions, - mutations, - state: createState(initialState), -}); diff --git a/app/assets/javascripts/releases/stores/modules/index/mutation_types.js b/app/assets/javascripts/releases/stores/modules/index/mutation_types.js deleted file mode 100644 index 669168efb88..00000000000 --- a/app/assets/javascripts/releases/stores/modules/index/mutation_types.js +++ /dev/null @@ -1,4 +0,0 @@ -export const REQUEST_RELEASES = 'REQUEST_RELEASES'; -export const RECEIVE_RELEASES_SUCCESS = 'RECEIVE_RELEASES_SUCCESS'; -export const RECEIVE_RELEASES_ERROR = 'RECEIVE_RELEASES_ERROR'; -export const SET_SORTING = 'SET_SORTING'; diff --git a/app/assets/javascripts/releases/stores/modules/index/mutations.js b/app/assets/javascripts/releases/stores/modules/index/mutations.js deleted file mode 100644 index 55a8a488be8..00000000000 --- a/app/assets/javascripts/releases/stores/modules/index/mutations.js +++ /dev/null @@ -1,44 +0,0 @@ -import * as types from './mutation_types'; - -export default { - /** - * Sets isLoading to true while the request is being made. - * @param {Object} state - */ - [types.REQUEST_RELEASES](state) { - state.isLoading = true; - }, - - /** - * Sets isLoading to false. - * Sets hasError to false. - * Sets the received data - * Sets the received pagination information - * @param {Object} state - * @param {Object} resp - */ - [types.RECEIVE_RELEASES_SUCCESS](state, { data, pageInfo }) { - state.hasError = false; - state.isLoading = false; - state.releases = data; - state.pageInfo = pageInfo; - }, - - /** - * Sets isLoading to false. - * Sets hasError to true. - * Resets the data - * @param {Object} state - * @param {Object} data - */ - [types.RECEIVE_RELEASES_ERROR](state) { - state.isLoading = false; - state.releases = []; - state.hasError = true; - state.pageInfo = {}; - }, - - [types.SET_SORTING](state, sorting) { - state.sorting = { ...state.sorting, ...sorting }; - }, -}; diff --git a/app/assets/javascripts/releases/stores/modules/index/state.js b/app/assets/javascripts/releases/stores/modules/index/state.js deleted file mode 100644 index 5e1aaab7b58..00000000000 --- a/app/assets/javascripts/releases/stores/modules/index/state.js +++ /dev/null @@ -1,24 +0,0 @@ -import { DESCENDING_ORDER, RELEASED_AT } from '../../../constants'; - -export default ({ - projectId, - projectPath, - documentationPath, - illustrationPath, - newReleasePath = '', -}) => ({ - projectId, - projectPath, - documentationPath, - illustrationPath, - newReleasePath, - - isLoading: false, - hasError: false, - releases: [], - pageInfo: {}, - sorting: { - sort: DESCENDING_ORDER, - orderBy: RELEASED_AT, - }, -}); -- cgit v1.2.3