diff options
Diffstat (limited to 'app/assets/javascripts/releases/components')
8 files changed, 283 insertions, 502 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') }} </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> |