Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot
2024-12-27 12:37:15 +00:00
parent f716bd05b1
commit 2c7d948103
15 changed files with 118 additions and 27 deletions

View File

@ -1 +1 @@
d0f58e0b8cc6f6e7736643238e9d7c67d6b84011
aef787134ce907cfc179f3c1b8486c5d5b180d68

View File

@ -1 +1 @@
7999217addae25ef054b769e74265cbd2ad28bad
916824d9cf035ed16cbd91364188745bd1602771

View File

@ -279,7 +279,6 @@ export default {
<template #row-details="{ item }">
<pipeline-failed-jobs-widget
v-if="displayFailedJobsWidget(item)"
:is-pipeline-active="item.active"
:pipeline-iid="item.iid"
:pipeline-path="item.path"
:project-path="getProjectPath(item)"

View File

@ -21,10 +21,6 @@ export default {
},
inject: ['fullPath', 'graphqlPath'],
props: {
isPipelineActive: {
required: true,
type: Boolean,
},
pipelineIid: {
required: true,
type: Number,
@ -44,10 +40,6 @@ export default {
return getQueryHeaders(this.graphqlResourceEtag);
},
query: getPipelineFailedJobsCount,
// Only poll if the pipeline is active
pollInterval() {
return this.isPipelineActive ? POLL_INTERVAL : 0;
},
variables() {
return {
fullPath: this.projectPath,
@ -55,6 +47,8 @@ export default {
};
},
update({ project }) {
this.isPipelineActive = project?.pipeline?.active || false;
return project?.pipeline?.jobs?.count || 0;
},
error() {
@ -66,6 +60,9 @@ export default {
return {
failedJobsCount: 0,
isExpanded: false,
// explicity set to null so watcher can detect
// reactivity changes for polling
isPipelineActive: null,
};
},
computed: {
@ -85,8 +82,20 @@ export default {
return this.failedJobsCount > 100;
},
},
mounted() {
toggleQueryPollingByVisibility(this.$apollo.queries.failedJobsCount, POLL_INTERVAL);
watch: {
isPipelineActive(active) {
if (!active) {
this.$apollo.queries.failedJobsCount.stopPolling();
} else {
this.$apollo.queries.failedJobsCount.startPolling(POLL_INTERVAL);
// ensure we only toggle polling back on tab switch
// if the pipeline is active
toggleQueryPollingByVisibility(this.$apollo.queries.failedJobsCount, POLL_INTERVAL);
}
},
},
beforeDestroy() {
this.$apollo.queries.failedJobsCount.stopPolling();
},
methods: {
toggleWidget() {
@ -94,9 +103,16 @@ export default {
},
async refetchCount() {
try {
// "pause" polling during manual refetch of count
// to avoid redundant calls
this.$apollo.queries.failedJobsCount.stopPolling();
await this.$apollo.queries.failedJobsCount.refetch();
} catch {
createAlert({ message: this.$options.fetchError });
} finally {
if (this.isPipelineActive) {
this.$apollo.queries.failedJobsCount.startPolling(POLL_INTERVAL);
}
}
},
},

View File

@ -3,6 +3,7 @@ query getPipelineFailedJobsCount($fullPath: ID!, $pipelineIid: ID!) {
id
pipeline(iid: $pipelineIid) {
id
active
jobs(statuses: [FAILED], retried: false, jobKind: BUILD) {
count
}

View File

@ -30,7 +30,6 @@ export const useDiffsList = defineStore('diffsList', {
const container = document.querySelector('#js-stream-container');
const { body } = await fetch(url, { signal });
if (previousController) previousController.abort();
// TODO: support aborting
await renderHtmlStreams([toPolyfillReadable(body)], container, { signal });
});
},

View File

@ -47,11 +47,21 @@ export class ChunkWriter {
constructor(htmlStream, config) {
this.htmlStream = htmlStream;
const { balanceRate, minChunkSize, maxChunkSize, lowFrameTime, highFrameTime, timeout } = {
const {
balanceRate,
minChunkSize,
maxChunkSize,
lowFrameTime,
highFrameTime,
timeout,
signal,
} = {
...defaultConfig,
...config,
};
this.registerSignal(signal);
// ensure we still render chunks over time if the size criteria is not met
this.scheduleAccumulatorFlush = throttle(this.flushAccumulator.bind(this), timeout);
@ -70,6 +80,18 @@ export class ChunkWriter {
});
}
registerSignal(signal) {
this.cancelAbort = () => {};
if (!signal) return;
const abort = this.abort.bind(this);
this.cancelAbort = () => {
signal.removeEventListener('abort', abort);
};
signal.addEventListener('abort', abort, {
once: true,
});
}
write(chunk) {
if (this.buffer) {
this.buffer = concatUint8Arrays(this.buffer, chunk);
@ -133,11 +155,13 @@ export class ChunkWriter {
this.buffer = null;
}
this.htmlStream.close();
this.cancelAbort();
}
abort() {
this.scheduleAccumulatorFlush.cancel();
this.buffer = null;
this.htmlStream.abort();
this.cancelAbort();
}
}

View File

@ -23695,6 +23695,9 @@ msgstr ""
msgid "Failure"
msgstr ""
msgid "False"
msgstr ""
msgid "Fast timeout"
msgstr ""
@ -59510,6 +59513,9 @@ msgstr ""
msgid "Troubleshoot failed CI/CD jobs with Root Cause Analysis."
msgstr ""
msgid "True"
msgstr ""
msgid "Trust user"
msgstr ""

View File

@ -320,5 +320,4 @@ spec/frontend/vue_shared/components/upload_dropzone/upload_dropzone_spec.js
spec/frontend/vue_shared/directives/tooltip_on_truncate_spec.js
spec/frontend/vue_shared/directives/track_event_spec.js
spec/frontend/vue_shared/issuable/sidebar/components/issuable_sidebar_root_spec.js
spec/frontend/webhooks/components/form_url_app_spec.js
spec/frontend/work_items/components/work_item_description_rendered_spec.js

View File

@ -208,7 +208,6 @@ describe('Pipelines Table', () => {
it('passes the expected props', () => {
expect(findPipelineFailureWidget().props()).toStrictEqual({
isPipelineActive: firstPipeline.active,
pipelineIid: firstPipeline.iid,
pipelinePath: firstPipeline.path,
// Make sure the forward slash was removed

View File

@ -32,13 +32,14 @@ export const allowedToFailJob = {
allowFailure: true,
};
export const createFailedJobsMockCount = (count = 4) => {
export const createFailedJobsMockCount = (count = 4, active = false) => {
return {
data: {
project: {
id: 'gid://gitlab/Project/20',
pipeline: {
id: 'gid://gitlab/Pipeline/20',
active,
jobs: {
count,
},
@ -75,3 +76,4 @@ export const activeFailedJobsMock = createFailedJobsMock([allowedToFailJob, job]
export const failedJobsMock2 = createFailedJobsMock([job]);
export const failedJobsCountMock = createFailedJobsMockCount();
export const failedJobsCountMockActive = createFailedJobsMockCount(4, true);

View File

@ -9,7 +9,7 @@ import { toggleQueryPollingByVisibility } from '~/graphql_shared/utils';
import PipelineFailedJobsWidget from '~/ci/pipelines_page/components/failure_widget/pipeline_failed_jobs_widget.vue';
import FailedJobsList from '~/ci/pipelines_page/components/failure_widget/failed_jobs_list.vue';
import getPipelineFailedJobsCount from '~/ci/pipelines_page/graphql/queries/get_pipeline_failed_jobs_count.query.graphql';
import { failedJobsCountMock } from './mock';
import { failedJobsCountMock, failedJobsCountMockActive } from './mock';
Vue.use(VueApollo);
jest.mock('~/alert');
@ -19,7 +19,6 @@ describe('PipelineFailedJobsWidget component', () => {
let wrapper;
const defaultProps = {
isPipelineActive: false,
pipelineIid: 1,
pipelinePath: '/pipelines/1',
projectPath: 'namespace/project/',
@ -31,6 +30,7 @@ describe('PipelineFailedJobsWidget component', () => {
};
const defaultHandler = jest.fn().mockResolvedValue(failedJobsCountMock);
const activeHandler = jest.fn().mockResolvedValue(failedJobsCountMockActive);
const createMockApolloProvider = (handler) => {
const requestHandlers = [[getPipelineFailedJobsCount, handler]];
@ -147,21 +147,21 @@ describe('PipelineFailedJobsWidget component', () => {
});
it('polls for failed jobs count when pipeline is active', async () => {
createComponent({ props: { isPipelineActive: true } });
createComponent({ handler: activeHandler });
await waitForPromises();
expect(defaultHandler).toHaveBeenCalledTimes(1);
expect(activeHandler).toHaveBeenCalledTimes(1);
jest.advanceTimersByTime(10000);
await waitForPromises();
expect(defaultHandler).toHaveBeenCalledTimes(2);
expect(activeHandler).toHaveBeenCalledTimes(2);
});
it('should set up toggle visibility on mount', async () => {
createComponent();
it('should set up toggle visibility when pipeline is active', async () => {
createComponent({ handler: activeHandler });
await waitForPromises();
@ -170,6 +170,42 @@ describe('PipelineFailedJobsWidget component', () => {
});
describe('job retry', () => {
it.each`
active | handler
${true} | ${activeHandler}
${false} | ${defaultHandler}
`(
'stops polling and restarts polling: $active if pipeline is active: $active',
async ({ active, handler }) => {
createComponent({ handler });
await waitForPromises();
const stopPollingSpy = jest.spyOn(
wrapper.vm.$apollo.queries.failedJobsCount,
'stopPolling',
);
const startPollingSpy = jest.spyOn(
wrapper.vm.$apollo.queries.failedJobsCount,
'startPolling',
);
await findFailedJobsButton().vm.$emit('click');
await findFailedJobsList().vm.$emit('job-retried');
expect(stopPollingSpy).toHaveBeenCalled();
await waitForPromises();
if (active) {
expect(startPollingSpy).toHaveBeenCalled();
} else {
expect(startPollingSpy).not.toHaveBeenCalled();
}
},
);
it('refetches failed jobs count', async () => {
createComponent();

View File

@ -247,4 +247,13 @@ describe('ChunkWriter', () => {
expect(write.mock.calls).toMatchObject([[text.repeat(3)]]);
expect(cancelTimer).not.toHaveBeenCalled();
});
it('aborts on abort signal', () => {
const controller = new AbortController();
config = { signal: controller.signal };
createWriter().write(createChunk('1234567890'));
controller.abort();
expect(abort).toHaveBeenCalledTimes(1);
expect(write).not.toHaveBeenCalled();
});
});

View File

@ -132,7 +132,7 @@ describe('FormUrlApp', () => {
expect(findAllUrlMaskItems()).toHaveLength(3);
const lastItem = findAllUrlMaskItems().at(-1);
const lastItem = findAllUrlMaskItems().at(2);
expect(lastItem.props()).toMatchObject({
itemKey: null,
itemValue: null,

View File

@ -135,7 +135,8 @@ RSpec.describe CrossDatabaseIgnoredTables, feature_category: :cell, query_analyz
expect { main_model_object.update!(updated_at: Time.zone.now) }.to raise_error(cross_database_exception)
end
it 'still raises an error when deleting an object' do
it 'still raises an error when deleting an object',
quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/508770' do
main_model_object = create_main_model_object
expect { main_model_object.destroy! }.to raise_error(cross_database_exception)
end