Skip to content
Snippets Groups Projects
Unverified Commit c074d05e authored by Rajendra Kadam's avatar Rajendra Kadam Committed by GitLab
Browse files

Merge branch 'bs-release-catalog-badges' into 'master'

Catalog release badges Part 2 - Add CI/CD Catalog badges to releases

See merge request https://gitlab.com/gitlab-org/gitlab/-/merge_requests/167944



Merged-by: default avatarRajendra Kadam <rkadam@gitlab.com>
Approved-by: default avatarSunjung Park <spark@gitlab.com>
Approved-by: default avatarRajendra Kadam <rkadam@gitlab.com>
Approved-by: default avatarArtur Fedorov <afedorov@gitlab.com>
Reviewed-by: default avatarJannik Lehmann <jlehmann@gitlab.com>
Reviewed-by: default avatarRajendra Kadam <rkadam@gitlab.com>
Reviewed-by: default avatarArtur Fedorov <afedorov@gitlab.com>
Co-authored-by: default avatarBriley Sandlin <bsandlin@gitlab.com>
parents 472e70bb 6f0c9131
No related branches found
No related tags found
No related merge requests found
Loading
Loading
@@ -2,6 +2,7 @@
import { s__ } from '~/locale';
import { createAlert } from '~/alert';
import getCiCatalogSettingsQuery from '~/ci/catalog/graphql/queries/get_ci_catalog_settings.query.graphql';
import catalogReleasesQuery from '../graphql/queries/catalog_releases.query.graphql';
 
