Commit 4c865eee authored by Jose Ivan Vargas's avatar Jose Ivan Vargas

Merge branch '356278-refactor-job-tab-counts' into 'master'

Update tab count on filtered search

See merge request gitlab-org/gitlab!83554
parents 30703023 ac6108ef
......@@ -32,6 +32,7 @@ export default {
nodes,
statuses,
pageInfo,
count: incoming.count,
};
}
......@@ -45,6 +46,7 @@ export default {
nodes,
statuses,
pageInfo,
count: incoming.count,
};
},
},
......
......@@ -3,6 +3,7 @@ query getJobs($fullPath: ID!, $after: String, $statuses: [CiJobStatus!]) {
id
__typename
jobs(after: $after, first: 30, statuses: $statuses) {
count
pageInfo {
endCursor
hasNextPage
......
......@@ -27,7 +27,6 @@ export default (containerId = 'js-jobs-table') => {
const {
fullPath,
jobCounts,
jobStatuses,
pipelineEditorPath,
emptyStateSvgPath,
......@@ -42,7 +41,6 @@ export default (containerId = 'js-jobs-table') => {
fullPath,
pipelineEditorPath,
jobStatuses: JSON.parse(jobStatuses),
jobCounts: JSON.parse(jobCounts),
admin: parseBoolean(admin),
},
render(createElement) {
......
......@@ -43,10 +43,11 @@ export default {
};
},
update(data) {
const { jobs: { nodes: list = [], pageInfo = {} } = {} } = data.project || {};
const { jobs: { nodes: list = [], pageInfo = {}, count } = {} } = data.project || {};
return {
list,
pageInfo,
count,
};
},
error() {
......@@ -64,6 +65,7 @@ export default {
scope: null,
infiniteScrollingTriggered: false,
filterSearchTriggered: false,
count: 0,
};
},
computed: {
......@@ -93,6 +95,20 @@ export default {
showFilteredSearch() {
return this.glFeatures?.jobsTableVueSearch && !this.scope;
},
jobsCount() {
return this.jobs.count;
},
},
watch: {
// this watcher ensures that the count on the all tab
// is not updated when switching to the finished tab
jobsCount(newCount, oldCount) {
if (this.scope) {
this.count = oldCount;
} else {
this.count = newCount;
}
},
},
mounted() {
eventHub.$on('jobActionPerformed', this.handleJobAction);
......@@ -161,7 +177,11 @@ export default {
{{ $options.i18n.errorMsg }}
</gl-alert>
<jobs-table-tabs @fetchJobsByStatus="fetchJobsByStatus" />
<jobs-table-tabs
:all-jobs-count="count"
:loading="loading"
@fetchJobsByStatus="fetchJobsByStatus"
/>
<jobs-filtered-search
v-if="showFilteredSearch"
......
<script>
import { GlBadge, GlTab, GlTabs } from '@gitlab/ui';
import { __ } from '~/locale';
import { GlBadge, GlTab, GlTabs, GlLoadingIcon } from '@gitlab/ui';
import { s__ } from '~/locale';
export default {
components: {
GlBadge,
GlTab,
GlTabs,
GlLoadingIcon,
},
inject: {
jobCounts: {
default: {},
},
jobStatuses: {
default: {},
},
},
props: {
allJobsCount: {
type: Number,
required: true,
},
loading: {
type: Boolean,
required: true,
},
},
computed: {
tabs() {
return [
{
text: __('All'),
count: this.jobCounts.all,
text: s__('Jobs|All'),
count: this.allJobsCount,
scope: null,
testId: 'jobs-all-tab',
showBadge: true,
},
{
text: __('Pending'),
count: this.jobCounts.pending,
scope: this.jobStatuses.pending,
testId: 'jobs-pending-tab',
},
{
text: __('Running'),
count: this.jobCounts.running,
scope: this.jobStatuses.running,
testId: 'jobs-running-tab',
},
{
text: __('Finished'),
count: this.jobCounts.finished,
text: s__('Jobs|Finished'),
scope: [this.jobStatuses.success, this.jobStatuses.failed, this.jobStatuses.canceled],
testId: 'jobs-finished-tab',
showBadge: false,
},
];
},
showLoadingIcon() {
return this.loading && !this.allJobsCount;
},
},
};
</script>
......@@ -59,7 +59,11 @@ export default {
>
<template #title>
<span>{{ tab.text }}</span>
<gl-badge size="sm" class="gl-tab-counter-badge">{{ tab.count }}</gl-badge>
<gl-loading-icon v-if="showLoadingIcon && tab.showBadge" class="gl-ml-2" />
<gl-badge v-else-if="tab.showBadge" size="sm" class="gl-tab-counter-badge">
{{ tab.count }}
</gl-badge>
</template>
</gl-tab>
</gl-tabs>
......
......@@ -3,7 +3,7 @@
- admin = local_assigns.fetch(:admin, false)
- if Feature.enabled?(:jobs_table_vue, @project, default_enabled: :yaml)
#js-jobs-table{ data: { admin: admin, full_path: @project.full_path, job_counts: job_counts.to_json, job_statuses: job_statuses.to_json, pipeline_editor_path: project_ci_pipeline_editor_path(@project), empty_state_svg_path: image_path('jobs-empty-state.svg') } }
#js-jobs-table{ data: { admin: admin, full_path: @project.full_path, job_statuses: job_statuses.to_json, pipeline_editor_path: project_ci_pipeline_editor_path(@project), empty_state_svg_path: image_path('jobs-empty-state.svg') } }
- else
.top-area
- build_path_proc = ->(scope) { project_jobs_path(@project, scope: scope) }
......
......@@ -21586,6 +21586,9 @@ msgstr ""
msgid "Jobs older than the configured time are considered expired and are archived. Archived jobs can no longer be retried. Leave empty to never archive jobs automatically. The default unit is in days, but you can use other units, for example %{code_open}15 days%{code_close}, %{code_open}1 month%{code_close}, %{code_open}2 years%{code_close}. Minimum value is 1 day."
msgstr ""
msgid "Jobs|All"
msgstr ""
msgid "Jobs|Are you sure you want to proceed?"
msgstr ""
......@@ -21598,6 +21601,9 @@ msgstr ""
msgid "Jobs|Filter jobs"
msgstr ""
msgid "Jobs|Finished"
msgstr ""
msgid "Jobs|Job is stuck. Check runners."
msgstr ""
......
......@@ -67,19 +67,8 @@ RSpec.describe 'User browses jobs' do
expect(page.find('[data-testid="jobs-all-tab"] .badge').text).to include('0')
end
it 'shows a tab for Pending jobs and count' do
expect(page.find('[data-testid="jobs-pending-tab"]').text).to include('Pending')
expect(page.find('[data-testid="jobs-pending-tab"] .badge').text).to include('0')
end
it 'shows a tab for Running jobs and count' do
expect(page.find('[data-testid="jobs-running-tab"]').text).to include('Running')
expect(page.find('[data-testid="jobs-running-tab"] .badge').text).to include('0')
end
it 'shows a tab for Finished jobs and count' do
expect(page.find('[data-testid="jobs-finished-tab"]').text).to include('Finished')
expect(page.find('[data-testid="jobs-finished-tab"] .badge').text).to include('0')
end
it 'updates the content when tab is clicked' do
......
......@@ -88,7 +88,7 @@ describe('Job table app', () => {
});
it('when switching tabs only the skeleton loader should show', () => {
findTabs().vm.$emit('fetchJobsByStatus', 'PENDING');
findTabs().vm.$emit('fetchJobsByStatus', null);
expect(findSkeletonLoader().exists()).toBe(true);
expect(findLoadingSpinner().exists()).toBe(false);
......@@ -184,14 +184,14 @@ describe('Job table app', () => {
it.each`
scope | shouldDisplay
${null} | ${true}
${'PENDING'} | ${false}
${'RUNNING'} | ${false}
${['FAILED', 'SUCCESS', 'CANCELED']} | ${false}
`(
'with tab scope $scope the filtered search displays $shouldDisplay',
async ({ scope, shouldDisplay }) => {
createComponent();
await waitForPromises();
await findTabs().vm.$emit('fetchJobsByStatus', scope);
expect(findFilteredSearch().exists()).toBe(shouldDisplay);
......
import { GlTab } from '@gitlab/ui';
import { mount } from '@vue/test-utils';
import { trimText } from 'helpers/text_helper';
import { extendedWrapper } from 'helpers/vue_test_utils_helper';
......@@ -7,16 +8,31 @@ describe('Jobs Table Tabs', () => {
let wrapper;
const defaultProps = {
jobCounts: { all: 848, pending: 0, running: 0, finished: 704 },
allJobsCount: 286,
loading: false,
};
const findTab = (testId) => wrapper.findByTestId(testId);
const statuses = {
success: 'SUCCESS',
failed: 'FAILED',
canceled: 'CANCELED',
};
const findAllTab = () => wrapper.findByTestId('jobs-all-tab');
const findFinishedTab = () => wrapper.findByTestId('jobs-finished-tab');
const triggerTabChange = (index) => wrapper.findAllComponents(GlTab).at(index).vm.$emit('click');
const createComponent = () => {
const createComponent = (props = defaultProps) => {
wrapper = extendedWrapper(
mount(JobsTableTabs, {
provide: {
...defaultProps,
jobStatuses: {
...statuses,
},
},
propsData: {
...props,
},
}),
);
......@@ -30,13 +46,21 @@ describe('Jobs Table Tabs', () => {
wrapper.destroy();
});
it('displays All tab with count', () => {
expect(trimText(findAllTab().text())).toBe(`All ${defaultProps.allJobsCount}`);
});
it('displays Finished tab with no count', () => {
expect(findFinishedTab().text()).toBe('Finished');
});
it.each`
tabId | text | count
${'jobs-all-tab'} | ${'All'} | ${defaultProps.jobCounts.all}
${'jobs-pending-tab'} | ${'Pending'} | ${defaultProps.jobCounts.pending}
${'jobs-running-tab'} | ${'Running'} | ${defaultProps.jobCounts.running}
${'jobs-finished-tab'} | ${'Finished'} | ${defaultProps.jobCounts.finished}
`('displays the right tab text and badge count', ({ tabId, text, count }) => {
expect(trimText(findTab(tabId).text())).toBe(`${text} ${count}`);
tabIndex | expectedScope
${0} | ${null}
${1} | ${[statuses.success, statuses.failed, statuses.canceled]}
`('emits fetchJobsByStatus with $expectedScope on tab change', ({ tabIndex, expectedScope }) => {
triggerTabChange(tabIndex);
expect(wrapper.emitted()).toEqual({ fetchJobsByStatus: [[expectedScope]] });
});
});
......@@ -1481,6 +1481,7 @@ export const mockJobsQueryResponse = {
project: {
id: '1',
jobs: {
count: 1,
pageInfo: {
endCursor: 'eyJpZCI6IjIzMTcifQ',
hasNextPage: true,
......
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