Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2022-04-20 13:00:54 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2022-04-20 13:00:54 +0300
commit3cccd102ba543e02725d247893729e5c73b38295 (patch)
treef36a04ec38517f5deaaacb5acc7d949688d1e187 /app/assets/javascripts/releases
parent205943281328046ef7b4528031b90fbda70c75ac (diff)
Add latest changes from gitlab-org/gitlab@14-10-stable-eev14.10.0-rc42
Diffstat (limited to 'app/assets/javascripts/releases')
-rw-r--r--app/assets/javascripts/releases/components/app_edit_new.vue2
-rw-r--r--app/assets/javascripts/releases/components/app_index.vue287
-rw-r--r--app/assets/javascripts/releases/components/app_index_apollo_client.vue275
-rw-r--r--app/assets/javascripts/releases/components/release_block_footer.vue2
-rw-r--r--app/assets/javascripts/releases/components/releases_pagination.vue20
-rw-r--r--app/assets/javascripts/releases/components/releases_pagination_apollo_client.vue37
-rw-r--r--app/assets/javascripts/releases/components/releases_sort.vue71
-rw-r--r--app/assets/javascripts/releases/components/releases_sort_apollo_client.vue91
-rw-r--r--app/assets/javascripts/releases/graphql/queries/all_releases.query.graphql92
-rw-r--r--app/assets/javascripts/releases/mount_index.js52
-rw-r--r--app/assets/javascripts/releases/stores/modules/index/actions.js65
-rw-r--r--app/assets/javascripts/releases/stores/modules/index/index.js10
-rw-r--r--app/assets/javascripts/releases/stores/modules/index/mutation_types.js4
-rw-r--r--app/assets/javascripts/releases/stores/modules/index/mutations.js44
-rw-r--r--app/assets/javascripts/releases/stores/modules/index/state.js24
15 files changed, 382 insertions, 694 deletions
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 {
<gl-button
class="mr-auto js-no-auto-disable"
category="primary"
- variant="success"
+ variant="confirm"
type="submit"
:disabled="isFormSubmissionDisabled"
data-testid="submit-button"
diff --git a/app/assets/javascripts/releases/components/app_index.vue b/app/assets/javascripts/releases/components/app_index.vue
index e53bfea7389..59fa2fca736 100644
--- a/app/assets/javascripts/releases/components/app_index.vue
+++ b/app/assets/javascripts/releases/components/app_index.vue
@@ -1,67 +1,237 @@
<script>
-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.'),
+ },
};
</script>
<template>
<div class="flex flex-column mt-2">
<div class="gl-align-self-end gl-mb-3">
- <releases-sort class="gl-mr-2" @sort:changed="fetchReleases" />
+ <releases-sort :value="sort" class="gl-mr-2" @input="onSortChanged" />
<gl-button
v-if="newReleasePath"
@@ -69,44 +239,27 @@ export default {
:aria-describedby="shouldRenderEmptyState && 'releases-description'"
category="primary"
variant="confirm"
- data-testid="new-release-button"
+ >{{ $options.i18n.newRelease }}</gl-button
>
- {{ __('New release') }}
- </gl-button>
</div>
- <release-skeleton-loader v-if="isLoading" />
-
- <gl-empty-state
- v-else-if="shouldRenderEmptyState"
- data-testid="empty-state"
- :title="__('Getting started with releases')"
- :svg-path="illustrationPath"
- >
- <template #description>
- <span id="releases-description">
- {{ emptyStateText }}
- <gl-link
- :href="documentationPath"
- :aria-label="__('Releases documentation')"
- target="_blank"
- >
- {{ __('More information') }}
- </gl-link>
- </span>
- </template>
- </gl-empty-state>
-
- <div v-else-if="shouldRenderSuccessState" data-testid="success-state">
- <release-block
- v-for="(release, index) in releases"
- :key="index"
- :release="release"
- :class="{ 'linked-card': releases.length > 1 && index !== releases.length - 1 }"
- />
- </div>
+ <releases-empty-state v-if="shouldRenderEmptyState" />
+
+ <release-block
+ v-for="(release, index) in releases"
+ :key="getReleaseKey(release, index)"
+ :release="release"
+ :class="{ 'linked-card': releases.length > 1 && index !== releases.length - 1 }"
+ />
+
+ <release-skeleton-loader v-if="shouldRenderLoadingIndicator" />
- <releases-pagination v-if="!isLoading" />
+ <releases-pagination
+ v-if="shouldRenderPagination"
+ :page-info="pageInfo"
+ @prev="onPaginationButtonPress"
+ @next="onPaginationButtonPress"
+ />
</div>
</template>
<style>
diff --git a/app/assets/javascripts/releases/components/app_index_apollo_client.vue b/app/assets/javascripts/releases/components/app_index_apollo_client.vue
deleted file mode 100644
index f49c44a399f..00000000000
--- a/app/assets/javascripts/releases/components/app_index_apollo_client.vue
+++ /dev/null
@@ -1,275 +0,0 @@
-<script>
-import { GlButton } from '@gitlab/ui';
-import allReleasesQuery from 'shared_queries/releases/all_releases.query.graphql';
-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 ReleaseBlock from './release_block.vue';
-import ReleaseSkeletonLoader from './release_skeleton_loader.vue';
-import ReleasesEmptyState from './releases_empty_state.vue';
-import ReleasesPaginationApolloClient from './releases_pagination_apollo_client.vue';
-import ReleasesSortApolloClient from './releases_sort_apollo_client.vue';
-
-export default {
- name: 'ReleasesIndexApolloClientApp',
- components: {
- GlButton,
- ReleaseBlock,
- ReleaseSkeletonLoader,
- ReleasesEmptyState,
- ReleasesPaginationApolloClient,
- ReleasesSortApolloClient,
- },
- 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: {
- 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;
- },
- 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 [];
- },
- 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.updateQueryParamsFromUrl();
-
- window.addEventListener('popstate', this.updateQueryParamsFromUrl);
- },
- destroyed() {
- window.removeEventListener('popstate', this.updateQueryParamsFromUrl);
- },
- methods: {
- 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.'),
- },
-};
-</script>
-<template>
- <div class="flex flex-column mt-2">
- <div class="gl-align-self-end gl-mb-3">
- <releases-sort-apollo-client :value="sort" class="gl-mr-2" @input="onSortChanged" />
-
- <gl-button
- v-if="newReleasePath"
- :href="newReleasePath"
- :aria-describedby="shouldRenderEmptyState && 'releases-description'"
- category="primary"
- variant="success"
- >{{ $options.i18n.newRelease }}</gl-button
- >
- </div>
-
- <releases-empty-state v-if="shouldRenderEmptyState" />
-
- <release-block
- v-for="(release, index) in releases"
- :key="getReleaseKey(release, index)"
- :release="release"
- :class="{ 'linked-card': releases.length > 1 && index !== releases.length - 1 }"
- />
-
- <release-skeleton-loader v-if="shouldRenderLoadingIndicator" />
-
- <releases-pagination-apollo-client
- v-if="shouldRenderPagination"
- :page-info="pageInfo"
- @prev="onPaginationButtonPress"
- @next="onPaginationButtonPress"
- />
- </div>
-</template>
-<style>
-.linked-card::after {
- width: 1px;
- content: ' ';
- border: 1px solid #e5e5e5;
- height: 17px;
- top: 100%;
- position: absolute;
- left: 32px;
-}
-</style>
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 {
<div v-if="author" class="d-flex">
<span class="text-secondary">{{ __('by') }}&nbsp;</span>
<user-avatar-link
+ class="gl-my-n1"
:link-href="author.webUrl"
:img-src="author.avatarUrl"
:img-alt="userImageAltDescription"
+ :img-size="24"
:tooltip-text="author.username"
tooltip-placement="bottom"
/>
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 @@
<script>
import { GlKeysetPagination } from '@gitlab/ui';
-import { mapActions, mapState } from 'vuex';
+import { isBoolean } from 'lodash';
import { historyPushState, buildUrlWithCurrentLocation } from '~/lib/utils/common_utils';
export default {
- name: 'ReleasesPaginationGraphql',
+ name: 'ReleasesPagination',
components: { GlKeysetPagination },
- computed: {
- ...mapState('index', ['pageInfo']),
- showPagination() {
- return this.pageInfo.hasPreviousPage || this.pageInfo.hasNextPage;
+ props: {
+ pageInfo: {
+ type: Object,
+ required: true,
+ validator: (info) => isBoolean(info.hasPreviousPage) && isBoolean(info.hasNextPage),
},
},
methods: {
- ...mapActions('index', ['fetchReleases']),
onPrev(before) {
historyPushState(buildUrlWithCurrentLocation(`?before=${before}`));
- this.fetchReleases({ before });
},
onNext(after) {
historyPushState(buildUrlWithCurrentLocation(`?after=${after}`));
- this.fetchReleases({ after });
},
},
};
@@ -28,8 +26,10 @@ export default {
<template>
<div class="gl-display-flex gl-justify-content-center">
<gl-keyset-pagination
- v-if="showPagination"
v-bind="pageInfo"
+ :prev-text="__('Prev')"
+ :next-text="__('Next')"
+ v-on="$listeners"
@prev="onPrev($event)"
@next="onNext($event)"
/>
diff --git a/app/assets/javascripts/releases/components/releases_pagination_apollo_client.vue b/app/assets/javascripts/releases/components/releases_pagination_apollo_client.vue
deleted file mode 100644
index 73339677a4b..00000000000
--- a/app/assets/javascripts/releases/components/releases_pagination_apollo_client.vue
+++ /dev/null
@@ -1,37 +0,0 @@
-<script>
-import { GlKeysetPagination } from '@gitlab/ui';
-import { isBoolean } from 'lodash';
-import { historyPushState, buildUrlWithCurrentLocation } from '~/lib/utils/common_utils';
-
-export default {
- name: 'ReleasesPaginationApolloClient',
- components: { GlKeysetPagination },
- props: {
- pageInfo: {
- type: Object,
- required: true,
- validator: (info) => isBoolean(info.hasPreviousPage) && isBoolean(info.hasNextPage),
- },
- },
- methods: {
- onPrev(before) {
- historyPushState(buildUrlWithCurrentLocation(`?before=${before}`));
- },
- onNext(after) {
- historyPushState(buildUrlWithCurrentLocation(`?after=${after}`));
- },
- },
-};
-</script>
-<template>
- <div class="gl-display-flex gl-justify-content-center">
- <gl-keyset-pagination
- v-bind="pageInfo"
- :prev-text="__('Prev')"
- :next-text="__('Next')"
- v-on="$listeners"
- @prev="onPrev($event)"
- @next="onNext($event)"
- />
- </div>
-</template>
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 @@
<script>
import { GlSorting, GlSortingItem } from '@gitlab/ui';
-import { mapState, mapActions } from 'vuex';
-import { ASCENDING_ORDER, DESCENDING_ORDER, SORT_OPTIONS } from '../constants';
+import {
+ ASCENDING_ORDER,
+ DESCENDING_ORDER,
+ SORT_OPTIONS,
+ RELEASED_AT,
+ CREATED_AT,
+ RELEASED_AT_ASC,
+ RELEASED_AT_DESC,
+ CREATED_ASC,
+ ALL_SORTS,
+ SORT_MAP,
+} from '../constants';
export default {
name: 'ReleasesSort',
@@ -9,35 +19,54 @@ export default {
GlSorting,
GlSortingItem,
},
+ props: {
+ value: {
+ type: String,
+ required: true,
+ validator: (sort) => ALL_SORTS.includes(sort),
+ },
+ },
computed: {
- ...mapState('index', {
- orderBy: (state) => state.sorting.orderBy,
- sort: (state) => state.sorting.sort,
- }),
+ orderBy() {
+ if (this.value === RELEASED_AT_ASC || this.value === RELEASED_AT_DESC) {
+ return RELEASED_AT;
+ }
+
+ return CREATED_AT;
+ },
+ direction() {
+ if (this.value === RELEASED_AT_ASC || this.value === CREATED_ASC) {
+ return ASCENDING_ORDER;
+ }
+
+ return DESCENDING_ORDER;
+ },
sortOptions() {
return SORT_OPTIONS;
},
sortText() {
- const option = this.sortOptions.find((s) => s.orderBy === this.orderBy);
- return option.label;
+ return this.sortOptions.find((s) => s.orderBy === this.orderBy).label;
},
- isSortAscending() {
- return this.sort === ASCENDING_ORDER;
+ isDirectionAscending() {
+ return this.direction === ASCENDING_ORDER;
},
},
methods: {
- ...mapActions('index', ['setSorting']),
onDirectionChange() {
- const sort = this.isSortAscending ? DESCENDING_ORDER : ASCENDING_ORDER;
- this.setSorting({ sort });
- this.$emit('sort:changed');
+ const direction = this.isDirectionAscending ? DESCENDING_ORDER : ASCENDING_ORDER;
+ this.emitInputEventIfChanged(this.orderBy, direction);
},
onSortItemClick(item) {
- this.setSorting({ orderBy: item });
- this.$emit('sort:changed');
+ this.emitInputEventIfChanged(item.orderBy, this.direction);
},
isActiveSortItem(item) {
- return this.orderBy === item;
+ return this.orderBy === item.orderBy;
+ },
+ emitInputEventIfChanged(orderBy, direction) {
+ const newSort = SORT_MAP[orderBy][direction];
+ if (newSort !== this.value) {
+ this.$emit('input', SORT_MAP[orderBy][direction]);
+ }
},
},
};
@@ -46,15 +75,15 @@ export default {
<template>
<gl-sorting
:text="sortText"
- :is-ascending="isSortAscending"
+ :is-ascending="isDirectionAscending"
data-testid="releases-sort"
@sortDirectionChange="onDirectionChange"
>
<gl-sorting-item
- v-for="item in sortOptions"
+ v-for="item of sortOptions"
:key="item.orderBy"
- :active="isActiveSortItem(item.orderBy)"
- @click="onSortItemClick(item.orderBy)"
+ :active="isActiveSortItem(item)"
+ @click="onSortItemClick(item)"
>
{{ item.label }}
</gl-sorting-item>
diff --git a/app/assets/javascripts/releases/components/releases_sort_apollo_client.vue b/app/assets/javascripts/releases/components/releases_sort_apollo_client.vue
deleted file mode 100644
index 7257b34bbf6..00000000000
--- a/app/assets/javascripts/releases/components/releases_sort_apollo_client.vue
+++ /dev/null
@@ -1,91 +0,0 @@
-<script>
-import { GlSorting, GlSortingItem } from '@gitlab/ui';
-import {
- ASCENDING_ORDER,
- DESCENDING_ORDER,
- SORT_OPTIONS,
- RELEASED_AT,
- CREATED_AT,
- RELEASED_AT_ASC,
- RELEASED_AT_DESC,
- CREATED_ASC,
- ALL_SORTS,
- SORT_MAP,
-} from '../constants';
-
-export default {
- name: 'ReleasesSortApolloclient',
- components: {
- GlSorting,
- GlSortingItem,
- },
- props: {
- value: {
- type: String,
- required: true,
- validator: (sort) => ALL_SORTS.includes(sort),
- },
- },
- computed: {
- orderBy() {
- if (this.value === RELEASED_AT_ASC || this.value === RELEASED_AT_DESC) {
- return RELEASED_AT;
- }
-
- return CREATED_AT;
- },
- direction() {
- if (this.value === RELEASED_AT_ASC || this.value === CREATED_ASC) {
- return ASCENDING_ORDER;
- }
-
- return DESCENDING_ORDER;
- },
- sortOptions() {
- return SORT_OPTIONS;
- },
- sortText() {
- return this.sortOptions.find((s) => s.orderBy === this.orderBy).label;
- },
- isDirectionAscending() {
- return this.direction === ASCENDING_ORDER;
- },
- },
- methods: {
- onDirectionChange() {
- const direction = this.isDirectionAscending ? DESCENDING_ORDER : ASCENDING_ORDER;
- this.emitInputEventIfChanged(this.orderBy, direction);
- },
- onSortItemClick(item) {
- this.emitInputEventIfChanged(item.orderBy, this.direction);
- },
- isActiveSortItem(item) {
- return this.orderBy === item.orderBy;
- },
- emitInputEventIfChanged(orderBy, direction) {
- const newSort = SORT_MAP[orderBy][direction];
- if (newSort !== this.value) {
- this.$emit('input', SORT_MAP[orderBy][direction]);
- }
- },
- },
-};
-</script>
-
-<template>
- <gl-sorting
- :text="sortText"
- :is-ascending="isDirectionAscending"
- data-testid="releases-sort"
- @sortDirectionChange="onDirectionChange"
- >
- <gl-sorting-item
- v-for="item of sortOptions"
- :key="item.orderBy"
- :active="isActiveSortItem(item)"
- @click="onSortItemClick(item)"
- >
- {{ item.label }}
- </gl-sorting-item>
- </gl-sorting>
-</template>
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,
- },
-});