/**
* Renderless component that wraps GraphQL queries for CI/CD Catalog information
Loading
Loading
@@ -13,9 +14,10 @@ import getCiCatalogSettingsQuery from '~/ci/catalog/graphql/queries/get_ci_catal
*
* ```vue
* <ci-cd-catalog-wrapper
* #default="{ isCiCdCatalogProject }"
* #default="{ isCatalogRelease, detailsPagePath }"
* :release-path="release.tagPath"
* >
* <gl-button :disabled="isCiCdCatalogProject">{{ __('New release') }}</gl-button>
* <gl-badge v-if="isCatalogRelease" :href="detailsPagePath">{{ __('CI/CD Catalog') }}</gl-badge>
* </ci-cd-catalog-wrapper>
* ```
*
Loading
Loading
@@ -27,10 +29,21 @@ export default {
catalogResourceQueryError: s__(
'CiCatalog|There was a problem fetching the CI/CD Catalog setting.',
),
catalogReleasesQueryError: s__(
'CiCatalog|There was a problem fetching the CI/CD Catalog releases.',
),
},
inject: ['projectPath'],
props: {
releasePath: {
type: String,
required: false,
default: '',
},
},
data() {
return {
catalogReleases: [],
isCiCdCatalogProject: false,
};
},
Loading
Loading
@@ -49,10 +62,37 @@ export default {
createAlert({ message: this.$options.i18n.catalogResourceQueryError });
},
},
catalogReleases: {
query: catalogReleasesQuery,
variables() {
return {
fullPath: this.projectPath,
};
},
skip() {
return !this.isCiCdCatalogProject;
},
update({ ciCatalogResource }) {
return ciCatalogResource?.versions?.nodes.map((version) => version.path) || [];
},
error() {
createAlert({ message: this.$options.i18n.catalogReleasesQueryError });
},
},
},
computed: {
detailsPagePath() {
return this.isCatalogRelease ? `/explore/catalog/${this.projectPath}` : '';
},
isCatalogRelease() {
return this.isCiCdCatalogProject ? this.catalogReleases?.includes(this.releasePath) : false;
},
},
render() {
return this.$scopedSlots.default({
isCatalogRelease: this.isCatalogRelease,
isCiCdCatalogProject: this.isCiCdCatalogProject,
detailsPagePath: this.detailsPagePath,
});
},
};
Loading
Loading
<script>
import { GlTooltipDirective, GlLink, GlBadge, GlIcon } from '@gitlab/ui';
import { __ } from '~/locale';
import CiCdCatalogWrapper from './ci_cd_catalog_wrapper.vue';
 
export default {
name: 'ReleaseBlockTitle',
i18n: {
historical: __('Historical release'),
historicalTooltip: __(
'This release was created with a date in the past. Evidence collection at the moment of the release is unavailable.',
),
},
name: 'ReleaseBlockHeader',
components: {
GlLink,
GlBadge,
GlIcon,
CiCdCatalogWrapper,
},
directives: {
GlTooltip: GlTooltipDirective,
Loading
Loading
@@ -34,7 +36,7 @@ export default {
 
<template>
<div class="gl-contents">
<gl-link v-if="selfLink" class="gl-text-default" :href="selfLink">
<gl-link v-if="selfLink" class="gl-self-center gl-text-default" :href="selfLink">
{{ release.name }}
</gl-link>
<template v-else>
Loading
Loading
@@ -50,14 +52,24 @@ export default {
class="gl-text-secondary"
/>
</template>
<gl-badge v-if="release.upcomingRelease" variant="warning" class="gl-self-center">{{
__('Upcoming Release')
}}</gl-badge>
<ci-cd-catalog-wrapper :release-path="release.tagPath">
<template #default="{ isCatalogRelease, detailsPagePath }">
<gl-badge
v-if="isCatalogRelease"
:href="detailsPagePath"
variant="info"
data-testid="catalog-badge"
>{{ __('CI/CD Catalog') }}</gl-badge
>
</template>
</ci-cd-catalog-wrapper>
<gl-badge v-if="release.upcomingRelease" variant="warning">
{{ __('Upcoming Release') }}
</gl-badge>
<gl-badge
v-else-if="release.historicalRelease"
v-gl-tooltip
:title="$options.i18n.historicalTooltip"
class="gl-self-center"
>
{{ $options.i18n.historical }}
</gl-badge>
Loading
Loading
query catalogReleases($fullPath: ID!) {
ciCatalogResource(fullPath: $fullPath) {
id
versions {
nodes {
id
path
}
}
}
}
Loading
Loading
@@ -11761,6 +11761,9 @@ msgstr ""
msgid "CiCatalog|The project will be findable in the CI/CD Catalog after the project has at least one release."
msgstr ""
 
msgid "CiCatalog|There was a problem fetching the CI/CD Catalog releases."
msgstr ""
msgid "CiCatalog|There was a problem fetching the CI/CD Catalog setting."
msgstr ""
 
import Vue from 'vue';
import VueApollo from 'vue-apollo';
import { shallowMount } from '@vue/test-utils';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import createMockApollo from 'helpers/mock_apollo_helper';
import waitForPromises from 'helpers/wait_for_promises';
 
Loading
Loading
@@ -8,7 +9,8 @@ import { createAlert } from '~/alert';
import CiCdCatalogWrapper from '~/releases/components/ci_cd_catalog_wrapper.vue';
 
import getCiCatalogSettingsQuery from '~/ci/catalog/graphql/queries/get_ci_catalog_settings.query.graphql';
import { generateCatalogSettingsResponse } from '../mock_data';
import catalogReleasesQuery from '~/releases/graphql/queries/catalog_releases.query.graphql';
import { catalogReleasesResponse, generateCatalogSettingsResponse } from '../mock_data';
 
Vue.use(VueApollo);
jest.mock('~/alert');
Loading
Loading
@@ -16,27 +18,39 @@ jest.mock('~/alert');
describe('CiCdCatalogWrapper', () => {
let wrapper;
let catalogSettingsHandler;
let catalogReleasesHandler;
const defaultProps = {
releasePath: '/root/project/-/tags/1.0.7',
};
const projectPath = 'project/path';
 
const createComponent = async ({ scopedSlots } = {}) => {
const handlers = [[getCiCatalogSettingsQuery, catalogSettingsHandler]];
const createComponent = async ({ props = {}, scopedSlots = { default: `<div></div>` } } = {}) => {
const handlers = [
[getCiCatalogSettingsQuery, catalogSettingsHandler],
[catalogReleasesQuery, catalogReleasesHandler],
];
 
wrapper = shallowMount(CiCdCatalogWrapper, {
wrapper = shallowMountExtended(CiCdCatalogWrapper, {
apolloProvider: createMockApollo(handlers),
provide: {
projectPath: 'project/path',
propsData: {
...defaultProps,
...props,
},
provide: { projectPath },
scopedSlots,
});
 
await waitForPromises();
};
 
const findButton = () => wrapper.find('button');
const findReleaseButton = () => wrapper.findByTestId('release-button');
const findSettingsButton = () => wrapper.findByTestId('settings-button');
 
describe('settings data', () => {
const scopedSlots = {
default: `
<button :disabled="props.isCiCdCatalogProject">New release</button>
<button data-testid="settings-button" :disabled="props.isCiCdCatalogProject">Depends on setting</button>
`,
};
 
Loading
Loading
@@ -51,42 +65,116 @@ describe('CiCdCatalogWrapper', () => {
});
 
it('renders button', () => {
expect(findButton().exists()).toBe(true);
expect(findSettingsButton().exists()).toBe(true);
});
describe('when project is not a CI/CD Catalog project', () => {
beforeEach(async () => {
catalogSettingsHandler = jest.fn().mockResolvedValue(generateCatalogSettingsResponse);
await createComponent({ scopedSlots });
});
it('renders button as enabled', () => {
expect(findSettingsButton().attributes('disabled')).toBe(undefined);
});
});
describe('when project is a CI/CD Catalog project', () => {
beforeEach(async () => {
catalogSettingsHandler = jest
.fn()
.mockResolvedValue(generateCatalogSettingsResponse(true));
await createComponent({ scopedSlots });
});
it('renders button as disabled', () => {
expect(findSettingsButton().attributes('disabled')).toBe('disabled');
});
});
describe('when settings query fails', () => {
beforeEach(async () => {
catalogSettingsHandler = jest.fn().mockRejectedValue();
await createComponent({ scopedSlots });
});
it('calls createAlert with the correct message', () => {
expect(createAlert).toHaveBeenCalled();
expect(createAlert).toHaveBeenCalledWith({
message: 'There was a problem fetching the CI/CD Catalog setting.',
});
});
});
});
});
describe('release data', () => {
const scopedSlots = {
default: `
<button data-testid="release-button" :href="props.detailsPagePath">{{props.isCatalogRelease}}</button>
`,
};
 
describe('when project is not a CI/CD Catalog project', () => {
beforeEach(async () => {
catalogSettingsHandler = jest.fn().mockResolvedValue(generateCatalogSettingsResponse);
catalogSettingsHandler = jest.fn().mockResolvedValue(generateCatalogSettingsResponse());
catalogReleasesHandler = jest.fn().mockResolvedValue(catalogReleasesResponse);
await createComponent({ scopedSlots });
});
 
it('renders button as enabled', () => {
expect(findButton().attributes('disabled')).toBe(undefined);
it('does not fire the catalog releases query', () => {
expect(catalogReleasesHandler).not.toHaveBeenCalled();
});
});
 
describe('when project is a CI/CD Catalog project', () => {
beforeEach(async () => {
catalogSettingsHandler = jest.fn().mockResolvedValue(generateCatalogSettingsResponse(true));
catalogReleasesHandler = await jest.fn().mockResolvedValue(catalogReleasesResponse);
await createComponent({ scopedSlots });
});
 
it('renders button as disabled', () => {
expect(findButton().attributes('disabled')).toBe('disabled');
it('fires the catalog releases query', () => {
expect(catalogReleasesHandler).toHaveBeenCalledTimes(1);
});
describe('when release is published in the catalog', () => {
it('sets isCatalogRelease to true', () => {
expect(findReleaseButton().text()).toBe('true');
});
it('assigns project path to button', () => {
expect(findReleaseButton().attributes('href')).toBe(`/explore/catalog/${projectPath}`);
});
});
});
describe('when release is not published in the catalog', () => {
beforeEach(async () => {
await createComponent({ props: { releasePath: '/not/included' }, scopedSlots });
});
it('sets isCatalogRelease to false', () => {
expect(findReleaseButton().text()).toBe('false');
});
it('does not assign project path to button', () => {
expect(findReleaseButton().attributes('href')).toBe('');
});
});
 
describe('when settings query fails', () => {
describe('when releases query fails', () => {
beforeEach(async () => {
catalogSettingsHandler = jest.fn().mockRejectedValue();
await createComponent({ scopedSlots });
catalogReleasesHandler = jest.fn().mockRejectedValue();
await createComponent();
});
 
it('calls createAlert with the correct message', () => {
expect(createAlert).toHaveBeenCalled();
expect(createAlert).toHaveBeenCalledWith({
message: 'There was a problem fetching the CI/CD Catalog setting.',
message: 'There was a problem fetching the CI/CD Catalog releases.',
});
});
});
Loading
Loading
import { GlLink, GlBadge } from '@gitlab/ui';
import { merge } from 'lodash';
import originalRelease from 'test_fixtures/api/releases/release.json';
import { stubComponent } from 'helpers/stub_component';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
import CiCdCatalogWrapper from '~/releases/components/ci_cd_catalog_wrapper.vue';
import ReleaseBlockTitle from '~/releases/components/release_block_title.vue';
 
describe('Release block header', () => {
describe('ReleaseBlockTitle', () => {
let wrapper;
let release;
 
const factory = (releaseUpdates = {}) => {
const detailsPagePath = '/path';
const createComponent = ({ isCatalogRelease = false, releaseUpdates = {} } = {}) => {
wrapper = shallowMountExtended(ReleaseBlockTitle, {
propsData: {
release: merge({}, release, releaseUpdates),
},
stubs: { GlBadge },
stubs: {
CiCdCatalogWrapper: {
...stubComponent(CiCdCatalogWrapper),
render() {
return this.$scopedSlots.default({
detailsPagePath,
isCatalogRelease,
});
},
},
GlBadge,
},
});
};
 
Loading
Loading
@@ -22,13 +37,14 @@ describe('Release block header', () => {
release = convertObjectPropsToCamelCase(originalRelease, { deep: true });
});
 
const findPlainHeader = () => wrapper.findByTestId('release-block-title');
const findHeaderLink = () => wrapper.findComponent(GlLink);
const findBadge = () => wrapper.findComponent(GlBadge);
const findCatalogBadge = () => wrapper.findByTestId('catalog-badge');
const findHeaderLink = () => wrapper.findComponent(GlLink);
const findPlainHeader = () => wrapper.findByTestId('release-block-title');
 
describe('when _links.self is provided', () => {
beforeEach(() => {
factory();
createComponent();
});
 
it('renders the title as a link', () => {
Loading
Loading
@@ -45,7 +61,7 @@ describe('Release block header', () => {
 
describe('when _links.self is missing', () => {
beforeEach(() => {
factory({ _links: { self: null } });
createComponent({ releaseUpdates: { _links: { self: null } } });
});
 
it('renders a plain header', () => {
Loading
Loading
@@ -56,7 +72,7 @@ describe('Release block header', () => {
 
describe('upcoming release', () => {
beforeEach(() => {
factory({ upcomingRelease: true, historicalRelease: false });
createComponent({ releaseUpdates: { upcomingRelease: true, historicalRelease: false } });
});
 
it('shows a badge that the release is upcoming', () => {
Loading
Loading
@@ -69,7 +85,7 @@ describe('Release block header', () => {
 
describe('historical release', () => {
beforeEach(() => {
factory({ upcomingRelease: false, historicalRelease: true });
createComponent({ releaseUpdates: { upcomingRelease: false, historicalRelease: true } });
});
 
it('shows a badge that the release is historical', () => {
Loading
Loading
@@ -81,4 +97,30 @@ describe('Release block header', () => {
);
});
});
describe('ci/cd catalog badge', () => {
describe('when the release is not a catalog release', () => {
beforeEach(() => {
createComponent();
});
it('does not render a catalog badge', () => {
expect(findCatalogBadge().exists()).toBe(false);
});
});
describe('when the release is a catalog release', () => {
beforeEach(() => {
createComponent({ isCatalogRelease: true });
});
it('renders a catalog badge', () => {
expect(findCatalogBadge().exists()).toBe(true);
});
it('assigns the correct href to the badge', () => {
expect(findCatalogBadge().attributes('href')).toBe(detailsPagePath);
});
});
});
});
Loading
Loading
@@ -26,3 +26,27 @@ export const generateCatalogSettingsResponse = (isCatalogResource = false) => {
},
};
};
export const catalogReleasesResponse = {
data: {
ciCatalogResource: {
id: 'gid://gitlab/Ci::Catalog::Resource/39',
versions: {
nodes: [
{
id: 'gid://gitlab/Ci::Catalog::Resources::Version/13',
path: '/root/project/-/tags/1.0.7',
__typename: 'CiCatalogResourceVersion',
},
{
id: 'gid://gitlab/Ci::Catalog::Resources::Version/12',
path: '/root/project/-/tags/1.0.6',
__typename: 'CiCatalogResourceVersion',
},
],
__typename: 'CiCatalogResourceVersionConnection',
},
__typename: 'CiCatalogResource',
},
},
};
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment