diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2020-10-21 07:08:36 +0000 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2020-10-21 07:08:36 +0000 |
commit | 48aff82709769b098321c738f3444b9bdaa694c6 (patch) | |
tree | e00c7c43e2d9b603a5a6af576b1685e400410dee /app/assets/javascripts/releases | |
parent | 879f5329ee916a948223f8f43d77fba4da6cd028 (diff) | |
download | gitlab-ce-48aff82709769b098321c738f3444b9bdaa694c6.tar.gz |
Add latest changes from gitlab-org/gitlab@13-5-stable-eev13.5.0-rc42
Diffstat (limited to 'app/assets/javascripts/releases')
31 files changed, 416 insertions, 488 deletions
diff --git a/app/assets/javascripts/releases/components/app_edit_new.vue b/app/assets/javascripts/releases/components/app_edit_new.vue index e1edf3d689d..1a07e0ed762 100644 --- a/app/assets/javascripts/releases/components/app_edit_new.vue +++ b/app/assets/javascripts/releases/components/app_edit_new.vue @@ -1,13 +1,11 @@ <script> -/* eslint-disable vue/no-v-html */ import { mapState, mapActions, mapGetters } from 'vuex'; -import { GlButton, GlFormInput, GlFormGroup } from '@gitlab/ui'; -import { __, sprintf } from '~/locale'; +import { GlButton, GlFormInput, GlFormGroup, GlSprintf } from '@gitlab/ui'; +import { __ } from '~/locale'; import MarkdownField from '~/vue_shared/components/markdown/field.vue'; import { BACK_URL_PARAM } from '~/releases/constants'; import { getParameterByName } from '~/lib/utils/common_utils'; import AssetLinksForm from './asset_links_form.vue'; -import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; import MilestoneCombobox from '~/milestones/project_milestone_combobox.vue'; import TagField from './tag_field.vue'; @@ -17,12 +15,12 @@ export default { GlFormInput, GlFormGroup, GlButton, + GlSprintf, MarkdownField, AssetLinksForm, MilestoneCombobox, TagField, }, - mixins: [glFeatureFlagsMixin()], computed: { ...mapState('detail', [ 'isFetchingRelease', @@ -41,18 +39,6 @@ export default { showForm() { return Boolean(!this.isFetchingRelease && !this.fetchError && this.release); }, - subtitleText() { - return sprintf( - __( - 'Releases are based on Git tags. We recommend tags that use semantic versioning, for example %{codeStart}v1.0%{codeEnd}, %{codeStart}v2.0-pre%{codeEnd}.', - ), - { - codeStart: '<code>', - codeEnd: '</code>', - }, - false, - ); - }, releaseTitle: { get() { return this.$store.state.detail.release.name; @@ -80,9 +66,6 @@ export default { cancelPath() { return getParameterByName(BACK_URL_PARAM) || this.releasesPagePath; }, - showAssetLinksForm() { - return this.glFeatures.releaseAssetLinkEditing; - }, saveButtonLabel() { return this.isExistingRelease ? __('Save changes') : __('Create release'); }, @@ -127,7 +110,19 @@ export default { </script> <template> <div class="d-flex flex-column"> - <p class="pt-3 js-subtitle-text" v-html="subtitleText"></p> + <p class="pt-3 js-subtitle-text"> + <gl-sprintf + :message=" + __( + 'Releases are based on Git tags. We recommend tags that use semantic versioning, for example %{codeStart}v1.0%{codeEnd}, %{codeStart}v2.0-pre%{codeEnd}.', + ) + " + > + <template #code="{ content }"> + <code>{{ content }}</code> + </template> + </gl-sprintf> + </p> <form v-if="showForm" class="js-quick-submit" @submit.prevent="submitForm"> <tag-field /> <gl-form-group> @@ -150,7 +145,7 @@ export default { /> </div> </gl-form-group> - <gl-form-group> + <gl-form-group data-testid="release-notes"> <label for="release-notes">{{ __('Release notes') }}</label> <div class="bordered-box pr-3 pl-3"> <markdown-field @@ -158,6 +153,7 @@ export default { :markdown-preview-path="markdownPreviewPath" :markdown-docs-path="markdownDocsPath" :add-spacing-classes="false" + :textarea-value="releaseNotes" class="gl-mt-3 gl-mb-3" > <template #textarea> @@ -175,7 +171,7 @@ export default { </div> </gl-form-group> - <asset-links-form v-if="showAssetLinksForm" /> + <asset-links-form /> <div class="d-flex pt-3"> <gl-button diff --git a/app/assets/javascripts/releases/components/app_index.vue b/app/assets/javascripts/releases/components/app_index.vue index b8cf6ce478f..422d8bf630d 100644 --- a/app/assets/javascripts/releases/components/app_index.vue +++ b/app/assets/javascripts/releases/components/app_index.vue @@ -1,29 +1,21 @@ <script> import { mapState, mapActions } from 'vuex'; -import { - GlDeprecatedSkeletonLoading as GlSkeletonLoading, - GlEmptyState, - GlLink, - GlButton, -} from '@gitlab/ui'; -import { - getParameterByName, - historyPushState, - buildUrlWithCurrentLocation, -} from '~/lib/utils/common_utils'; +import { GlEmptyState, GlLink, GlButton } from '@gitlab/ui'; +import { getParameterByName } from '~/lib/utils/common_utils'; import { __ } from '~/locale'; -import TablePagination from '~/vue_shared/components/pagination/table_pagination.vue'; import ReleaseBlock from './release_block.vue'; +import ReleasesPagination from './releases_pagination.vue'; +import ReleaseSkeletonLoader from './release_skeleton_loader.vue'; export default { name: 'ReleasesApp', components: { - GlSkeletonLoading, GlEmptyState, - ReleaseBlock, - TablePagination, GlLink, GlButton, + ReleaseBlock, + ReleasesPagination, + ReleaseSkeletonLoader, }, computed: { ...mapState('list', [ @@ -33,7 +25,6 @@ export default { 'isLoading', 'releases', 'hasError', - 'pageInfo', ]), shouldRenderEmptyState() { return !this.releases.length && !this.hasError && !this.isLoading; @@ -48,15 +39,23 @@ export default { }, }, created() { - this.fetchReleases({ - page: getParameterByName('page'), - }); + this.fetchReleases(); + + window.addEventListener('popstate', this.fetchReleases); }, methods: { - ...mapActions('list', ['fetchReleases']), - onChangePage(page) { - historyPushState(buildUrlWithCurrentLocation(`?page=${page}`)); - this.fetchReleases({ page }); + ...mapActions('list', { + fetchReleasesStoreAction: 'fetchReleases', + }), + fetchReleases() { + this.fetchReleasesStoreAction({ + // these two parameters are only used in "GraphQL mode" + before: getParameterByName('before'), + after: getParameterByName('after'), + + // this parameter is only used when in "REST mode" + page: getParameterByName('page'), + }); }, }, }; @@ -74,7 +73,7 @@ export default { {{ __('New release') }} </gl-button> - <gl-skeleton-loading v-if="isLoading" class="js-loading" /> + <release-skeleton-loader v-if="isLoading" class="js-loading" /> <gl-empty-state v-else-if="shouldRenderEmptyState" @@ -105,7 +104,7 @@ export default { /> </div> - <table-pagination v-if="!isLoading" :change="onChangePage" :page-info="pageInfo" /> + <releases-pagination v-if="!isLoading" /> </div> </template> <style> diff --git a/app/assets/javascripts/releases/components/app_show.vue b/app/assets/javascripts/releases/components/app_show.vue index 8b89f0cf3fc..9ef38503c10 100644 --- a/app/assets/javascripts/releases/components/app_show.vue +++ b/app/assets/javascripts/releases/components/app_show.vue @@ -1,13 +1,13 @@ <script> import { mapState, mapActions } from 'vuex'; -import { GlDeprecatedSkeletonLoading as GlSkeletonLoading } from '@gitlab/ui'; import ReleaseBlock from './release_block.vue'; +import ReleaseSkeletonLoader from './release_skeleton_loader.vue'; export default { name: 'ReleaseShowApp', components: { - GlSkeletonLoading, ReleaseBlock, + ReleaseSkeletonLoader, }, computed: { ...mapState('detail', ['isFetchingRelease', 'fetchError', 'release']), @@ -22,7 +22,7 @@ export default { </script> <template> <div class="gl-mt-3"> - <gl-skeleton-loading v-if="isFetchingRelease" /> + <release-skeleton-loader v-if="isFetchingRelease" /> <release-block v-else-if="!fetchError" :release="release" /> </div> diff --git a/app/assets/javascripts/releases/components/asset_links_form.vue b/app/assets/javascripts/releases/components/asset_links_form.vue index 07fab840067..331cc8ade6c 100644 --- a/app/assets/javascripts/releases/components/asset_links_form.vue +++ b/app/assets/javascripts/releases/components/asset_links_form.vue @@ -10,7 +10,6 @@ import { GlFormInput, GlFormSelect, } from '@gitlab/ui'; -import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; import { DEFAULT_ASSET_LINK_TYPE, ASSET_LINK_TYPE } from '../constants'; import { s__ } from '~/locale'; @@ -26,7 +25,6 @@ export default { GlFormSelect, }, directives: { GlTooltip: GlTooltipDirective }, - mixins: [glFeatureFlagsMixin()], computed: { ...mapState('detail', ['release', 'releaseAssetsDocsPath']), ...mapGetters('detail', ['validationErrors']), @@ -195,7 +193,6 @@ export default { </gl-form-group> <gl-form-group - v-if="glFeatures.releaseAssetLinkType" class="link-type-field col-auto px-sm-2" :label="__('Type')" :label-for="`asset-type-${index}`" diff --git a/app/assets/javascripts/releases/components/evidence_block.vue b/app/assets/javascripts/releases/components/evidence_block.vue index 3724162f6d5..6e6017637d4 100644 --- a/app/assets/javascripts/releases/components/evidence_block.vue +++ b/app/assets/javascripts/releases/components/evidence_block.vue @@ -83,11 +83,7 @@ export default { <span class="js-expanded monospace gl-pl-2">{{ sha(index) }}</span> </template> </expand-button> - <clipboard-button - :title="__('Copy evidence SHA')" - :text="sha(index)" - css-class="btn-default btn-transparent btn-clipboard" - /> + <clipboard-button :title="__('Copy evidence SHA')" :text="sha(index)" category="tertiary" /> </div> <div class="d-flex align-items-center text-muted"> diff --git a/app/assets/javascripts/releases/components/release_block.vue b/app/assets/javascripts/releases/components/release_block.vue index 2629df08be7..e9163a52792 100644 --- a/app/assets/javascripts/releases/components/release_block.vue +++ b/app/assets/javascripts/releases/components/release_block.vue @@ -11,7 +11,6 @@ import EvidenceBlock from './evidence_block.vue'; import ReleaseBlockAssets from './release_block_assets.vue'; import ReleaseBlockFooter from './release_block_footer.vue'; import ReleaseBlockHeader from './release_block_header.vue'; -import ReleaseBlockMetadata from './release_block_metadata.vue'; import ReleaseBlockMilestoneInfo from './release_block_milestone_info.vue'; export default { @@ -21,7 +20,6 @@ export default { ReleaseBlockAssets, ReleaseBlockFooter, ReleaseBlockHeader, - ReleaseBlockMetadata, ReleaseBlockMilestoneInfo, }, mixins: [glFeatureFlagsMixin()], @@ -54,22 +52,13 @@ export default { milestones() { return this.release.milestones || []; }, - shouldShowEvidence() { - return this.glFeatures.releaseEvidenceCollection; - }, - shouldShowFooter() { - return this.glFeatures.releaseIssueSummary; - }, shouldRenderAssets() { return Boolean( this.assets.links.length || (this.assets.sources && this.assets.sources.length), ); }, - shouldRenderReleaseMetaData() { - return !this.glFeatures.releaseIssueSummary; - }, shouldRenderMilestoneInfo() { - return Boolean(this.glFeatures.releaseIssueSummary && !isEmpty(this.release.milestones)); + return Boolean(!isEmpty(this.release.milestones)); }, }, @@ -105,9 +94,8 @@ export default { <hr class="mb-3 mt-0" /> </div> - <release-block-metadata v-if="shouldRenderReleaseMetaData" :release="release" /> <release-block-assets v-if="shouldRenderAssets" :assets="assets" /> - <evidence-block v-if="hasEvidence && shouldShowEvidence" :release="release" /> + <evidence-block v-if="hasEvidence" :release="release" /> <div ref="gfm-content" class="card-text gl-mt-3"> <div class="md" v-html="release.descriptionHtml"></div> @@ -115,7 +103,6 @@ export default { </div> <release-block-footer - v-if="shouldShowFooter" class="card-footer" :commit="release.commit" :commit-path="release.commitPath" diff --git a/app/assets/javascripts/releases/components/release_block_assets.vue b/app/assets/javascripts/releases/components/release_block_assets.vue index 8824cbefd7e..eb83d8657c0 100644 --- a/app/assets/javascripts/releases/components/release_block_assets.vue +++ b/app/assets/javascripts/releases/components/release_block_assets.vue @@ -1,7 +1,6 @@ <script> import { GlTooltipDirective, GlLink, GlButton, GlCollapse, GlIcon, GlBadge } from '@gitlab/ui'; import { difference, get } from 'lodash'; -import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; import { ASSET_LINK_TYPE } from '../constants'; import { __, s__, sprintf } from '~/locale'; @@ -17,7 +16,6 @@ export default { directives: { GlTooltip: GlTooltipDirective, }, - mixins: [glFeatureFlagsMixin()], props: { assets: { type: Object, @@ -30,9 +28,6 @@ export default { }; }, computed: { - hasAssets() { - return Boolean(this.assets.count); - }, imageLinks() { return this.linksForType(ASSET_LINK_TYPE.IMAGE); }, @@ -95,94 +90,50 @@ export default { <template> <div class="card-text gl-mt-3"> - <template v-if="glFeatures.releaseAssetLinkType"> - <gl-button - data-testid="accordion-button" - variant="link" - class="gl-font-weight-bold" - @click="toggleAssetsExpansion" - > - <gl-icon - name="chevron-right" - class="gl-transition-medium" - :class="{ 'gl-rotate-90': isAssetsExpanded }" - /> - {{ __('Assets') }} - <gl-badge size="sm" variant="neutral" class="gl-display-inline-block">{{ - assets.count - }}</gl-badge> - </gl-button> - <gl-collapse v-model="isAssetsExpanded"> - <div class="gl-pl-6 gl-pt-3 js-assets-list"> - <template v-for="(section, index) in sections"> - <h5 v-if="section.title" :key="`section-header-${index}`" class="gl-mb-2"> - {{ section.title }} - </h5> - <ul :key="`section-body-${index}`" class="list-unstyled gl-m-0"> - <li v-for="link in section.links" :key="link.url"> - <gl-link - :href="link.directAssetUrl || link.url" - class="gl-display-flex gl-align-items-center gl-line-height-24" - > - <gl-icon - :name="section.iconName" - class="gl-mr-2 gl-flex-shrink-0 gl-flex-grow-0" - /> - {{ link.name }} - <gl-icon - v-if="link.external" - v-gl-tooltip - name="external-link" - :aria-label="$options.externalLinkTooltipText" - :title="$options.externalLinkTooltipText" - data-testid="external-link-indicator" - class="gl-ml-2 gl-flex-shrink-0 gl-flex-grow-0 gl-text-gray-400" - /> - </gl-link> - </li> - </ul> - </template> - </div> - </gl-collapse> - </template> - - <template v-else> - <b> - {{ __('Assets') }} - <span class="js-assets-count badge badge-pill">{{ assets.count }}</span> - </b> - - <ul v-if="assets.links.length" class="pl-0 mb-0 gl-mt-3 list-unstyled js-assets-list"> - <li v-for="link in assets.links" :key="link.name" class="gl-mb-3"> - <gl-link v-gl-tooltip.bottom :title="__('Download asset')" :href="link.directAssetUrl"> - <gl-icon name="package" class="align-middle gl-mr-2 align-text-bottom" /> - {{ link.name }} - <span v-if="link.external" data-testid="external-link-indicator">{{ - __('(external source)') - }}</span> - </gl-link> - </li> - </ul> - - <div v-if="hasAssets" class="dropdown"> - <button - type="button" - class="btn btn-link" - data-toggle="dropdown" - aria-haspopup="true" - aria-expanded="false" - > - <gl-icon name="doc-code" class="align-top gl-mr-2" /> - {{ __('Source code') }} - <gl-icon name="chevron-down" /> - </button> - - <div class="js-sources-dropdown dropdown-menu"> - <li v-for="asset in assets.sources" :key="asset.url"> - <gl-link :href="asset.url">{{ __('Download') }} {{ asset.format }}</gl-link> - </li> - </div> + <gl-button + data-testid="accordion-button" + variant="link" + class="gl-font-weight-bold" + @click="toggleAssetsExpansion" + > + <gl-icon + name="chevron-right" + class="gl-transition-medium" + :class="{ 'gl-rotate-90': isAssetsExpanded }" + /> + {{ __('Assets') }} + <gl-badge size="sm" variant="neutral" class="gl-display-inline-block">{{ + assets.count + }}</gl-badge> + </gl-button> + <gl-collapse v-model="isAssetsExpanded"> + <div class="gl-pl-6 gl-pt-3 js-assets-list"> + <template v-for="(section, index) in sections"> + <h5 v-if="section.title" :key="`section-header-${index}`" class="gl-mb-2"> + {{ section.title }} + </h5> + <ul :key="`section-body-${index}`" class="list-unstyled gl-m-0"> + <li v-for="link in section.links" :key="link.url" class="gl-display-flex"> + <gl-link + :href="link.directAssetUrl || link.url" + class="gl-display-flex gl-align-items-center gl-line-height-24" + > + <gl-icon :name="section.iconName" class="gl-mr-2 gl-flex-shrink-0 gl-flex-grow-0" /> + {{ link.name }} + <gl-icon + v-if="link.external" + v-gl-tooltip + name="external-link" + :aria-label="$options.externalLinkTooltipText" + :title="$options.externalLinkTooltipText" + data-testid="external-link-indicator" + class="gl-ml-2 gl-flex-shrink-0 gl-flex-grow-0 gl-text-gray-400" + /> + </gl-link> + </li> + </ul> + </template> </div> - </template> + </gl-collapse> </div> </template> diff --git a/app/assets/javascripts/releases/components/release_block_author.vue b/app/assets/javascripts/releases/components/release_block_author.vue deleted file mode 100644 index 72c578068cd..00000000000 --- a/app/assets/javascripts/releases/components/release_block_author.vue +++ /dev/null @@ -1,42 +0,0 @@ -<script> -import { GlSprintf } from '@gitlab/ui'; -import { __, sprintf } from '~/locale'; -import UserAvatarLink from '~/vue_shared/components/user_avatar/user_avatar_link.vue'; - -export default { - name: 'ReleaseBlockAuthor', - components: { - GlSprintf, - UserAvatarLink, - }, - props: { - author: { - type: Object, - required: true, - }, - }, - computed: { - userImageAltDescription() { - return this.author && this.author.username - ? sprintf(__("%{username}'s avatar"), { username: this.author.username }) - : null; - }, - }, -}; -</script> - -<template> - <div class="d-flex"> - <gl-sprintf :message="__('by %{user}')"> - <template #user> - <user-avatar-link - class="gl-ml-2" - :link-href="author.webUrl" - :img-src="author.avatarUrl" - :img-alt="userImageAltDescription" - :tooltip-text="author.username" - /> - </template> - </gl-sprintf> - </div> -</template> diff --git a/app/assets/javascripts/releases/components/release_block_header.vue b/app/assets/javascripts/releases/components/release_block_header.vue index 95292a26bce..87538244f1a 100644 --- a/app/assets/javascripts/releases/components/release_block_header.vue +++ b/app/assets/javascripts/releases/components/release_block_header.vue @@ -1,5 +1,5 @@ <script> -import { GlTooltipDirective, GlLink, GlBadge, GlButton, GlIcon } from '@gitlab/ui'; +import { GlTooltipDirective, GlLink, GlBadge, GlButton } from '@gitlab/ui'; import { BACK_URL_PARAM } from '~/releases/constants'; import { setUrlParams } from '~/lib/utils/url_utility'; @@ -8,7 +8,6 @@ export default { components: { GlLink, GlBadge, - GlIcon, GlButton, }, directives: { @@ -55,11 +54,10 @@ export default { v-gl-tooltip category="primary" variant="default" + icon="pencil" class="gl-mr-3 js-edit-button ml-2 pb-2" :title="__('Edit this release')" :href="editLink" - > - <gl-icon name="pencil" /> - </gl-button> + /> </div> </template> diff --git a/app/assets/javascripts/releases/components/release_block_metadata.vue b/app/assets/javascripts/releases/components/release_block_metadata.vue deleted file mode 100644 index 2247b4c0064..00000000000 --- a/app/assets/javascripts/releases/components/release_block_metadata.vue +++ /dev/null @@ -1,90 +0,0 @@ -<script> -import { GlTooltipDirective, GlLink, GlIcon } from '@gitlab/ui'; -import { __, sprintf } from '~/locale'; -import timeagoMixin from '~/vue_shared/mixins/timeago'; -import ReleaseBlockAuthor from './release_block_author.vue'; -import ReleaseBlockMilestones from './release_block_milestones.vue'; - -export default { - name: 'ReleaseBlockMetadata', - components: { - GlIcon, - GlLink, - ReleaseBlockAuthor, - ReleaseBlockMilestones, - }, - directives: { - GlTooltip: GlTooltipDirective, - }, - mixins: [timeagoMixin], - props: { - release: { - type: Object, - required: true, - }, - }, - computed: { - author() { - return this.release.author; - }, - commit() { - return this.release.commit || {}; - }, - commitUrl() { - return this.release.commitPath; - }, - hasAuthor() { - return Boolean(this.author); - }, - releasedTimeAgo() { - const now = new Date(); - const isFuture = now < new Date(this.release.releasedAt); - const time = this.timeFormatted(this.release.releasedAt); - return isFuture - ? sprintf(__('will be released %{time}'), { time }) - : sprintf(__('released %{time}'), { time }); - }, - shouldRenderMilestones() { - return Boolean(this.release.milestones?.length); - }, - tagUrl() { - return this.release.tagPath; - }, - }, -}; -</script> - -<template> - <div class="card-subtitle d-flex flex-wrap text-secondary"> - <div class="gl-mr-3"> - <gl-icon name="commit" class="align-middle" /> - <gl-link v-if="commitUrl" v-gl-tooltip.bottom :title="commit.title" :href="commitUrl"> - {{ commit.shortId }} - </gl-link> - <span v-else v-gl-tooltip.bottom :title="commit.title">{{ commit.shortId }}</span> - </div> - - <div class="gl-mr-3"> - <gl-icon name="tag" class="align-middle" /> - <gl-link v-if="tagUrl" v-gl-tooltip.bottom :title="__('Tag')" :href="tagUrl"> - {{ release.tagName }} - </gl-link> - <span v-else v-gl-tooltip.bottom :title="__('Tag')">{{ release.tagName }}</span> - </div> - - <release-block-milestones v-if="shouldRenderMilestones" :milestones="release.milestones" /> - - <div class="gl-mr-2"> - • - <span - v-gl-tooltip.bottom - class="js-release-date-info" - :title="tooltipTitle(release.releasedAt)" - > - {{ releasedTimeAgo }} - </span> - </div> - - <release-block-author v-if="hasAuthor" :author="author" /> - </div> -</template> diff --git a/app/assets/javascripts/releases/components/release_block_milestones.vue b/app/assets/javascripts/releases/components/release_block_milestones.vue deleted file mode 100644 index 1da683764b3..00000000000 --- a/app/assets/javascripts/releases/components/release_block_milestones.vue +++ /dev/null @@ -1,50 +0,0 @@ -<script> -import { GlTooltipDirective, GlLink, GlIcon } from '@gitlab/ui'; -import { n__ } from '~/locale'; - -export default { - name: 'ReleaseBlockMilestones', - components: { - GlLink, - GlIcon, - }, - directives: { - GlTooltip: GlTooltipDirective, - }, - props: { - milestones: { - type: Array, - required: true, - }, - }, - computed: { - labelText() { - return n__('Milestone', 'Milestones', this.milestones.length); - }, - }, -}; -</script> - -<template> - <div> - <div class="js-milestone-list-label"> - <gl-icon name="flag" class="align-middle" /> - <span class="js-label-text">{{ labelText }}</span> - </div> - - <template v-for="(milestone, index) in milestones"> - <gl-link - :key="milestone.id" - v-gl-tooltip - :title="milestone.description" - :href="milestone.webUrl" - class="mx-1 js-milestone-link" - > - {{ milestone.title }} - </gl-link> - <template v-if="index !== milestones.length - 1"> - • - </template> - </template> - </div> -</template> diff --git a/app/assets/javascripts/releases/components/release_skeleton_loader.vue b/app/assets/javascripts/releases/components/release_skeleton_loader.vue new file mode 100644 index 00000000000..054620af636 --- /dev/null +++ b/app/assets/javascripts/releases/components/release_skeleton_loader.vue @@ -0,0 +1,51 @@ +<script> +import { GlSkeletonLoader } from '@gitlab/ui'; + +export default { + name: 'ReleaseSkeletonLoader', + components: { GlSkeletonLoader }, +}; +</script> +<template> + <gl-skeleton-loader :width="1248" :height="420"> + <!-- Outside border --> + <path + d="M 4.5 0 C 2.0156486 0 0 2.0156486 0 4.5 L 0 415.5 C 0 417.98435 2.0156486 420 4.5 420 L 1243.5 420 C 1245.9844 420 1248 417.98435 1248 415.5 L 1248 4.5 C 1248 2.0156486 1245.9844 0 1243.5 0 L 4.5 0 z M 4.5 1 L 1243.5 1 C 1245.4476 1 1247 2.5523514 1247 4.5 L 1247 415.5 C 1247 417.44765 1245.4476 419 1243.5 419 L 4.5 419 C 2.5523514 419 1 417.44765 1 415.5 L 1 4.5 C 1 2.5523514 2.5523514 1 4.5 1 z " + /> + + <!-- Header bottom border --> + <rect x="0" y="63.5" width="1248" height="1" /> + + <!-- Release title --> + <rect x="16" y="20" width="293" height="24" /> + + <!-- Edit (pencil) button --> + <rect x="1207" y="16" rx="4" width="32" height="32" /> + + <!-- Asset link 1 --> + <rect x="40" y="121" rx="4" width="16" height="16" /> + <rect x="60" y="125" width="116" height="8" /> + + <!-- Asset link 2 --> + <rect x="40" y="145" rx="4" width="16" height="16" /> + <rect x="60" y="149" width="132" height="8" /> + + <!-- Asset link 3 --> + <rect x="40" y="169" rx="4" width="16" height="16" /> + <rect x="60" y="173" width="140" height="8" /> + + <!-- Asset link 4 --> + <rect x="40" y="193" rx="4" width="16" height="16" /> + <rect x="60" y="197" width="112" height="8" /> + + <!-- Release notes --> + <rect x="16" y="228" width="480" height="8" /> + <rect x="16" y="252" width="560" height="8" /> + <rect x="16" y="276" width="480" height="8" /> + <rect x="16" y="300" width="560" height="8" /> + <rect x="16" y="324" width="320" height="8" /> + + <!-- Footer top border --> + <rect x="0" y="373" width="1248" height="1" /> + </gl-skeleton-loader> +</template> diff --git a/app/assets/javascripts/releases/components/releases_pagination_graphql.vue b/app/assets/javascripts/releases/components/releases_pagination_graphql.vue index a4fe407a5bd..cb6f1fa18a1 100644 --- a/app/assets/javascripts/releases/components/releases_pagination_graphql.vue +++ b/app/assets/javascripts/releases/components/releases_pagination_graphql.vue @@ -13,14 +13,14 @@ export default { }, }, methods: { - ...mapActions('list', ['fetchReleasesGraphQl']), + ...mapActions('list', ['fetchReleases']), onPrev(before) { historyPushState(buildUrlWithCurrentLocation(`?before=${before}`)); - this.fetchReleasesGraphQl({ before }); + this.fetchReleases({ before }); }, onNext(after) { historyPushState(buildUrlWithCurrentLocation(`?after=${after}`)); - this.fetchReleasesGraphQl({ after }); + this.fetchReleases({ after }); }, }, }; diff --git a/app/assets/javascripts/releases/components/releases_pagination_rest.vue b/app/assets/javascripts/releases/components/releases_pagination_rest.vue index 992cc4cd469..334458a2302 100644 --- a/app/assets/javascripts/releases/components/releases_pagination_rest.vue +++ b/app/assets/javascripts/releases/components/releases_pagination_rest.vue @@ -7,18 +7,18 @@ export default { name: 'ReleasesPaginationRest', components: { TablePagination }, computed: { - ...mapState('list', ['pageInfo']), + ...mapState('list', ['restPageInfo']), }, methods: { - ...mapActions('list', ['fetchReleasesRest']), + ...mapActions('list', ['fetchReleases']), onChangePage(page) { historyPushState(buildUrlWithCurrentLocation(`?page=${page}`)); - this.fetchReleasesRest({ page }); + this.fetchReleases({ page }); }, }, }; </script> <template> - <table-pagination :change="onChangePage" :page-info="pageInfo" /> + <table-pagination :change="onChangePage" :page-info="restPageInfo" /> </template> diff --git a/app/assets/javascripts/releases/constants.js b/app/assets/javascripts/releases/constants.js index 361cee70747..953e7b4189c 100644 --- a/app/assets/javascripts/releases/constants.js +++ b/app/assets/javascripts/releases/constants.js @@ -10,3 +10,5 @@ export const ASSET_LINK_TYPE = Object.freeze({ }); export const DEFAULT_ASSET_LINK_TYPE = ASSET_LINK_TYPE.OTHER; + +export const PAGE_SIZE = 20; diff --git a/app/assets/javascripts/releases/mount_edit.js b/app/assets/javascripts/releases/mount_edit.js index 623b18591a0..2f4b0e64e36 100644 --- a/app/assets/javascripts/releases/mount_edit.js +++ b/app/assets/javascripts/releases/mount_edit.js @@ -13,9 +13,6 @@ export default () => { modules: { detail: createDetailModule(el.dataset), }, - featureFlags: { - releaseShowPage: Boolean(gon.features?.releaseShowPage), - }, }); return new Vue({ diff --git a/app/assets/javascripts/releases/mount_new.js b/app/assets/javascripts/releases/mount_new.js index 10725e47740..5c481498ffb 100644 --- a/app/assets/javascripts/releases/mount_new.js +++ b/app/assets/javascripts/releases/mount_new.js @@ -13,9 +13,6 @@ export default () => { modules: { detail: createDetailModule(el.dataset), }, - featureFlags: { - releaseShowPage: Boolean(gon.features?.releaseShowPage), - }, }); return new Vue({ diff --git a/app/assets/javascripts/releases/mount_show.js b/app/assets/javascripts/releases/mount_show.js index eef015ee0a6..b09ecc9fb55 100644 --- a/app/assets/javascripts/releases/mount_show.js +++ b/app/assets/javascripts/releases/mount_show.js @@ -13,6 +13,9 @@ export default () => { modules: { detail: createDetailModule(el.dataset), }, + featureFlags: { + graphqlIndividualReleasePage: Boolean(gon.features?.graphqlIndividualReleasePage), + }, }); return new Vue({ diff --git a/app/assets/javascripts/releases/queries/all_releases.query.graphql b/app/assets/javascripts/releases/queries/all_releases.query.graphql index 7a99f32fdfa..c35306f163d 100644 --- a/app/assets/javascripts/releases/queries/all_releases.query.graphql +++ b/app/assets/javascripts/releases/queries/all_releases.query.graphql @@ -1,68 +1,16 @@ -query allReleases($fullPath: ID!) { +#import "./release.fragment.graphql" + +query allReleases($fullPath: ID!, $first: Int, $last: Int, $before: String, $after: String) { project(fullPath: $fullPath) { - releases(first: 20) { - count + releases(first: $first, last: $last, before: $before, after: $after) { nodes { - name - tagName - tagPath - descriptionHtml - releasedAt - upcomingRelease - assets { - count - sources { - nodes { - format - url - } - } - links { - nodes { - id - name - url - directAssetUrl - linkType - external - } - } - } - evidences { - nodes { - filepath - collectedAt - sha - } - } - links { - editUrl - issuesUrl - mergeRequestsUrl - selfUrl - } - commit { - sha - webUrl - title - } - author { - webUrl - avatarUrl - username - } - milestones { - nodes { - id - title - description - webPath - stats { - totalIssuesCount - closedIssuesCount - } - } - } + ...Release + } + pageInfo { + startCursor + hasPreviousPage + hasNextPage + endCursor } } } diff --git a/app/assets/javascripts/releases/queries/one_release.query.graphql b/app/assets/javascripts/releases/queries/one_release.query.graphql new file mode 100644 index 00000000000..b893aea94b0 --- /dev/null +++ b/app/assets/javascripts/releases/queries/one_release.query.graphql @@ -0,0 +1,9 @@ +#import "./release.fragment.graphql" + +query oneRelease($fullPath: ID!, $tagName: String!) { + project(fullPath: $fullPath) { + release(tagName: $tagName) { + ...Release + } + } +} diff --git a/app/assets/javascripts/releases/queries/release.fragment.graphql b/app/assets/javascripts/releases/queries/release.fragment.graphql new file mode 100644 index 00000000000..445ed616348 --- /dev/null +++ b/app/assets/javascripts/releases/queries/release.fragment.graphql @@ -0,0 +1,62 @@ +fragment Release on Release { + name + tagName + tagPath + descriptionHtml + releasedAt + upcomingRelease + assets { + count + sources { + nodes { + format + url + } + } + links { + nodes { + id + name + url + directAssetUrl + linkType + external + } + } + } + evidences { + nodes { + filepath + collectedAt + sha + } + } + links { + editUrl + issuesUrl + mergeRequestsUrl + selfUrl + } + commit { + sha + webUrl + title + } + author { + webUrl + avatarUrl + username + } + milestones { + nodes { + id + title + description + webPath + stats { + totalIssuesCount + closedIssuesCount + } + } + } +} diff --git a/app/assets/javascripts/releases/stores/getters.js b/app/assets/javascripts/releases/stores/getters.js new file mode 100644 index 00000000000..6a1da63289c --- /dev/null +++ b/app/assets/javascripts/releases/stores/getters.js @@ -0,0 +1,11 @@ +/** + * @returns {Boolean} `true` if all the feature flags + * required to enable the GraphQL endpoint are enabled + */ +export const useGraphQLEndpoint = rootState => { + return Boolean( + rootState.featureFlags.graphqlReleaseData && + rootState.featureFlags.graphqlReleasesPage && + rootState.featureFlags.graphqlMilestoneStats, + ); +}; diff --git a/app/assets/javascripts/releases/stores/index.js b/app/assets/javascripts/releases/stores/index.js index b2e93d789d7..cc8b586964f 100644 --- a/app/assets/javascripts/releases/stores/index.js +++ b/app/assets/javascripts/releases/stores/index.js @@ -1,7 +1,9 @@ import Vuex from 'vuex'; +import * as getters from './getters'; export default ({ modules, featureFlags }) => new Vuex.Store({ modules, state: { featureFlags }, + getters, }); diff --git a/app/assets/javascripts/releases/stores/modules/detail/actions.js b/app/assets/javascripts/releases/stores/modules/detail/actions.js index 5b682a0ab0f..e8a46f40d20 100644 --- a/app/assets/javascripts/releases/stores/modules/detail/actions.js +++ b/app/assets/javascripts/releases/stores/modules/detail/actions.js @@ -3,7 +3,13 @@ import api from '~/api'; import { deprecatedCreateFlash as createFlash } from '~/flash'; import { s__ } from '~/locale'; import { redirectTo } from '~/lib/utils/url_utility'; -import { releaseToApiJson, apiJsonToRelease } from '~/releases/util'; +import { + releaseToApiJson, + apiJsonToRelease, + gqClient, + convertOneReleaseGraphQLResponse, +} from '~/releases/util'; +import oneReleaseQuery from '~/releases/queries/one_release.query.graphql'; export const initializeRelease = ({ commit, dispatch, getters }) => { if (getters.isExistingRelease) { @@ -18,9 +24,29 @@ export const initializeRelease = ({ commit, dispatch, getters }) => { return Promise.resolve(); }; -export const fetchRelease = ({ commit, state }) => { +export const fetchRelease = ({ commit, state, rootState }) => { commit(types.REQUEST_RELEASE); + if (rootState.featureFlags?.graphqlIndividualReleasePage) { + return gqClient + .query({ + query: oneReleaseQuery, + variables: { + fullPath: state.projectPath, + tagName: state.tagName, + }, + }) + .then(response => { + const { data: release } = convertOneReleaseGraphQLResponse(response); + + commit(types.RECEIVE_RELEASE_SUCCESS, release); + }) + .catch(error => { + commit(types.RECEIVE_RELEASE_ERROR, error); + createFlash(s__('Release|Something went wrong while getting the release details')); + }); + } + return api .release(state.projectId, state.tagName) .then(({ data }) => { @@ -45,6 +71,9 @@ export const updateReleaseNotes = ({ commit }, notes) => commit(types.UPDATE_REL export const updateReleaseMilestones = ({ commit }, milestones) => commit(types.UPDATE_RELEASE_MILESTONES, milestones); +export const updateReleaseGroupMilestones = ({ commit }, groupMilestones) => + commit(types.UPDATE_RELEASE_GROUP_MILESTONES, groupMilestones); + export const addEmptyAssetLink = ({ commit }) => { commit(types.ADD_EMPTY_ASSET_LINK); }; @@ -65,9 +94,9 @@ export const removeAssetLink = ({ commit }, linkIdToRemove) => { commit(types.REMOVE_ASSET_LINK, linkIdToRemove); }; -export const receiveSaveReleaseSuccess = ({ commit, state, rootState }, release) => { +export const receiveSaveReleaseSuccess = ({ commit }, release) => { commit(types.RECEIVE_SAVE_RELEASE_SUCCESS); - redirectTo(rootState.featureFlags.releaseShowPage ? release._links.self : state.releasesPagePath); + redirectTo(release._links.self); }; export const saveRelease = ({ commit, dispatch, getters }) => { diff --git a/app/assets/javascripts/releases/stores/modules/detail/mutation_types.js b/app/assets/javascripts/releases/stores/modules/detail/mutation_types.js index 7784e0cc741..1b2f5f33f02 100644 --- a/app/assets/javascripts/releases/stores/modules/detail/mutation_types.js +++ b/app/assets/javascripts/releases/stores/modules/detail/mutation_types.js @@ -9,6 +9,7 @@ export const UPDATE_CREATE_FROM = 'UPDATE_CREATE_FROM'; export const UPDATE_RELEASE_TITLE = 'UPDATE_RELEASE_TITLE'; export const UPDATE_RELEASE_NOTES = 'UPDATE_RELEASE_NOTES'; export const UPDATE_RELEASE_MILESTONES = 'UPDATE_RELEASE_MILESTONES'; +export const UPDATE_RELEASE_GROUP_MILESTONES = 'UPDATE_RELEASE_GROUP_MILESTONES'; export const REQUEST_SAVE_RELEASE = 'REQUEST_SAVE_RELEASE'; export const RECEIVE_SAVE_RELEASE_SUCCESS = 'RECEIVE_SAVE_RELEASE_SUCCESS'; diff --git a/app/assets/javascripts/releases/stores/modules/detail/mutations.js b/app/assets/javascripts/releases/stores/modules/detail/mutations.js index 750f496665d..58a1958c5e2 100644 --- a/app/assets/javascripts/releases/stores/modules/detail/mutations.js +++ b/app/assets/javascripts/releases/stores/modules/detail/mutations.js @@ -13,6 +13,7 @@ export default { name: '', description: '', milestones: [], + groupMilestones: [], assets: { links: [], }, @@ -51,6 +52,10 @@ export default { state.release.milestones = milestones; }, + [types.UPDATE_RELEASE_GROUP_MILESTONES](state, groupMilestones) { + state.release.groupMilestones = groupMilestones; + }, + [types.REQUEST_SAVE_RELEASE](state) { state.isUpdatingRelease = true; }, diff --git a/app/assets/javascripts/releases/stores/modules/detail/state.js b/app/assets/javascripts/releases/stores/modules/detail/state.js index a46e750df53..782a5c46d6c 100644 --- a/app/assets/javascripts/releases/stores/modules/detail/state.js +++ b/app/assets/javascripts/releases/stores/modules/detail/state.js @@ -1,5 +1,6 @@ export default ({ projectId, + projectPath, markdownDocsPath, markdownPreviewPath, updateReleaseApiDocsPath, @@ -12,6 +13,7 @@ export default ({ defaultBranch = null, }) => ({ projectId, + projectPath, markdownDocsPath, markdownPreviewPath, updateReleaseApiDocsPath, diff --git a/app/assets/javascripts/releases/stores/modules/list/actions.js b/app/assets/javascripts/releases/stores/modules/list/actions.js index 945b093b983..02e67415e63 100644 --- a/app/assets/javascripts/releases/stores/modules/list/actions.js +++ b/app/assets/javascripts/releases/stores/modules/list/actions.js @@ -8,55 +8,90 @@ import { convertObjectPropsToCamelCase, } from '~/lib/utils/common_utils'; import allReleasesQuery from '~/releases/queries/all_releases.query.graphql'; -import { gqClient, convertGraphQLResponse } from '../../../util'; +import { gqClient, convertAllReleasesGraphQLResponse } from '../../../util'; +import { PAGE_SIZE } from '../../../constants'; /** - * Commits a mutation to update the state while the main endpoint is being requested. + * Gets a paginated list of releases from the server + * + * @param {Object} vuexParams + * @param {Object} actionParams + * @param {Number} [actionParams.page] The page number of results to fetch + * (this parameter is only used when fetching results from the REST API) + * @param {String} [actionParams.before] A GraphQL cursor. If provided, + * the items returned will proceed the provided cursor (this parameter is only + * used when fetching results from the GraphQL API). + * @param {String} [actionParams.after] A GraphQL cursor. If provided, + * the items returned will follow the provided cursor (this parameter is only + * used when fetching results from the GraphQL API). */ -export const requestReleases = ({ commit }) => commit(types.REQUEST_RELEASES); +export const fetchReleases = ({ dispatch, rootGetters }, { page = 1, before, after }) => { + if (rootGetters.useGraphQLEndpoint) { + dispatch('fetchReleasesGraphQl', { before, after }); + } else { + dispatch('fetchReleasesRest', { page }); + } +}; /** - * Fetches the main endpoint. - * Will dispatch requestNamespace action before starting the request. - * Will dispatch receiveNamespaceSuccess if the request is successful - * Will dispatch receiveNamesapceError if the request returns an error - * - * @param {String} projectId + * Gets a paginated list of releases from the GraphQL endpoint */ -export const fetchReleases = ({ dispatch, rootState, state }, { page = '1' }) => { - dispatch('requestReleases'); +export const fetchReleasesGraphQl = ( + { dispatch, commit, state }, + { before = null, after = null }, +) => { + commit(types.REQUEST_RELEASES); - if ( - rootState.featureFlags.graphqlReleaseData && - rootState.featureFlags.graphqlReleasesPage && - rootState.featureFlags.graphqlMilestoneStats - ) { - gqClient - .query({ - query: allReleasesQuery, - variables: { - fullPath: state.projectPath, - }, - }) - .then(response => { - dispatch('receiveReleasesSuccess', convertGraphQLResponse(response)); - }) - .catch(() => dispatch('receiveReleasesError')); + 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 { - api - .releases(state.projectId, { page }) - .then(response => dispatch('receiveReleasesSuccess', response)) - .catch(() => dispatch('receiveReleasesError')); + throw new Error( + 'Both a `before` and an `after` parameter were provided to fetchReleasesGraphQl. These parameters cannot be used together.', + ); } + + gqClient + .query({ + query: allReleasesQuery, + variables: { + fullPath: state.projectPath, + ...paginationParams, + }, + }) + .then(response => { + const { data, paginationInfo: graphQlPageInfo } = convertAllReleasesGraphQLResponse(response); + + commit(types.RECEIVE_RELEASES_SUCCESS, { + data, + graphQlPageInfo, + }); + }) + .catch(() => dispatch('receiveReleasesError')); }; -export const receiveReleasesSuccess = ({ commit }, { data, headers }) => { - const pageInfo = parseIntPagination(normalizeHeaders(headers)); - const camelCasedReleases = convertObjectPropsToCamelCase(data, { deep: true }); - commit(types.RECEIVE_RELEASES_SUCCESS, { - data: camelCasedReleases, - pageInfo, - }); +/** + * Gets a paginated list of releases from the REST endpoint + */ +export const fetchReleasesRest = ({ dispatch, commit, state }, { page }) => { + commit(types.REQUEST_RELEASES); + + api + .releases(state.projectId, { page }) + .then(({ data, headers }) => { + const restPageInfo = parseIntPagination(normalizeHeaders(headers)); + const camelCasedReleases = convertObjectPropsToCamelCase(data, { deep: true }); + + commit(types.RECEIVE_RELEASES_SUCCESS, { + data: camelCasedReleases, + restPageInfo, + }); + }) + .catch(() => dispatch('receiveReleasesError')); }; export const receiveReleasesError = ({ commit }) => { diff --git a/app/assets/javascripts/releases/stores/modules/list/mutations.js b/app/assets/javascripts/releases/stores/modules/list/mutations.js index 99fc096264a..296487cfee2 100644 --- a/app/assets/javascripts/releases/stores/modules/list/mutations.js +++ b/app/assets/javascripts/releases/stores/modules/list/mutations.js @@ -17,11 +17,12 @@ export default { * @param {Object} state * @param {Object} resp */ - [types.RECEIVE_RELEASES_SUCCESS](state, { data, pageInfo }) { + [types.RECEIVE_RELEASES_SUCCESS](state, { data, restPageInfo, graphQlPageInfo }) { state.hasError = false; state.isLoading = false; state.releases = data; - state.pageInfo = pageInfo; + state.restPageInfo = restPageInfo; + state.graphQlPageInfo = graphQlPageInfo; }, /** @@ -35,5 +36,7 @@ export default { state.isLoading = false; state.releases = []; state.hasError = true; + state.restPageInfo = {}; + state.graphQlPageInfo = {}; }, }; diff --git a/app/assets/javascripts/releases/stores/modules/list/state.js b/app/assets/javascripts/releases/stores/modules/list/state.js index 9fe313745fc..0bffaa0f9db 100644 --- a/app/assets/javascripts/releases/stores/modules/list/state.js +++ b/app/assets/javascripts/releases/stores/modules/list/state.js @@ -14,5 +14,6 @@ export default ({ isLoading: false, hasError: false, releases: [], - pageInfo: {}, + restPageInfo: {}, + graphQlPageInfo: {}, }); diff --git a/app/assets/javascripts/releases/util.js b/app/assets/javascripts/releases/util.js index d7fac7a9b65..445c429fd96 100644 --- a/app/assets/javascripts/releases/util.js +++ b/app/assets/javascripts/releases/util.js @@ -107,7 +107,24 @@ const convertMilestones = graphQLRelease => ({ }); /** - * Converts the response from the GraphQL endpoint into the + * Converts a single release object fetched from GraphQL + * into a release object that matches the shape of the REST API + * (the same shape that is returned by `apiJsonToRelease` above.) + * + * @param graphQLRelease The release object returned from a GraphQL query + */ +export const convertGraphQLRelease = graphQLRelease => ({ + ...convertScalarProperties(graphQLRelease), + ...convertAssets(graphQLRelease), + ...convertEvidences(graphQLRelease), + ...convertLinks(graphQLRelease), + ...convertCommit(graphQLRelease), + ...convertAuthor(graphQLRelease), + ...convertMilestones(graphQLRelease), +}); + +/** + * Converts the response from all_releases.query.graphql into the * same shape as is returned from the Releases REST API. * * This allows the release components to use the response @@ -115,16 +132,27 @@ const convertMilestones = graphQLRelease => ({ * * @param response The response received from the GraphQL endpoint */ -export const convertGraphQLResponse = response => { - const releases = response.data.project.releases.nodes.map(r => ({ - ...convertScalarProperties(r), - ...convertAssets(r), - ...convertEvidences(r), - ...convertLinks(r), - ...convertCommit(r), - ...convertAuthor(r), - ...convertMilestones(r), - })); - - return { data: releases }; +export const convertAllReleasesGraphQLResponse = response => { + const releases = response.data.project.releases.nodes.map(convertGraphQLRelease); + + const paginationInfo = { + ...response.data.project.releases.pageInfo, + }; + + return { data: releases, paginationInfo }; +}; + +/** + * Converts the response from one_release.query.graphql into the + * same shape as is returned from the Releases REST API. + * + * This allows the release components to use the response + * from either endpoint interchangeably. + * + * @param response The response received from the GraphQL endpoint + */ +export const convertOneReleaseGraphQLResponse = response => { + const release = convertGraphQLRelease(response.data.project.release); + + return { data: release }; }; |