Commit 6d499a19 authored by David O'Regan's avatar David O'Regan

Merge branch '341370-dast-view-scans-truncate-values-gl-truncate' into 'master'

Truncate long table values in on-demand scans page

See merge request gitlab-org/gitlab!73641
parents 9bf98911 8f1a83be
......@@ -12,6 +12,7 @@ export default {
{
label: __('Status'),
key: 'detailedStatus',
columnClass: 'gl-w-15',
},
{
label: __('Name'),
......@@ -20,6 +21,7 @@ export default {
{
label: s__('OnDemandScans|Scan type'),
key: 'scanType',
columnClass: 'gl-w-13',
},
{
label: s__('OnDemandScans|Target'),
......@@ -28,10 +30,12 @@ export default {
{
label: __('Start date'),
key: 'createdAt',
columnClass: 'gl-w-15',
},
{
label: __('Pipeline'),
key: 'id',
columnClass: 'gl-w-13',
},
],
i18n: {
......
......@@ -7,6 +7,7 @@ import {
GlKeysetPagination,
GlAlert,
GlSkeletonLoader,
GlTruncate,
} from '@gitlab/ui';
import CiBadgeLink from '~/vue_shared/components/ci_badge_link.vue';
import TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
......@@ -36,6 +37,7 @@ export default {
GlKeysetPagination,
GlAlert,
GlSkeletonLoader,
GlTruncate,
CiBadgeLink,
TimeAgoTooltip,
EmptyState,
......@@ -122,9 +124,8 @@ export default {
return this.pipelines?.pageInfo;
},
tableFields() {
return this.fields.map(({ key, label }) => ({
key,
label,
return this.fields.map((field) => ({
...field,
class: ['gl-text-black-normal'],
thClass: ['gl-bg-transparent!', 'gl-white-space-nowrap'],
}));
......@@ -188,7 +189,12 @@ export default {
:items="pipelineNodes"
:busy="$apollo.queries.pipelines.loading"
stacked="md"
fixed
>
<template #table-colgroup="scope">
<col v-for="field in scope.fields" :key="field.key" :class="field.columnClass" />
</template>
<template #table-busy>
<gl-skeleton-loader v-for="i in 20" :key="i" :width="1000" :height="45">
<rect width="85" height="20" x="0" y="5" rx="4" />
......@@ -205,12 +211,30 @@ export default {
</div>
</template>
<!-- eslint-disable-next-line vue/valid-v-slot -->
<template #cell(dastProfile.name)="{ item }">
<gl-truncate v-if="item.dastProfile" :text="item.dastProfile.name" with-tooltip />
</template>
<template #cell(scanType)>
{{ $options.DAST_SHORT_NAME }}
</template>
<!-- eslint-disable-next-line vue/valid-v-slot -->
<template #cell(dastProfile.dastSiteProfile.targetUrl)="{ item }">
<gl-truncate
v-if="item.dastProfile"
:text="item.dastProfile.dastSiteProfile.targetUrl"
with-tooltip
/>
</template>
<template #cell(createdAt)="{ item }">
<time-ago-tooltip v-if="item.createdAt" :time="item.createdAt" tooltip-placement="left" />
<time-ago-tooltip
v-if="item.createdAt"
class="gl-white-space-nowrap"
:time="item.createdAt"
/>
</template>
<template #cell(id)="{ item }">
......
......@@ -15,6 +15,7 @@ RSpec.describe 'On-demand DAST scans (GraphQL fixtures)' do
path = 'on_demand_scans/graphql/on_demand_scans.query.graphql'
before do
stub_licensed_features(security_on_demand_scans: true)
project.add_developer(current_user)
end
......
......@@ -3,6 +3,7 @@
exports[`AllTab renders the base tab with the correct props 1`] = `
Array [
Object {
"columnClass": "gl-w-15",
"key": "detailedStatus",
"label": "Status",
},
......@@ -11,6 +12,7 @@ Array [
"label": "Name",
},
Object {
"columnClass": "gl-w-13",
"key": "scanType",
"label": "Scan type",
},
......@@ -19,10 +21,12 @@ Array [
"label": "Target",
},
Object {
"columnClass": "gl-w-15",
"key": "createdAt",
"label": "Start date",
},
Object {
"columnClass": "gl-w-13",
"key": "id",
"label": "Pipeline",
},
......
import { GlTab, GlTable, GlAlert } from '@gitlab/ui';
import { createLocalVue } from '@vue/test-utils';
import VueApollo from 'vue-apollo';
import { merge } from 'lodash';
import allPipelinesWithPipelinesMock from 'test_fixtures/graphql/on_demand_scans/graphql/on_demand_scans.query.graphql.with_pipelines.json';
import allPipelinesWithoutPipelinesMock from 'test_fixtures/graphql/on_demand_scans/graphql/on_demand_scans.query.graphql.without_pipelines.json';
import { stubComponent } from 'helpers/stub_component';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import { mountExtended, shallowMountExtended } from 'helpers/vue_test_utils_helper';
import BaseTab from 'ee/on_demand_scans/components/tabs/base_tab.vue';
import EmptyState from 'ee/on_demand_scans/components/empty_state.vue';
import createMockApollo from 'helpers/mock_apollo_helper';
......@@ -13,6 +14,7 @@ import { createRouter } from 'ee/on_demand_scans/router';
import waitForPromises from 'helpers/wait_for_promises';
import { scrollToElement } from '~/lib/utils/common_utils';
import setWindowLocation from 'helpers/set_window_location_helper';
import { getIdFromGraphQLId } from '~/graphql_shared/utils';
jest.mock('~/lib/utils/common_utils');
......@@ -44,25 +46,52 @@ describe('BaseTab', () => {
return wrapper.vm.$nextTick();
};
const createComponent = (propsData) => {
const createComponentFactory = (mountFn = shallowMountExtended) => (options = {}) => {
router = createRouter();
wrapper = shallowMountExtended(BaseTab, {
localVue,
apolloProvider: createMockApolloProvider(),
router,
propsData: {
title: 'All',
query: onDemandScansQuery,
itemsCount: 0,
fields: [{ name: 'ID', key: 'id' }],
...propsData,
},
provide: {
projectPath,
},
stubs: {
GlTab: stubComponent(GlTab, {
template: `
wrapper = mountFn(
BaseTab,
merge(
{
localVue,
apolloProvider: createMockApolloProvider(),
router,
propsData: {
title: 'All',
query: onDemandScansQuery,
itemsCount: 0,
fields: [
{
label: 'Status',
key: 'detailedStatus',
},
{
label: 'Name',
key: 'dastProfile.name',
},
{
label: 'OnDemandScans|Scan type',
key: 'scanType',
},
{
label: 'OnDemandScans|Target',
key: 'dastProfile.dastSiteProfile.targetUrl',
},
{
label: 'Start date',
key: 'createdAt',
},
{
label: 'Pipeline',
key: 'id',
},
],
},
provide: {
projectPath,
},
stubs: {
GlTab: stubComponent(GlTab, {
template: `
<div>
<span data-testid="tab-title">
<slot name="title" />
......@@ -70,14 +99,20 @@ describe('BaseTab', () => {
<slot />
</div>
`,
}),
GlTable: stubComponent(GlTable, {
props: ['items', 'busy'],
}),
},
});
}),
GlTable: stubComponent(GlTable, {
props: ['items', 'busy'],
}),
},
},
options,
),
);
};
const createComponent = createComponentFactory();
const createFullComponent = createComponentFactory(mountExtended);
beforeEach(() => {
requestHandler = jest.fn().mockResolvedValue(allPipelinesWithPipelinesMock);
});
......@@ -127,11 +162,13 @@ describe('BaseTab', () => {
describe('when there are pipelines', () => {
beforeEach(() => {
createComponent({
itemsCount: 30,
propsData: {
itemsCount: 30,
},
});
});
it('renders the title with the item count', () => {
it('renders the title with the item count', async () => {
expect(findTitle().text()).toMatchInterpolatedText('All 30');
});
......@@ -171,6 +208,66 @@ describe('BaseTab', () => {
});
});
describe('rendered cells', () => {
const [firstPipeline] = allPipelinesWithPipelinesMock.data.project.pipelines.nodes;
const findFirstRow = () => wrapper.find('tbody > tr');
const findCellAt = (index) => findFirstRow().findAll('td').at(index);
beforeEach(() => {
createFullComponent({
propsData: {
itemsCount: 30,
},
stubs: {
GlTable: false,
},
});
});
it('renders the status badge', () => {
const statusCell = findCellAt(0);
expect(statusCell.text()).toBe(firstPipeline.detailedStatus.text);
});
it('renders the name with GlTruncate', () => {
const nameCell = findCellAt(1);
const truncateContainer = nameCell.find('[data-testid="truncate-end-container"]');
expect(truncateContainer.exists()).toBe(true);
expect(truncateContainer.text()).toBe(firstPipeline.dastProfile.name);
});
it('renders the scan type', () => {
const scanTypeCell = findCellAt(2);
expect(scanTypeCell.text()).toBe('DAST');
});
it('renders the target URL with GlTruncate', () => {
const targetUrlCell = findCellAt(3);
const truncateContainer = targetUrlCell.find('[data-testid="truncate-end-container"]');
expect(truncateContainer.exists()).toBe(true);
expect(truncateContainer.text()).toBe(firstPipeline.dastProfile.dastSiteProfile.targetUrl);
});
it('renders the start date as a timeElement', () => {
const startDateCell = findCellAt(4);
const timeElement = startDateCell.find('time');
expect(timeElement.exists()).toBe(true);
expect(timeElement.attributes('datetime')).toBe(firstPipeline.createdAt);
});
it('renders the pipeline ID', () => {
const pipelineIdCell = findCellAt(5);
expect(pipelineIdCell.text()).toBe(`#${getIdFromGraphQLId(firstPipeline.id)}`);
});
});
describe('when there are no pipelines', () => {
beforeEach(() => {
requestHandler = jest.fn().mockResolvedValue(allPipelinesWithoutPipelinesMock);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment