Skip to content
代码片段 群组 项目
提交 2c139d79 编辑于 作者: Alexander Turinske's avatar Alexander Turinske
浏览文件

Merge branch...

Merge branch '431408-frontend-cmd-click-does-not-open-catalog-resource-page-in-separate-tab' into 'master' 

Add option to open catalog resource details in separate tab

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



Merged-by: default avatarAlexander Turinske <aturinske@gitlab.com>
Approved-by: default avatarBriley Sandlin <bsandlin@gitlab.com>
Approved-by: default avatarJose Ivan Vargas <jvargas@gitlab.com>
Approved-by: default avatarAlexander Turinske <aturinske@gitlab.com>
Co-authored-by: default avatarFrédéric Caplette <fcaplette@gitlab.com>
No related branches found
No related tags found
无相关合并请求
<script> <script>
import { import { GlAvatar, GlBadge, GlIcon, GlLink, GlSprintf, GlTooltipDirective } from '@gitlab/ui';
GlAvatar,
GlBadge,
GlButton,
GlIcon,
GlLink,
GlSprintf,
GlTooltipDirective,
} from '@gitlab/ui';
import { s__ } from '~/locale'; import { s__ } from '~/locale';
import { getIdFromGraphQLId } from '~/graphql_shared/utils'; import { getIdFromGraphQLId } from '~/graphql_shared/utils';
import { formatDate, getTimeago } from '~/lib/utils/datetime_utility'; import { formatDate, getTimeago } from '~/lib/utils/datetime_utility';
...@@ -21,7 +13,6 @@ export default { ...@@ -21,7 +13,6 @@ export default {
components: { components: {
GlAvatar, GlAvatar,
GlBadge, GlBadge,
GlButton,
GlIcon, GlIcon,
GlLink, GlLink,
GlSprintf, GlSprintf,
...@@ -42,6 +33,12 @@ export default { ...@@ -42,6 +33,12 @@ export default {
authorProfileUrl() { authorProfileUrl() {
return this.latestVersion.author.webUrl; return this.latestVersion.author.webUrl;
}, },
detailsPageHref() {
return this.$router.resolve({
name: CI_RESOURCE_DETAILS_PAGE_NAME,
params: { id: this.entityId },
}).href;
},
entityId() { entityId() {
return getIdFromGraphQLId(this.resource.id); return getIdFromGraphQLId(this.resource.id);
}, },
...@@ -68,7 +65,16 @@ export default { ...@@ -68,7 +65,16 @@ export default {
}, },
}, },
methods: { methods: {
navigateToDetailsPage() { navigateToDetailsPage(e) {
// Open link in a new tab if any of these modifier key is held down.
if (e?.ctrlKey || e?.metaKey) {
return;
}
// Override the <a> tag if no modifier key is held down to use Vue router and not
// open a new tab.
e.preventDefault();
this.$router.push({ this.$router.push({
name: CI_RESOURCE_DETAILS_PAGE_NAME, name: CI_RESOURCE_DETAILS_PAGE_NAME,
params: { id: this.entityId }, params: { id: this.entityId },
...@@ -93,14 +99,14 @@ export default { ...@@ -93,14 +99,14 @@ export default {
/> />
<div class="gl-display-flex gl-flex-direction-column gl-flex-grow-1"> <div class="gl-display-flex gl-flex-direction-column gl-flex-grow-1">
<div class="gl-display-flex gl-flex-wrap gl-gap-2 gl-mb-2"> <div class="gl-display-flex gl-flex-wrap gl-gap-2 gl-mb-2">
<gl-button <gl-link
variant="link"
class="gl-text-gray-900! gl-mr-1" class="gl-text-gray-900! gl-mr-1"
:href="detailsPageHref"
data-testid="ci-resource-link" data-testid="ci-resource-link"
@click="navigateToDetailsPage" @click="navigateToDetailsPage"
> >
{{ resourcePath }} <b> {{ resource.name }}</b> {{ resourcePath }} <b> {{ resource.name }}</b>
</gl-button> </gl-link>
<div class="gl-display-flex gl-flex-grow-1 gl-md-justify-content-space-between"> <div class="gl-display-flex gl-flex-grow-1 gl-md-justify-content-space-between">
<gl-badge size="sm">{{ tagName }}</gl-badge> <gl-badge size="sm">{{ tagName }}</gl-badge>
<span class="gl-display-flex gl-align-items-center gl-ml-5"> <span class="gl-display-flex gl-align-items-center gl-ml-5">
......
import Vue from 'vue'; import Vue from 'vue';
import VueRouter from 'vue-router'; import VueRouter from 'vue-router';
import { GlAvatar, GlBadge, GlButton, GlSprintf } from '@gitlab/ui'; import { GlAvatar, GlBadge, GlSprintf } from '@gitlab/ui';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import { createRouter } from '~/ci/catalog/router/index'; import { createRouter } from '~/ci/catalog/router/index';
import CiResourcesListItem from '~/ci/catalog/components/list/ci_resources_list_item.vue'; import CiResourcesListItem from '~/ci/catalog/components/list/ci_resources_list_item.vue';
...@@ -10,6 +10,7 @@ import { catalogSinglePageResponse } from '../../mock'; ...@@ -10,6 +10,7 @@ import { catalogSinglePageResponse } from '../../mock';
Vue.use(VueRouter); Vue.use(VueRouter);
const defaultEvent = { preventDefault: jest.fn, ctrlKey: false, metaKey: false };
let router; let router;
let routerPush; let routerPush;
...@@ -43,7 +44,7 @@ describe('CiResourcesListItem', () => { ...@@ -43,7 +44,7 @@ describe('CiResourcesListItem', () => {
const findAvatar = () => wrapper.findComponent(GlAvatar); const findAvatar = () => wrapper.findComponent(GlAvatar);
const findBadge = () => wrapper.findComponent(GlBadge); const findBadge = () => wrapper.findComponent(GlBadge);
const findResourceName = () => wrapper.findComponent(GlButton); const findResourceName = () => wrapper.findByTestId('ci-resource-link');
const findResourceDescription = () => wrapper.findByText(defaultProps.resource.description); const findResourceDescription = () => wrapper.findByText(defaultProps.resource.description);
const findUserLink = () => wrapper.findByTestId('user-link'); const findUserLink = () => wrapper.findByTestId('user-link');
const findTimeAgoMessage = () => wrapper.findComponent(GlSprintf); const findTimeAgoMessage = () => wrapper.findComponent(GlSprintf);
...@@ -70,8 +71,11 @@ describe('CiResourcesListItem', () => { ...@@ -70,8 +71,11 @@ describe('CiResourcesListItem', () => {
}); });
}); });
it('renders the resource name button', () => { it('renders the resource name and link', () => {
expect(findResourceName().exists()).toBe(true); expect(findResourceName().exists()).toBe(true);
expect(findResourceName().attributes().href).toBe(
`/${getIdFromGraphQLId(defaultProps.resource.id)}`,
);
}); });
it('renders the resource version badge', () => { it('renders the resource version badge', () => {
...@@ -121,18 +125,37 @@ describe('CiResourcesListItem', () => { ...@@ -121,18 +125,37 @@ describe('CiResourcesListItem', () => {
}); });
describe('when clicking on an item title', () => { describe('when clicking on an item title', () => {
beforeEach(async () => { beforeEach(() => {
createComponent(); createComponent();
});
describe('without holding down a modifier key', () => {
beforeEach(async () => {
await findResourceName().vm.$emit('click', defaultEvent);
});
await findResourceName().vm.$emit('click'); it('navigates to the details page in the same tab', () => {
expect(routerPush).toHaveBeenCalledWith({
name: CI_RESOURCE_DETAILS_PAGE_NAME,
params: {
id: getIdFromGraphQLId(resource.id),
},
});
});
}); });
it('navigates to the details page', () => { describe.each`
expect(routerPush).toHaveBeenCalledWith({ keyName
name: CI_RESOURCE_DETAILS_PAGE_NAME, ${'ctrlKey'}
params: { ${'metaKey'}
id: getIdFromGraphQLId(resource.id), `('when $keyName is being held down', ({ keyName }) => {
}, beforeEach(async () => {
createComponent();
await findResourceName().vm.$emit('click', { ...defaultEvent, [keyName]: true });
});
it('does not call VueRouter push', () => {
expect(routerPush).not.toHaveBeenCalled();
}); });
}); });
}); });
...@@ -141,7 +164,7 @@ describe('CiResourcesListItem', () => { ...@@ -141,7 +164,7 @@ describe('CiResourcesListItem', () => {
beforeEach(async () => { beforeEach(async () => {
createComponent(); createComponent();
await findAvatar().vm.$emit('click'); await findAvatar().vm.$emit('click', defaultEvent);
}); });
it('navigates to the details page', () => { it('navigates to the details page', () => {
...@@ -160,6 +183,7 @@ describe('CiResourcesListItem', () => { ...@@ -160,6 +183,7 @@ describe('CiResourcesListItem', () => {
createComponent({ createComponent({
props: { props: {
resource: { resource: {
...resource,
starCount: 0, starCount: 0,
}, },
}, },
......
0% 加载中 .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册