Commit 22c881fa authored by pburdette's avatar pburdette Committed by Payton Burdette

Remove table row and spec

Since we are using GlTable
now we don't need to pipelines
table row component.
parent 7de5845a
......@@ -9,7 +9,6 @@ import PipelineTriggerer from './pipeline_triggerer.vue';
import PipelineUrl from './pipeline_url.vue';
import PipelinesCommit from './pipelines_commit.vue';
import PipelinesStatusBadge from './pipelines_status_badge.vue';
import PipelinesTableRowComponent from './pipelines_table_row.vue';
import PipelinesTimeago from './time_ago.vue';
const DEFAULT_TD_CLASS = 'gl-p-5!';
......@@ -82,7 +81,6 @@ export default {
PipelineOperations,
PipelinesStatusBadge,
PipelineStopModal,
PipelinesTableRowComponent,
PipelinesTimeago,
PipelineTriggerer,
PipelineUrl,
......
<script>
import { GlButton, GlTooltipDirective, GlModalDirective } from '@gitlab/ui';
import { __ } from '~/locale';
import CiBadge from '~/vue_shared/components/ci_badge_link.vue';
import CommitComponent from '~/vue_shared/components/commit.vue';
import eventHub from '../../event_hub';
import PipelineMiniGraph from './pipeline_mini_graph.vue';
import PipelineTriggerer from './pipeline_triggerer.vue';
import PipelineUrl from './pipeline_url.vue';
import PipelinesArtifactsComponent from './pipelines_artifacts.vue';
import PipelinesManualActionsComponent from './pipelines_manual_actions.vue';
import PipelinesTimeago from './time_ago.vue';
export default {
i18n: {
cancelTitle: __('Cancel'),
redeployTitle: __('Retry'),
},
directives: {
GlTooltip: GlTooltipDirective,
GlModalDirective,
},
components: {
PipelinesManualActionsComponent,
PipelinesArtifactsComponent,
CommitComponent,
PipelineMiniGraph,
PipelineUrl,
PipelineTriggerer,
CiBadge,
PipelinesTimeago,
GlButton,
},
props: {
pipeline: {
type: Object,
required: true,
},
pipelineScheduleUrl: {
type: String,
required: false,
default: '',
},
updateGraphDropdown: {
type: Boolean,
required: false,
default: false,
},
viewType: {
type: String,
required: true,
},
cancelingPipeline: {
type: Number,
required: false,
default: null,
},
},
data() {
return {
isRetrying: false,
};
},
computed: {
actions() {
if (!this.pipeline || !this.pipeline.details) {
return [];
}
const { details } = this.pipeline;
return [...(details.manual_actions || []), ...(details.scheduled_actions || [])];
},
/**
* If provided, returns the commit tag.
* Needed to render the commit component column.
*
* This field needs a lot of verification, because of different possible cases:
*
* 1. person who is an author of a commit might be a GitLab user
* 2. if person who is an author of a commit is a GitLab user, they can have a GitLab avatar
* 3. If GitLab user does not have avatar they might have a Gravatar
* 4. If committer is not a GitLab User they can have a Gravatar
* 5. We do not have consistent API object in this case
* 6. We should improve API and the code
*
* @returns {Object|Undefined}
*/
commitAuthor() {
let commitAuthorInformation;
if (!this.pipeline || !this.pipeline.commit) {
return null;
}
// 1. person who is an author of a commit might be a GitLab user
if (this.pipeline.commit.author) {
// 2. if person who is an author of a commit is a GitLab user
// they can have a GitLab avatar
if (this.pipeline.commit.author.avatar_url) {
commitAuthorInformation = this.pipeline.commit.author;
// 3. If GitLab user does not have avatar, they might have a Gravatar
} else if (this.pipeline.commit.author_gravatar_url) {
commitAuthorInformation = {
...this.pipeline.commit.author,
avatar_url: this.pipeline.commit.author_gravatar_url,
};
}
// 4. If committer is not a GitLab User, they can have a Gravatar
} else {
commitAuthorInformation = {
avatar_url: this.pipeline.commit.author_gravatar_url,
path: `mailto:${this.pipeline.commit.author_email}`,
username: this.pipeline.commit.author_name,
};
}
return commitAuthorInformation;
},
commitTag() {
return this.pipeline?.ref?.tag;
},
commitRef() {
return this.pipeline?.ref;
},
commitUrl() {
return this.pipeline?.commit?.commit_path;
},
commitShortSha() {
return this.pipeline?.commit?.short_id;
},
commitTitle() {
return this.pipeline?.commit?.title;
},
pipelineStatus() {
return this.pipeline?.details?.status ?? {};
},
hasStages() {
return this.pipeline?.details?.stages?.length > 0;
},
displayPipelineActions() {
return (
this.pipeline.flags.retryable ||
this.pipeline.flags.cancelable ||
this.pipeline.details.manual_actions.length ||
this.pipeline.details.artifacts.length
);
},
isChildView() {
return this.viewType === 'child';
},
isCancelling() {
return this.cancelingPipeline === this.pipeline.id;
},
},
watch: {
pipeline() {
this.isRetrying = false;
},
},
methods: {
handleCancelClick() {
eventHub.$emit('openConfirmationModal', {
pipeline: this.pipeline,
endpoint: this.pipeline.cancel_path,
});
},
handleRetryClick() {
this.isRetrying = true;
eventHub.$emit('retryPipeline', this.pipeline.retry_path);
},
handlePipelineActionRequestComplete() {
// warn the pipelines table to update
eventHub.$emit('refreshPipelinesTable');
},
},
};
</script>
<template>
<div class="commit gl-responsive-table-row">
<div class="table-section section-10 commit-link">
<div class="table-mobile-header" role="rowheader">{{ s__('Pipeline|Status') }}</div>
<div class="table-mobile-content">
<ci-badge
:status="pipelineStatus"
:show-text="!isChildView"
:icon-classes="'gl-vertical-align-middle!'"
data-qa-selector="pipeline_commit_status"
/>
</div>
</div>
<pipeline-url :pipeline="pipeline" :pipeline-schedule-url="pipelineScheduleUrl" />
<pipeline-triggerer :pipeline="pipeline" />
<div class="table-section section-wrap section-20">
<div class="table-mobile-header" role="rowheader">{{ s__('Pipeline|Commit') }}</div>
<div class="table-mobile-content">
<commit-component
:tag="commitTag"
:commit-ref="commitRef"
:commit-url="commitUrl"
:merge-request-ref="pipeline.merge_request"
:short-sha="commitShortSha"
:title="commitTitle"
:author="commitAuthor"
:show-ref-info="!isChildView"
/>
</div>
</div>
<div class="table-section section-wrap section-15 stage-cell">
<div class="table-mobile-header" role="rowheader">{{ s__('Pipeline|Stages') }}</div>
<div class="table-mobile-content">
<pipeline-mini-graph
v-if="hasStages"
:stages="pipeline.details.stages"
:update-dropdown="updateGraphDropdown"
@pipelineActionRequestComplete="handlePipelineActionRequestComplete"
/>
</div>
</div>
<pipelines-timeago class="gl-text-right" :pipeline="pipeline" />
<div
v-if="displayPipelineActions"
class="table-section section-20 table-button-footer pipeline-actions"
>
<div class="btn-group table-action-buttons">
<pipelines-manual-actions-component v-if="actions.length > 0" :actions="actions" />
<pipelines-artifacts-component
v-if="pipeline.details.artifacts.length"
:artifacts="pipeline.details.artifacts"
/>
<gl-button
v-if="pipeline.flags.retryable"
v-gl-tooltip.hover
:aria-label="$options.i18n.redeployTitle"
:title="$options.i18n.redeployTitle"
:disabled="isRetrying"
:loading="isRetrying"
class="js-pipelines-retry-button"
data-qa-selector="pipeline_retry_button"
icon="repeat"
variant="default"
category="secondary"
@click="handleRetryClick"
/>
<gl-button
v-if="pipeline.flags.cancelable"
v-gl-tooltip.hover
v-gl-modal-directive="'confirmation-modal'"
:aria-label="$options.i18n.cancelTitle"
:title="$options.i18n.cancelTitle"
:loading="isCancelling"
:disabled="isCancelling"
icon="close"
variant="danger"
category="primary"
class="js-pipelines-cancel-button"
@click="handleCancelClick"
/>
</div>
</div>
</div>
</template>
---
title: Use GlTable design system component for pipelines table
merge_request: 58581
author:
type: changed
......@@ -9,8 +9,11 @@ module QA
element :pipeline_url_link
end
view 'app/assets/javascripts/pipelines/components/pipelines_list/pipelines_table_row.vue' do
view 'app/assets/javascripts/pipelines/components/pipelines_list/pipelines_status_badge.vue' do
element :pipeline_commit_status
end
view 'app/assets/javascripts/pipelines/components/pipelines_list/pipeline_operations.vue' do
element :pipeline_retry_button
end
......
import { mount } from '@vue/test-utils';
import waitForPromises from 'helpers/wait_for_promises';
import PipelinesTableRowComponent from '~/pipelines/components/pipelines_list/pipelines_table_row.vue';
import eventHub from '~/pipelines/event_hub';
describe('Pipelines Table Row', () => {
const jsonFixtureName = 'pipelines/pipelines.json';
const createWrapper = (pipeline) =>
mount(PipelinesTableRowComponent, {
propsData: {
pipeline,
viewType: 'root',
},
});
let wrapper;
let pipeline;
let pipelineWithoutAuthor;
let pipelineWithoutCommit;
beforeEach(() => {
const { pipelines } = getJSONFixture(jsonFixtureName);
pipeline = pipelines.find((p) => p.user !== null && p.commit !== null);
pipelineWithoutAuthor = pipelines.find((p) => p.user === null && p.commit !== null);
pipelineWithoutCommit = pipelines.find((p) => p.user === null && p.commit === null);
});
afterEach(() => {
wrapper.destroy();
wrapper = null;
});
it('should render a table row', () => {
wrapper = createWrapper(pipeline);
expect(wrapper.attributes('class')).toContain('gl-responsive-table-row');
});
describe('status column', () => {
beforeEach(() => {
wrapper = createWrapper(pipeline);
});
it('should render a pipeline link', () => {
expect(wrapper.find('.table-section.commit-link a').attributes('href')).toEqual(
pipeline.path,
);
});
it('should render status text', () => {
expect(wrapper.find('.table-section.commit-link a').text()).toContain(
pipeline.details.status.text,
);
});
});
describe('information column', () => {
beforeEach(() => {
wrapper = createWrapper(pipeline);
});
it('should render a pipeline link', () => {
expect(wrapper.find('.table-section:nth-child(2) a').attributes('href')).toEqual(
pipeline.path,
);
});
it('should render pipeline ID', () => {
expect(wrapper.find('.table-section:nth-child(2) a > span').text()).toEqual(
`#${pipeline.id}`,
);
});
describe('when a user is provided', () => {
it('should render user information', () => {
expect(
wrapper.find('.table-section:nth-child(3) .js-pipeline-url-user').attributes('href'),
).toEqual(pipeline.user.path);
expect(
wrapper.find('.table-section:nth-child(3) .js-user-avatar-image-tooltip').text().trim(),
).toEqual(pipeline.user.name);
});
});
});
describe('commit column', () => {
it('should render link to commit', () => {
wrapper = createWrapper(pipeline);
const commitLink = wrapper.find('.branch-commit .commit-sha');
expect(commitLink.attributes('href')).toEqual(pipeline.commit.commit_path);
});
const findElements = () => {
const commitTitleElement = wrapper.find('.branch-commit .commit-title');
const commitAuthorElement = commitTitleElement.find('a.avatar-image-container');
if (!commitAuthorElement.exists()) {
return {
commitAuthorElement,
};
}
const commitAuthorLink = commitAuthorElement.attributes('href');
const commitAuthorName = commitAuthorElement
.find('.js-user-avatar-image-tooltip')
.text()
.trim();
return {
commitAuthorElement,
commitAuthorLink,
commitAuthorName,
};
};
it('renders nothing without commit', () => {
expect(pipelineWithoutCommit.commit).toBe(null);
wrapper = createWrapper(pipelineWithoutCommit);
const { commitAuthorElement } = findElements();
expect(commitAuthorElement.exists()).toBe(false);
});
it('renders commit author', () => {
wrapper = createWrapper(pipeline);
const { commitAuthorLink, commitAuthorName } = findElements();
expect(commitAuthorLink).toEqual(pipeline.commit.author.path);
expect(commitAuthorName).toEqual(pipeline.commit.author.username);
});
it('renders commit with unregistered author', () => {
expect(pipelineWithoutAuthor.commit.author).toBe(null);
wrapper = createWrapper(pipelineWithoutAuthor);
const { commitAuthorLink, commitAuthorName } = findElements();
expect(commitAuthorLink).toEqual(`mailto:${pipelineWithoutAuthor.commit.author_email}`);
expect(commitAuthorName).toEqual(pipelineWithoutAuthor.commit.author_name);
});
});
describe('stages column', () => {
const findAllMiniPipelineStages = () =>
wrapper.findAll('.table-section:nth-child(5) [data-testid="mini-pipeline-graph-dropdown"]');
it('should render an icon for each stage', () => {
wrapper = createWrapper(pipeline);
expect(findAllMiniPipelineStages()).toHaveLength(pipeline.details.stages.length);
});
it('should not render stages when stages are empty', () => {
const withoutStages = { ...pipeline };
withoutStages.details = { ...withoutStages.details, stages: null };
wrapper = createWrapper(withoutStages);
expect(findAllMiniPipelineStages()).toHaveLength(0);
});
});
describe('actions column', () => {
const scheduledJobAction = {
name: 'some scheduled job',
};
beforeEach(() => {
const withActions = { ...pipeline };
withActions.details.scheduled_actions = [scheduledJobAction];
withActions.flags.cancelable = true;
withActions.flags.retryable = true;
withActions.cancel_path = '/cancel';
withActions.retry_path = '/retry';
wrapper = createWrapper(withActions);
});
it('should render the provided actions', () => {
expect(wrapper.find('.js-pipelines-retry-button').exists()).toBe(true);
expect(wrapper.find('.js-pipelines-retry-button').attributes('title')).toMatch('Retry');
expect(wrapper.find('.js-pipelines-cancel-button').exists()).toBe(true);
expect(wrapper.find('.js-pipelines-cancel-button').attributes('title')).toMatch('Cancel');
});
it('should render the manual actions', async () => {
const manualActions = wrapper.find('[data-testid="pipelines-manual-actions-dropdown"]');
// Click on the dropdown and wait for `lazy` dropdown items
manualActions.find('.dropdown-toggle').trigger('click');
await waitForPromises();
expect(manualActions.text()).toContain(scheduledJobAction.name);
});
it('emits `retryPipeline` event when retry button is clicked and toggles loading', () => {
eventHub.$on('retryPipeline', (endpoint) => {
expect(endpoint).toBe('/retry');
});
wrapper.find('.js-pipelines-retry-button').trigger('click');
expect(wrapper.vm.isRetrying).toBe(true);
});
it('emits `openConfirmationModal` event when cancel button is clicked and toggles loading', () => {
eventHub.$once('openConfirmationModal', (data) => {
const { id, ref, commit } = pipeline;
expect(data.endpoint).toBe('/cancel');
expect(data.pipeline).toEqual(
expect.objectContaining({
id,
ref,
commit,
}),
);
});
wrapper.find('.js-pipelines-cancel-button').trigger('click');
});
it('renders a loading icon when `cancelingPipeline` matches pipeline id', (done) => {
wrapper.setProps({ cancelingPipeline: pipeline.id });
wrapper.vm
.$nextTick()
.then(() => {
expect(wrapper.vm.isCancelling).toBe(true);
})
.then(done)
.catch(done.fail);
});
});
});
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