mirror of
https://github.com/gitlabhq/gitlabhq.git
synced 2025-07-23 02:54:40 +00:00
Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
@ -1 +1 @@
|
||||
d0f58e0b8cc6f6e7736643238e9d7c67d6b84011
|
||||
aef787134ce907cfc179f3c1b8486c5d5b180d68
|
||||
|
@ -1 +1 @@
|
||||
7999217addae25ef054b769e74265cbd2ad28bad
|
||||
916824d9cf035ed16cbd91364188745bd1602771
|
||||
|
@ -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)"
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
|
@ -3,6 +3,7 @@ query getPipelineFailedJobsCount($fullPath: ID!, $pipelineIid: ID!) {
|
||||
id
|
||||
pipeline(iid: $pipelineIid) {
|
||||
id
|
||||
active
|
||||
jobs(statuses: [FAILED], retried: false, jobKind: BUILD) {
|
||||
count
|
||||
}
|
||||
|
@ -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 });
|
||||
});
|
||||
},
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
@ -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 ""
|
||||
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
|
@ -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();
|
||||
|
||||
|
@ -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();
|
||||
});
|
||||
});
|
||||
|
@ -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,
|
||||
|
@ -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
|
||||
|
Reference in New Issue
Block a user