Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
a8632f5099
commit
532c924885
|
|
@ -29,6 +29,11 @@ export default {
|
|||
return {};
|
||||
},
|
||||
},
|
||||
visible: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
failureHistoryMessage() {
|
||||
|
|
@ -77,6 +82,8 @@ export default {
|
|||
:modal-id="modalId"
|
||||
:title="testCase.classname"
|
||||
:action-primary="$options.modalCloseButton"
|
||||
:visible="visible"
|
||||
@hidden="$emit('hidden')"
|
||||
>
|
||||
<div class="gl-display-flex gl-flex-wrap gl-mx-n4 gl-my-3">
|
||||
<strong class="gl-text-right col-sm-3">{{ $options.text.name }}</strong>
|
||||
|
|
|
|||
|
|
@ -21,13 +21,7 @@ import projectInfoQuery from '../queries/project_info.query.graphql';
|
|||
import getRefMixin from '../mixins/get_ref';
|
||||
import userInfoQuery from '../queries/user_info.query.graphql';
|
||||
import applicationInfoQuery from '../queries/application_info.query.graphql';
|
||||
import {
|
||||
DEFAULT_BLOB_INFO,
|
||||
TEXT_FILE_TYPE,
|
||||
LFS_STORAGE,
|
||||
LEGACY_FILE_TYPES,
|
||||
CODEOWNERS_FILE_NAME,
|
||||
} from '../constants';
|
||||
import { DEFAULT_BLOB_INFO, TEXT_FILE_TYPE, LFS_STORAGE, LEGACY_FILE_TYPES } from '../constants';
|
||||
import BlobButtonGroup from './blob_button_group.vue';
|
||||
import ForkSuggestion from './fork_suggestion.vue';
|
||||
import { loadViewer } from './blob_viewers';
|
||||
|
|
@ -38,7 +32,6 @@ export default {
|
|||
BlobButtonGroup,
|
||||
BlobContent,
|
||||
GlLoadingIcon,
|
||||
CodeownersValidation: () => import('ee_component/blob/components/codeowners_validation.vue'),
|
||||
GlButton,
|
||||
ForkSuggestion,
|
||||
WebIdeLink,
|
||||
|
|
@ -181,9 +174,6 @@ export default {
|
|||
currentRef() {
|
||||
return this.originalBranch || this.ref;
|
||||
},
|
||||
isCodeownersFile() {
|
||||
return this.path.includes(CODEOWNERS_FILE_NAME);
|
||||
},
|
||||
viewer() {
|
||||
const { richViewer, simpleViewer } = this.blobInfo;
|
||||
return this.activeViewerType === RICH_BLOB_VIEWER ? richViewer : simpleViewer;
|
||||
|
|
@ -418,12 +408,6 @@ export default {
|
|||
:fork-path="forkPath"
|
||||
@cancel="setForkTarget(null)"
|
||||
/>
|
||||
<codeowners-validation
|
||||
v-if="isCodeownersFile"
|
||||
:current-ref="currentRef"
|
||||
:project-path="projectPath"
|
||||
:file-path="path"
|
||||
/>
|
||||
<blob-content
|
||||
v-if="!blobViewer"
|
||||
class="js-syntax-highlight"
|
||||
|
|
@ -441,6 +425,8 @@ export default {
|
|||
v-else
|
||||
:blob="blobInfo"
|
||||
:chunks="chunks"
|
||||
:project-path="projectPath"
|
||||
:current-ref="currentRef"
|
||||
class="blob-viewer"
|
||||
@error="onError"
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -116,5 +116,3 @@ export const POLLING_INTERVAL_BACKOFF = 2;
|
|||
export const CONFLICTS_MODAL_ID = 'fork-sync-conflicts-modal';
|
||||
|
||||
export const FORK_UPDATED_EVENT = 'fork:updated';
|
||||
|
||||
export const CODEOWNERS_FILE_NAME = 'CODEOWNERS';
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ export default {
|
|||
import(
|
||||
'~/vue_merge_request_widget/extensions/security_reports/mr_widget_security_reports.vue'
|
||||
),
|
||||
MrTestReportWidget: () => import('~/vue_merge_request_widget/extensions/test_report/index.vue'),
|
||||
MrTerraformWidget: () => import('~/vue_merge_request_widget/extensions/terraform/index.vue'),
|
||||
MrCodeQualityWidget: () =>
|
||||
import('~/vue_merge_request_widget/extensions/code_quality/index.vue'),
|
||||
|
|
@ -18,6 +19,10 @@ export default {
|
|||
},
|
||||
|
||||
computed: {
|
||||
testReportWidget() {
|
||||
return this.mr.testResultsPath && 'MrTestReportWidget';
|
||||
},
|
||||
|
||||
terraformPlansWidget() {
|
||||
return this.mr.terraformReportsPath && 'MrTerraformWidget';
|
||||
},
|
||||
|
|
@ -27,9 +32,12 @@ export default {
|
|||
},
|
||||
|
||||
widgets() {
|
||||
return [this.codeQualityWidget, this.terraformPlansWidget, 'MrSecurityWidget'].filter(
|
||||
(w) => w,
|
||||
);
|
||||
return [
|
||||
this.codeQualityWidget,
|
||||
this.testReportWidget,
|
||||
this.terraformPlansWidget,
|
||||
'MrSecurityWidget',
|
||||
].filter((w) => w);
|
||||
},
|
||||
},
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,189 +0,0 @@
|
|||
import { uniqueId } from 'lodash';
|
||||
import { __ } from '~/locale';
|
||||
import axios from '~/lib/utils/axios_utils';
|
||||
import { HTTP_STATUS_NO_CONTENT } from '~/lib/utils/http_status';
|
||||
import TestCaseDetails from '~/pipelines/components/test_reports/test_case_details.vue';
|
||||
import { EXTENSION_ICONS } from '../../constants';
|
||||
import {
|
||||
summaryTextBuilder,
|
||||
reportTextBuilder,
|
||||
reportSubTextBuilder,
|
||||
countRecentlyFailedTests,
|
||||
recentFailuresTextBuilder,
|
||||
formatFilePath,
|
||||
} from './utils';
|
||||
import { i18n, TESTS_FAILED_STATUS, ERROR_STATUS } from './constants';
|
||||
|
||||
export default {
|
||||
name: 'WidgetTestSummary',
|
||||
enablePolling: true,
|
||||
i18n,
|
||||
props: ['testResultsPath', 'headBlobPath', 'pipeline'],
|
||||
modalComponent: TestCaseDetails,
|
||||
computed: {
|
||||
failedTestNames() {
|
||||
if (!this.collapsedData?.suites) {
|
||||
return '';
|
||||
}
|
||||
|
||||
const newFailures = this.collapsedData?.suites.flatMap((suite) => [suite.new_failures || []]);
|
||||
const fileNames = newFailures.flatMap((newFailure) => {
|
||||
return newFailure.map((failure) => {
|
||||
return failure.file;
|
||||
});
|
||||
});
|
||||
|
||||
return fileNames.join(' ').trim();
|
||||
},
|
||||
summary(data) {
|
||||
if (data.parsingInProgress) {
|
||||
return this.$options.i18n.loading;
|
||||
}
|
||||
if (data.hasSuiteError) {
|
||||
return this.$options.i18n.error;
|
||||
}
|
||||
return {
|
||||
subject: summaryTextBuilder(this.$options.i18n.label, data.summary),
|
||||
meta: recentFailuresTextBuilder(data.summary),
|
||||
};
|
||||
},
|
||||
statusIcon(data) {
|
||||
if (data.status === TESTS_FAILED_STATUS) {
|
||||
return EXTENSION_ICONS.warning;
|
||||
}
|
||||
if (data.hasSuiteError) {
|
||||
return EXTENSION_ICONS.failed;
|
||||
}
|
||||
return EXTENSION_ICONS.success;
|
||||
},
|
||||
tertiaryButtons() {
|
||||
const actionButtons = [];
|
||||
|
||||
if (this.failedTestNames().length > 0) {
|
||||
actionButtons.push({
|
||||
dataClipboardText: this.failedTestNames(),
|
||||
id: uniqueId('copy-to-clipboard'),
|
||||
icon: 'copy-to-clipboard',
|
||||
testId: 'copy-failed-specs-btn',
|
||||
text: this.$options.i18n.copyFailedSpecs,
|
||||
tooltipText: this.$options.i18n.copyFailedSpecsTooltip,
|
||||
tooltipOnClick: __('Copied'),
|
||||
});
|
||||
}
|
||||
|
||||
actionButtons.push({
|
||||
text: this.$options.i18n.fullReport,
|
||||
href: `${this.pipeline.path}/test_report`,
|
||||
target: '_blank',
|
||||
trackFullReportClicked: true,
|
||||
testId: 'full-report-link',
|
||||
});
|
||||
|
||||
return actionButtons;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
fetchCollapsedData() {
|
||||
return axios.get(this.testResultsPath).then((response) => {
|
||||
const { data = {}, status } = response;
|
||||
const { suites = [], summary = {} } = data;
|
||||
|
||||
return {
|
||||
...response,
|
||||
data: {
|
||||
hasSuiteError: suites.some((suite) => suite.status === ERROR_STATUS),
|
||||
parsingInProgress: status === HTTP_STATUS_NO_CONTENT,
|
||||
...data,
|
||||
summary: {
|
||||
recentlyFailed: countRecentlyFailedTests(suites),
|
||||
...summary,
|
||||
},
|
||||
},
|
||||
};
|
||||
});
|
||||
},
|
||||
fetchFullData() {
|
||||
return Promise.resolve(this.prepareReports());
|
||||
},
|
||||
suiteIcon(suite) {
|
||||
if (suite.status === ERROR_STATUS) {
|
||||
return EXTENSION_ICONS.error;
|
||||
}
|
||||
if (suite.status === TESTS_FAILED_STATUS) {
|
||||
return EXTENSION_ICONS.failed;
|
||||
}
|
||||
return EXTENSION_ICONS.success;
|
||||
},
|
||||
testHeader(test, sectionHeader, index) {
|
||||
const headers = [];
|
||||
if (index === 0) {
|
||||
headers.push(sectionHeader);
|
||||
}
|
||||
if (test.recent_failures?.count && test.recent_failures?.base_branch) {
|
||||
headers.push(i18n.recentFailureCount(test.recent_failures));
|
||||
}
|
||||
return headers;
|
||||
},
|
||||
mapTestAsChild({ iconName, sectionHeader }) {
|
||||
return (test, index) => {
|
||||
return {
|
||||
id: uniqueId('test-'),
|
||||
header: this.testHeader(test, sectionHeader, index),
|
||||
modal: {
|
||||
text: test.name,
|
||||
onClick: () => {
|
||||
this.modalData = {
|
||||
testCase: {
|
||||
filePath: test.file && `${this.headBlobPath}/${formatFilePath(test.file)}`,
|
||||
...test,
|
||||
},
|
||||
};
|
||||
},
|
||||
},
|
||||
icon: { name: iconName },
|
||||
};
|
||||
};
|
||||
},
|
||||
prepareReports() {
|
||||
return this.collapsedData.suites
|
||||
.map((suite) => {
|
||||
return {
|
||||
...suite,
|
||||
summary: {
|
||||
recentlyFailed: countRecentlyFailedTests(suite),
|
||||
...suite.summary,
|
||||
},
|
||||
};
|
||||
})
|
||||
.map((suite) => {
|
||||
return {
|
||||
id: uniqueId('suite-'),
|
||||
text: reportTextBuilder(suite),
|
||||
subtext: reportSubTextBuilder(suite),
|
||||
icon: {
|
||||
name: this.suiteIcon(suite),
|
||||
},
|
||||
children: [
|
||||
...[...suite.new_failures, ...suite.new_errors].map(
|
||||
this.mapTestAsChild({
|
||||
sectionHeader: i18n.newHeader,
|
||||
iconName: EXTENSION_ICONS.failed,
|
||||
}),
|
||||
),
|
||||
...[...suite.existing_failures, ...suite.existing_errors].map(
|
||||
this.mapTestAsChild({
|
||||
iconName: EXTENSION_ICONS.failed,
|
||||
}),
|
||||
),
|
||||
...[...suite.resolved_failures, ...suite.resolved_errors].map(
|
||||
this.mapTestAsChild({
|
||||
sectionHeader: i18n.fixedHeader,
|
||||
iconName: EXTENSION_ICONS.success,
|
||||
}),
|
||||
),
|
||||
],
|
||||
};
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
|
|
@ -0,0 +1,313 @@
|
|||
<script>
|
||||
import { uniqueId, uniq } from 'lodash';
|
||||
import { __ } from '~/locale';
|
||||
import axios from '~/lib/utils/axios_utils';
|
||||
import { HTTP_STATUS_NO_CONTENT } from '~/lib/utils/http_status';
|
||||
import TestCaseDetails from '~/pipelines/components/test_reports/test_case_details.vue';
|
||||
import MrWidget from '~/vue_merge_request_widget/components/widget/widget.vue';
|
||||
import MrWidgetRow from '~/vue_merge_request_widget/components/widget/widget_content_row.vue';
|
||||
import { DynamicScroller, DynamicScrollerItem } from 'vendor/vue-virtual-scroller';
|
||||
import { EXTENSION_ICONS } from '../../constants';
|
||||
import {
|
||||
summaryTextBuilder,
|
||||
reportTextBuilder,
|
||||
reportSubTextBuilder,
|
||||
countRecentlyFailedTests,
|
||||
recentFailuresTextBuilder,
|
||||
formatFilePath,
|
||||
} from './utils';
|
||||
import { i18n, TESTS_FAILED_STATUS, ERROR_STATUS } from './constants';
|
||||
|
||||
export default {
|
||||
name: 'WidgetTestReport',
|
||||
components: {
|
||||
MrWidget,
|
||||
MrWidgetRow,
|
||||
DynamicScroller,
|
||||
DynamicScrollerItem,
|
||||
TestCaseDetails,
|
||||
},
|
||||
i18n,
|
||||
props: {
|
||||
mr: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
collapsedData: {},
|
||||
suites: [],
|
||||
modalData: null,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
failedTestNames() {
|
||||
const { data: { suites = [] } = {} } = this.collapsedData;
|
||||
|
||||
if (!this.hasSuites) {
|
||||
return '';
|
||||
}
|
||||
|
||||
const newFailures = suites.flatMap((suite) => [suite.new_failures || []]);
|
||||
const fileNames = newFailures.flatMap((newFailure) => {
|
||||
return newFailure.map((failure) => {
|
||||
return failure.file;
|
||||
});
|
||||
});
|
||||
|
||||
return uniq(fileNames).join(' ').trim();
|
||||
},
|
||||
summary() {
|
||||
const {
|
||||
data: { parsingInProgress = false, hasSuiteError = false, summary = {} } = {},
|
||||
} = this.collapsedData;
|
||||
|
||||
if (parsingInProgress) {
|
||||
return { title: this.$options.i18n.loading };
|
||||
}
|
||||
if (hasSuiteError) {
|
||||
return { title: this.$options.i18n.error };
|
||||
}
|
||||
return {
|
||||
title: summaryTextBuilder(this.$options.i18n.label, summary),
|
||||
subtitle: recentFailuresTextBuilder(summary),
|
||||
};
|
||||
},
|
||||
statusIcon() {
|
||||
const { data: { status = null, hasSuiteError = false } = {} } = this.collapsedData;
|
||||
|
||||
if (status === TESTS_FAILED_STATUS) {
|
||||
return EXTENSION_ICONS.warning;
|
||||
}
|
||||
if (hasSuiteError) {
|
||||
return EXTENSION_ICONS.failed;
|
||||
}
|
||||
return EXTENSION_ICONS.success;
|
||||
},
|
||||
tertiaryButtons() {
|
||||
const actionButtons = [];
|
||||
|
||||
if (this.failedTestNames.length > 0) {
|
||||
actionButtons.push({
|
||||
dataClipboardText: this.failedTestNames,
|
||||
id: uniqueId('copy-to-clipboard'),
|
||||
icon: 'copy-to-clipboard',
|
||||
testId: 'copy-failed-specs-btn',
|
||||
text: this.$options.i18n.copyFailedSpecs,
|
||||
tooltipText: this.$options.i18n.copyFailedSpecsTooltip,
|
||||
tooltipOnClick: __('Copied'),
|
||||
});
|
||||
}
|
||||
|
||||
actionButtons.push({
|
||||
text: this.$options.i18n.fullReport,
|
||||
href: `${this.mr.pipeline.path}/test_report`,
|
||||
target: '_blank',
|
||||
trackFullReportClicked: true,
|
||||
testId: 'full-report-link',
|
||||
});
|
||||
|
||||
return actionButtons;
|
||||
},
|
||||
testResultsPath() {
|
||||
return this.mr.testResultsPath;
|
||||
},
|
||||
hasSuites() {
|
||||
return this.suites.length > 0;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
fetchCollapsedData() {
|
||||
return axios.get(this.testResultsPath).then((response) => {
|
||||
const { data = {}, status } = response;
|
||||
const { suites = [], summary = {} } = data;
|
||||
|
||||
this.collapsedData = {
|
||||
...response,
|
||||
data: {
|
||||
hasSuiteError: suites.some((suite) => suite.status === ERROR_STATUS),
|
||||
parsingInProgress: status === HTTP_STATUS_NO_CONTENT,
|
||||
...data,
|
||||
summary: {
|
||||
recentlyFailed: countRecentlyFailedTests(suites),
|
||||
...summary,
|
||||
},
|
||||
},
|
||||
};
|
||||
this.suites = this.prepareSuites(this.collapsedData);
|
||||
|
||||
return response;
|
||||
});
|
||||
},
|
||||
suiteIcon(suite) {
|
||||
if (suite.status === ERROR_STATUS) {
|
||||
return EXTENSION_ICONS.error;
|
||||
}
|
||||
if (suite.status === TESTS_FAILED_STATUS) {
|
||||
return EXTENSION_ICONS.failed;
|
||||
}
|
||||
return EXTENSION_ICONS.success;
|
||||
},
|
||||
testHeader(test, sectionHeader, index) {
|
||||
const headers = [];
|
||||
if (index === 0) {
|
||||
headers.push(sectionHeader);
|
||||
}
|
||||
if (test.recent_failures?.count && test.recent_failures?.base_branch) {
|
||||
headers.push(i18n.recentFailureCount(test.recent_failures));
|
||||
}
|
||||
return headers;
|
||||
},
|
||||
mapTestAsChild({ iconName, sectionHeader }) {
|
||||
return (test, index) => {
|
||||
return {
|
||||
id: uniqueId('test-'),
|
||||
header: this.testHeader(test, sectionHeader, index),
|
||||
text: test.name,
|
||||
actions: [
|
||||
{
|
||||
text: __('View details'),
|
||||
onClick: () => {
|
||||
this.modalData = {
|
||||
testCase: {
|
||||
filePath: test.file && `${this.mr.headBlobPath}/${formatFilePath(test.file)}`,
|
||||
...test,
|
||||
},
|
||||
};
|
||||
},
|
||||
},
|
||||
],
|
||||
icon: { name: iconName },
|
||||
};
|
||||
};
|
||||
},
|
||||
onModalHidden() {
|
||||
this.modalData = null;
|
||||
},
|
||||
prepareSuites(collapsedData) {
|
||||
const {
|
||||
data: { suites = [] },
|
||||
} = collapsedData;
|
||||
|
||||
return suites
|
||||
.map((suite) => {
|
||||
return {
|
||||
...suite,
|
||||
summary: {
|
||||
recentlyFailed: countRecentlyFailedTests(suite),
|
||||
...suite.summary,
|
||||
},
|
||||
};
|
||||
})
|
||||
.map((suite) => {
|
||||
return {
|
||||
id: uniqueId('suite-'),
|
||||
text: reportTextBuilder(suite),
|
||||
subtext: reportSubTextBuilder(suite),
|
||||
icon: {
|
||||
name: this.suiteIcon(suite),
|
||||
},
|
||||
children: [
|
||||
...[...suite.new_failures, ...suite.new_errors].map(
|
||||
this.mapTestAsChild({
|
||||
sectionHeader: i18n.newHeader,
|
||||
iconName: EXTENSION_ICONS.failed,
|
||||
}),
|
||||
),
|
||||
...[...suite.existing_failures, ...suite.existing_errors].map(
|
||||
this.mapTestAsChild({
|
||||
iconName: EXTENSION_ICONS.failed,
|
||||
}),
|
||||
),
|
||||
...[...suite.resolved_failures, ...suite.resolved_errors].map(
|
||||
this.mapTestAsChild({
|
||||
sectionHeader: i18n.fixedHeader,
|
||||
iconName: EXTENSION_ICONS.success,
|
||||
}),
|
||||
),
|
||||
],
|
||||
};
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<template>
|
||||
<div>
|
||||
<mr-widget
|
||||
:error-text="$options.i18n.error"
|
||||
:status-icon-name="statusIcon"
|
||||
:loading-text="$options.i18n.loading"
|
||||
:action-buttons="tertiaryButtons"
|
||||
:help-popover="$options.helpPopover"
|
||||
:widget-name="$options.name"
|
||||
:summary="summary"
|
||||
:fetch-collapsed-data="fetchCollapsedData"
|
||||
:is-collapsible="hasSuites"
|
||||
>
|
||||
<template #content>
|
||||
<mr-widget-row
|
||||
v-for="suite in suites"
|
||||
:key="suite.id"
|
||||
:level="2"
|
||||
:status-icon-name="suite.icon.name"
|
||||
:widget-name="$options.name"
|
||||
data-testid="extension-list-item"
|
||||
>
|
||||
<template #header>
|
||||
<div class="gl-flex-direction-column">
|
||||
<div>{{ suite.text }}</div>
|
||||
<div
|
||||
v-for="(subtext, i) in suite.subtext"
|
||||
:key="`${suite.id}-subtext-${i}`"
|
||||
class="gl-font-sm gl-text-gray-700"
|
||||
>
|
||||
{{ subtext }}
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<template #body>
|
||||
<div v-if="suite.children.length > 0" class="gl-mt-2 gl-w-full">
|
||||
<dynamic-scroller
|
||||
:items="suite.children"
|
||||
:min-item-size="32"
|
||||
:style="{ maxHeight: '170px' }"
|
||||
key-field="id"
|
||||
class="gl-pr-5"
|
||||
>
|
||||
<template #default="{ item, active }">
|
||||
<dynamic-scroller-item :item="item" :active="active">
|
||||
<strong
|
||||
v-for="(headerText, i) in item.header"
|
||||
:key="`${item.id}-headerText-${i}`"
|
||||
class="gl-display-block gl-mt-2"
|
||||
>
|
||||
{{ headerText }}
|
||||
</strong>
|
||||
<mr-widget-row
|
||||
:key="item.id"
|
||||
:level="3"
|
||||
:widget-name="$options.name"
|
||||
:status-icon-name="item.icon.name"
|
||||
:action-buttons="item.actions"
|
||||
class="gl-mt-2"
|
||||
>
|
||||
<template #header>{{ item.text }}</template>
|
||||
</mr-widget-row>
|
||||
</dynamic-scroller-item>
|
||||
</template>
|
||||
</dynamic-scroller>
|
||||
</div>
|
||||
</template>
|
||||
</mr-widget-row>
|
||||
</template>
|
||||
</mr-widget>
|
||||
<test-case-details
|
||||
:modal-id="`modal${$options.name}`"
|
||||
:visible="modalData !== null"
|
||||
v-bind="modalData"
|
||||
@hidden="onModalHidden"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
|
@ -62,7 +62,7 @@ export const reportSubTextBuilder = ({ suite_errors: suiteErrors, summary }) =>
|
|||
}
|
||||
return errors;
|
||||
}
|
||||
return recentFailuresTextBuilder(summary);
|
||||
return [recentFailuresTextBuilder(summary)];
|
||||
};
|
||||
|
||||
export const countRecentlyFailedTests = (subject) => {
|
||||
|
|
|
|||
|
|
@ -56,7 +56,6 @@ import mergeRequestQueryVariablesMixin from './mixins/merge_request_query_variab
|
|||
import getStateQuery from './queries/get_state.query.graphql';
|
||||
import getStateSubscription from './queries/get_state.subscription.graphql';
|
||||
import accessibilityExtension from './extensions/accessibility';
|
||||
import testReportExtension from './extensions/test_report';
|
||||
import ReportWidgetContainer from './components/report_widget_container.vue';
|
||||
import MrWidgetReadyToMerge from './components/states/new_ready_to_merge.vue';
|
||||
|
||||
|
|
@ -225,9 +224,6 @@ export default {
|
|||
this.mr.mergePipelinesEnabled && this.mr.sourceProjectId !== this.mr.targetProjectId,
|
||||
);
|
||||
},
|
||||
shouldRenderTestReport() {
|
||||
return Boolean(this.mr?.testResultsPath);
|
||||
},
|
||||
mergeError() {
|
||||
let { mergeError } = this.mr;
|
||||
|
||||
|
|
@ -281,11 +277,6 @@ export default {
|
|||
this.registerAccessibilityExtension();
|
||||
}
|
||||
},
|
||||
shouldRenderTestReport(newVal) {
|
||||
if (newVal) {
|
||||
this.registerTestReportExtension();
|
||||
}
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
MRWidgetService.fetchInitialData()
|
||||
|
|
@ -525,11 +516,6 @@ export default {
|
|||
registerExtension(accessibilityExtension);
|
||||
}
|
||||
},
|
||||
registerTestReportExtension() {
|
||||
if (this.shouldRenderTestReport) {
|
||||
registerExtension(testReportExtension);
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -33,6 +33,7 @@ export default {
|
|||
components: {
|
||||
GlLoadingIcon,
|
||||
Chunk,
|
||||
CodeownersValidation: () => import('ee_component/blob/components/codeowners_validation.vue'),
|
||||
},
|
||||
mixins: [Tracking.mixin()],
|
||||
props: {
|
||||
|
|
@ -40,6 +41,14 @@ export default {
|
|||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
projectPath: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
currentRef: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
|
@ -66,7 +75,7 @@ export default {
|
|||
if (this.blob.name && this.blob.name.endsWith(`.${SVELTE_LANGUAGE}`)) {
|
||||
// override for svelte files until https://github.com/rouge-ruby/rouge/issues/1717 is resolved
|
||||
return SVELTE_LANGUAGE;
|
||||
} else if (this.blob.name === this.$options.codeownersFileName) {
|
||||
} else if (this.isCodeownersFile) {
|
||||
// override for codeowners files
|
||||
return this.$options.codeownersLanguage;
|
||||
}
|
||||
|
|
@ -87,6 +96,9 @@ export default {
|
|||
totalChunks() {
|
||||
return Object.keys(this.chunks).length;
|
||||
},
|
||||
isCodeownersFile() {
|
||||
return this.blob.name === CODEOWNERS_FILE_NAME;
|
||||
},
|
||||
},
|
||||
async created() {
|
||||
if (this.isLfsBlob) {
|
||||
|
|
@ -238,7 +250,6 @@ export default {
|
|||
},
|
||||
userColorScheme: window.gon.user_color_scheme,
|
||||
currentlySelectedLine: null,
|
||||
codeownersFileName: CODEOWNERS_FILE_NAME,
|
||||
codeownersLanguage: CODEOWNERS_LANGUAGE,
|
||||
};
|
||||
</script>
|
||||
|
|
@ -250,6 +261,13 @@ export default {
|
|||
:data-path="blob.path"
|
||||
data-qa-selector="blob_viewer_file_content"
|
||||
>
|
||||
<codeowners-validation
|
||||
v-if="isCodeownersFile"
|
||||
class="gl-text-black-normal"
|
||||
:current-ref="currentRef"
|
||||
:project-path="projectPath"
|
||||
:file-path="blob.path"
|
||||
/>
|
||||
<chunk
|
||||
v-if="firstChunk"
|
||||
:lines="firstChunk.lines"
|
||||
|
|
@ -263,20 +281,21 @@ export default {
|
|||
/>
|
||||
|
||||
<gl-loading-icon v-if="isLoading" size="sm" class="gl-my-5" />
|
||||
<chunk
|
||||
v-for="(chunk, key, index) in chunks"
|
||||
v-else
|
||||
:key="key"
|
||||
:lines="chunk.lines"
|
||||
:content="chunk.content"
|
||||
:total-lines="chunk.totalLines"
|
||||
:starting-from="chunk.startingFrom"
|
||||
:is-highlighted="chunk.isHighlighted"
|
||||
:chunk-index="index"
|
||||
:language="chunk.language"
|
||||
:blame-path="blob.blamePath"
|
||||
:total-chunks="totalChunks"
|
||||
@appear="highlightChunk"
|
||||
/>
|
||||
<template v-else>
|
||||
<chunk
|
||||
v-for="(chunk, key, index) in chunks"
|
||||
:key="key"
|
||||
:lines="chunk.lines"
|
||||
:content="chunk.content"
|
||||
:total-lines="chunk.totalLines"
|
||||
:starting-from="chunk.startingFrom"
|
||||
:is-highlighted="chunk.isHighlighted"
|
||||
:chunk-index="index"
|
||||
:language="chunk.language"
|
||||
:blame-path="blob.blamePath"
|
||||
:total-chunks="totalChunks"
|
||||
@appear="highlightChunk"
|
||||
/>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
<script>
|
||||
import {
|
||||
GlDropdown,
|
||||
GlDropdownItem,
|
||||
GlDisclosureDropdown,
|
||||
GlDisclosureDropdownItem,
|
||||
GlDropdownForm,
|
||||
GlDropdownDivider,
|
||||
GlModal,
|
||||
|
|
@ -53,8 +53,8 @@ export default {
|
|||
emailAddressCopied: __('Email address copied'),
|
||||
},
|
||||
components: {
|
||||
GlDropdown,
|
||||
GlDropdownItem,
|
||||
GlDisclosureDropdown,
|
||||
GlDisclosureDropdownItem,
|
||||
GlDropdownForm,
|
||||
GlDropdownDivider,
|
||||
GlModal,
|
||||
|
|
@ -308,7 +308,7 @@ export default {
|
|||
|
||||
<template>
|
||||
<div>
|
||||
<gl-dropdown
|
||||
<gl-disclosure-dropdown
|
||||
icon="ellipsis_v"
|
||||
data-testid="work-item-actions-dropdown"
|
||||
text-sr-only
|
||||
|
|
@ -322,7 +322,7 @@ export default {
|
|||
class="work-item-notifications-form"
|
||||
:data-testid="$options.notificationsToggleFormTestId"
|
||||
>
|
||||
<div class="gl-px-5 gl-pb-2 gl-pt-1">
|
||||
<div class="gl-px-4 gl-pb-2 gl-pt-2">
|
||||
<gl-toggle
|
||||
:value="subscribedToNotifications"
|
||||
:label="$options.i18n.notifications"
|
||||
|
|
@ -335,50 +335,56 @@ export default {
|
|||
</gl-dropdown-form>
|
||||
<gl-dropdown-divider />
|
||||
</template>
|
||||
<gl-dropdown-item
|
||||
<gl-disclosure-dropdown-item
|
||||
v-if="canPromoteToObjective"
|
||||
:data-testid="$options.promoteActionTestId"
|
||||
@click="promoteToObjective"
|
||||
@action="promoteToObjective"
|
||||
>
|
||||
{{ __('Promote to objective') }}
|
||||
</gl-dropdown-item>
|
||||
<template #list-item>{{ __('Promote to objective') }}</template>
|
||||
</gl-disclosure-dropdown-item>
|
||||
<template v-if="canUpdate && !isParentConfidential">
|
||||
<gl-dropdown-item
|
||||
<gl-disclosure-dropdown-item
|
||||
:data-testid="$options.confidentialityTestId"
|
||||
@click="handleToggleWorkItemConfidentiality"
|
||||
>{{
|
||||
@action="handleToggleWorkItemConfidentiality"
|
||||
><template #list-item>{{
|
||||
isConfidential
|
||||
? $options.i18n.disableTaskConfidentiality
|
||||
: $options.i18n.enableTaskConfidentiality
|
||||
}}</gl-dropdown-item
|
||||
}}</template></gl-disclosure-dropdown-item
|
||||
>
|
||||
</template>
|
||||
<gl-dropdown-item
|
||||
<gl-disclosure-dropdown-item
|
||||
ref="workItemReference"
|
||||
:data-testid="$options.copyReferenceTestId"
|
||||
:data-clipboard-text="workItemReference"
|
||||
@click="copyToClipboard(workItemReference, $options.i18n.referenceCopied)"
|
||||
>{{ $options.i18n.copyReference }}</gl-dropdown-item
|
||||
@action="copyToClipboard(workItemReference, $options.i18n.referenceCopied)"
|
||||
><template #list-item>{{
|
||||
$options.i18n.copyReference
|
||||
}}</template></gl-disclosure-dropdown-item
|
||||
>
|
||||
<template v-if="$options.isLoggedIn && workItemCreateNoteEmail">
|
||||
<gl-dropdown-item
|
||||
<gl-disclosure-dropdown-item
|
||||
ref="workItemCreateNoteEmail"
|
||||
:data-testid="$options.copyCreateNoteEmailTestId"
|
||||
:data-clipboard-text="workItemCreateNoteEmail"
|
||||
@click="copyToClipboard(workItemCreateNoteEmail, $options.i18n.emailAddressCopied)"
|
||||
>{{ i18n.copyCreateNoteEmail }}</gl-dropdown-item
|
||||
@action="copyToClipboard(workItemCreateNoteEmail, $options.i18n.emailAddressCopied)"
|
||||
><template #list-item>{{
|
||||
i18n.copyCreateNoteEmail
|
||||
}}</template></gl-disclosure-dropdown-item
|
||||
>
|
||||
<gl-dropdown-divider v-if="canDelete" />
|
||||
</template>
|
||||
<gl-dropdown-item
|
||||
<gl-dropdown-divider v-if="canDelete" />
|
||||
<gl-disclosure-dropdown-item
|
||||
v-if="canDelete"
|
||||
:data-testid="$options.deleteActionTestId"
|
||||
variant="danger"
|
||||
@click="handleDelete"
|
||||
@action="handleDelete"
|
||||
>
|
||||
{{ i18n.deleteWorkItem }}
|
||||
</gl-dropdown-item>
|
||||
</gl-dropdown>
|
||||
<template #list-item
|
||||
><span class="text-danger">{{ i18n.deleteWorkItem }}</span></template
|
||||
>
|
||||
</gl-disclosure-dropdown-item>
|
||||
</gl-disclosure-dropdown>
|
||||
<gl-modal
|
||||
ref="modal"
|
||||
modal-id="work-item-confirm-delete"
|
||||
|
|
|
|||
|
|
@ -94,7 +94,7 @@ class Groups::LabelsController < Groups::ApplicationController
|
|||
|
||||
def label_params
|
||||
allowed = [:title, :description, :color]
|
||||
allowed << :lock_on_merge if Feature.enabled?(:enforce_locked_labels_on_merge, @project, type: :ops)
|
||||
allowed << :lock_on_merge if @group.supports_lock_on_merge?
|
||||
|
||||
params.require(:label).permit(allowed)
|
||||
end
|
||||
|
|
|
|||
|
|
@ -156,7 +156,7 @@ class Projects::LabelsController < Projects::ApplicationController
|
|||
|
||||
def label_params
|
||||
allowed = [:title, :description, :color]
|
||||
allowed << :lock_on_merge if Feature.enabled?(:enforce_locked_labels_on_merge, @project, type: :ops)
|
||||
allowed << :lock_on_merge if @project.supports_lock_on_merge?
|
||||
|
||||
params.require(:label).permit(allowed)
|
||||
end
|
||||
|
|
|
|||
|
|
@ -0,0 +1,16 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Ci
|
||||
class TriggersFinder
|
||||
def initialize(current_user, project)
|
||||
@current_user = current_user
|
||||
@project = project
|
||||
end
|
||||
|
||||
def execute
|
||||
return Ci::Trigger.none unless Ability.allowed?(@current_user, :admin_build, @project)
|
||||
|
||||
@project.triggers
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -10,7 +10,8 @@ module Resolvers
|
|||
type Types::Ci::PipelineTriggerType.connection_type, null: false
|
||||
|
||||
def resolve_with_lookahead
|
||||
apply_lookahead(object.triggers)
|
||||
triggers = ::Ci::TriggersFinder.new(current_user, object).execute
|
||||
apply_lookahead(triggers)
|
||||
end
|
||||
|
||||
private
|
||||
|
|
|
|||
|
|
@ -904,6 +904,10 @@ class Group < Namespace
|
|||
feature_flag_enabled_for_self_or_ancestor?(:linked_work_items)
|
||||
end
|
||||
|
||||
def supports_lock_on_merge?
|
||||
feature_flag_enabled_for_self_or_ancestor?(:enforce_locked_labels_on_merge, type: :ops)
|
||||
end
|
||||
|
||||
def usage_quotas_enabled?
|
||||
::Feature.enabled?(:usage_quotas_for_all_editions, self) && root?
|
||||
end
|
||||
|
|
@ -945,12 +949,12 @@ class Group < Namespace
|
|||
|
||||
private
|
||||
|
||||
def feature_flag_enabled_for_self_or_ancestor?(feature_flag)
|
||||
def feature_flag_enabled_for_self_or_ancestor?(feature_flag, type: :development)
|
||||
actors = [root_ancestor]
|
||||
actors << self if root_ancestor != self
|
||||
|
||||
actors.any? do |actor|
|
||||
::Feature.enabled?(feature_flag, actor)
|
||||
::Feature.enabled?(feature_flag, actor, type: type)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -737,7 +737,7 @@ class MergeRequest < ApplicationRecord
|
|||
def supports_lock_on_merge?
|
||||
return false unless merged?
|
||||
|
||||
Feature.enabled?(:enforce_locked_labels_on_merge, project, type: :ops)
|
||||
project.supports_lock_on_merge?
|
||||
end
|
||||
|
||||
# Calls `MergeWorker` to proceed with the merge process and
|
||||
|
|
|
|||
|
|
@ -3255,6 +3255,10 @@ class Project < ApplicationRecord
|
|||
group.crm_enabled?
|
||||
end
|
||||
|
||||
def supports_lock_on_merge?
|
||||
group&.supports_lock_on_merge? || ::Feature.enabled?(:enforce_locked_labels_on_merge, self, type: :ops)
|
||||
end
|
||||
|
||||
def path_availability
|
||||
base, _, host = path.partition('.')
|
||||
|
||||
|
|
|
|||
|
|
@ -13,9 +13,7 @@ module Labels
|
|||
project_or_group = target_params[:project] || target_params[:group]
|
||||
|
||||
if project_or_group.present?
|
||||
if Feature.disabled?(:enforce_locked_labels_on_merge, project_or_group, type: :ops)
|
||||
params.delete(:lock_on_merge)
|
||||
end
|
||||
params.delete(:lock_on_merge) unless project_or_group.supports_lock_on_merge?
|
||||
|
||||
project_or_group.labels.create(params)
|
||||
elsif target_params[:template]
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ module Labels
|
|||
def allow_lock_on_merge?(label)
|
||||
return if label.template?
|
||||
return unless label.respond_to?(:parent_container)
|
||||
return unless Feature.enabled?(:enforce_locked_labels_on_merge, label.parent_container, type: :ops)
|
||||
return unless label.parent_container.supports_lock_on_merge?
|
||||
|
||||
# If we've made it here, then we're allowed to turn it on. However, we do _not_
|
||||
# want to allow it to be turned off. So if it's already set, then don't allow the possibility
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
- add_to_breadcrumbs _("Labels"), group_labels_path(@group)
|
||||
- breadcrumb_title _("Edit")
|
||||
- page_title _("Edit"), @label.name, _("Labels")
|
||||
- show_lock_on_merge = Feature.enabled?(:enforce_locked_labels_on_merge, @group, type: :ops)
|
||||
- show_lock_on_merge = @group.supports_lock_on_merge?
|
||||
|
||||
%h1.page-title.gl-font-size-h-display
|
||||
= _('Edit Label')
|
||||
|
|
|
|||
|
|
@ -1,9 +1,17 @@
|
|||
- @content_class = "limit-container-width" unless fluid_layout
|
||||
- add_to_breadcrumbs _("Incidents"), project_incidents_path(@project)
|
||||
- breadcrumb_title @issue.to_reference
|
||||
|
||||
- page_title "#{@issue.title} (#{@issue.to_reference})", _("Incidents")
|
||||
- page_description @issue.description_html
|
||||
- page_card_attributes @issue.card_attributes
|
||||
- if @issue.relocation_target
|
||||
- page_canonical_link @issue.relocation_target.present(current_user: current_user).web_url
|
||||
|
||||
- add_page_specific_style 'page_bundles/design_management'
|
||||
- add_page_specific_style 'page_bundles/incidents'
|
||||
- add_page_specific_style 'page_bundles/issuable'
|
||||
- add_page_specific_style 'page_bundles/issues_show'
|
||||
|
||||
= render 'projects/issuable/show', issuable: @issue
|
||||
- @content_class = "limit-container-width" unless fluid_layout
|
||||
|
||||
= render 'projects/issues/details_content', issuable: @issue
|
||||
|
|
|
|||
|
|
@ -1,10 +0,0 @@
|
|||
- api_awards_path = local_assigns.fetch(:api_awards_path, nil)
|
||||
- page_description issuable.description_html
|
||||
- page_card_attributes issuable.card_attributes
|
||||
- if issuable.relocation_target
|
||||
- page_canonical_link issuable.relocation_target.present(current_user: current_user).web_url
|
||||
- add_page_specific_style 'page_bundles/issuable'
|
||||
|
||||
= render "projects/issues/service_desk/alert_moved_from_service_desk", issue: issuable
|
||||
|
||||
= render 'shared/issue_type/details_content', issuable: issuable, api_awards_path: api_awards_path
|
||||
|
|
@ -1,6 +1,8 @@
|
|||
- related_branches_path = related_branches_project_issue_path(@project, issuable)
|
||||
- api_awards_path = local_assigns.fetch(:api_awards_path, nil)
|
||||
|
||||
= render "projects/issues/service_desk/alert_moved_from_service_desk", issue: issuable
|
||||
|
||||
.issue-details.issuable-details.js-issue-details
|
||||
.detail-page-description.content-block.js-detail-page-description.gl-pt-3.gl-pb-0.gl-border-none
|
||||
#js-issuable-app{ data: { initial: issuable_initial_data(issuable).to_json,
|
||||
|
|
@ -18,10 +20,10 @@
|
|||
= edited_time_ago_with_tooltip(issuable, placement: 'bottom', html_class: 'issue-edited-ago js-issue-edited-ago')
|
||||
|
||||
.js-issue-widgets
|
||||
= render 'shared/issue_type/emoji_block', issuable: issuable, api_awards_path: api_awards_path
|
||||
= render 'projects/issues/emoji_block', issuable: issuable, api_awards_path: api_awards_path
|
||||
|
||||
.js-issue-widgets
|
||||
= render 'shared/issue_type/sentry_stack_trace', issuable: issuable
|
||||
= render 'projects/issues/sentry_stack_trace', issuable: issuable
|
||||
|
||||
= render 'projects/issues/design_management'
|
||||
|
||||
|
|
@ -1,10 +1,18 @@
|
|||
- @content_class = "limit-container-width" unless fluid_layout
|
||||
- add_to_breadcrumbs _("Issues"), project_issues_path(@project)
|
||||
- breadcrumb_title @issue.to_reference
|
||||
|
||||
- page_title "#{@issue.title} (#{@issue.to_reference})", _("Issues")
|
||||
- page_description @issue.description_html
|
||||
- page_card_attributes @issue.card_attributes
|
||||
- if @issue.relocation_target
|
||||
- page_canonical_link @issue.relocation_target.present(current_user: current_user).web_url
|
||||
|
||||
- add_page_specific_style 'page_bundles/design_management'
|
||||
- add_page_specific_style 'page_bundles/incidents'
|
||||
- add_page_specific_style 'page_bundles/issuable'
|
||||
- add_page_specific_style 'page_bundles/issues_show'
|
||||
- add_page_specific_style 'page_bundles/work_items'
|
||||
|
||||
= render 'projects/issuable/show', issuable: @issue, api_awards_path: award_emoji_issue_api_path(@issue)
|
||||
- @content_class = "limit-container-width" unless fluid_layout
|
||||
|
||||
= render 'projects/issues/details_content', issuable: @issue, api_awards_path: award_emoji_issue_api_path(@issue)
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
- add_to_breadcrumbs _("Labels"), project_labels_path(@project)
|
||||
- breadcrumb_title _("Edit")
|
||||
- page_title _("Edit"), @label.name, _("Labels")
|
||||
- show_lock_on_merge = Feature.enabled?(:enforce_locked_labels_on_merge, @project, type: :ops)
|
||||
- show_lock_on_merge = @project.supports_lock_on_merge?
|
||||
|
||||
%h1.page-title.gl-font-size-h-display
|
||||
= _('Edit Label')
|
||||
|
|
|
|||
|
|
@ -81,7 +81,6 @@
|
|||
- insider_threat
|
||||
- instance_resiliency
|
||||
- integrations
|
||||
- intel_code_security
|
||||
- interactive_application_security_testing
|
||||
- internationalization
|
||||
- logging
|
||||
|
|
@ -112,7 +111,6 @@
|
|||
- remote_development
|
||||
- requirements_management
|
||||
- review_apps
|
||||
- runbooks
|
||||
- runner
|
||||
- runner_fleet
|
||||
- runner_saas
|
||||
|
|
@ -142,4 +140,3 @@
|
|||
- web_ide
|
||||
- webhooks
|
||||
- wiki
|
||||
- workflow_automation
|
||||
|
|
|
|||
|
|
@ -0,0 +1,8 @@
|
|||
---
|
||||
name: use_primary_and_secondary_stores_for_action_cable
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/126451
|
||||
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/423216
|
||||
milestone: '16.4'
|
||||
type: development
|
||||
group: group::scalability
|
||||
default_enabled: false
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
---
|
||||
name: use_primary_store_as_default_for_action_cable
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/126451
|
||||
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/423216
|
||||
milestone: '16.4'
|
||||
type: development
|
||||
group: group::scalability
|
||||
default_enabled: false
|
||||
|
|
@ -16,7 +16,10 @@ ActionCable::SubscriptionAdapter::Redis.redis_connector = lambda do |config|
|
|||
args = config.except(:adapter, :channel_prefix)
|
||||
.merge(instrumentation_class: ::Gitlab::Instrumentation::Redis::ActionCable)
|
||||
|
||||
::Redis.new(args)
|
||||
primary_store = ::Redis.new(Gitlab::Redis::Pubsub.params)
|
||||
secondary_store = ::Redis.new(args)
|
||||
|
||||
Gitlab::Redis::MultiStore.new(primary_store, secondary_store, "ActionCable")
|
||||
end
|
||||
|
||||
Gitlab::ActionCable::RequestStoreCallbacks.install
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ table_name: merge_request_predictions
|
|||
classes:
|
||||
- MergeRequest::Predictions
|
||||
feature_categories:
|
||||
- workflow_automation
|
||||
- code_review_workflow
|
||||
description: Includes machine learning model predictions
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/97622
|
||||
milestone: '15.4'
|
||||
|
|
|
|||
|
|
@ -0,0 +1,10 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class RemoveNamespaceDetailsDashboardFields < Gitlab::Database::Migration[2.1]
|
||||
enable_lock_retries!
|
||||
|
||||
def change
|
||||
remove_column :namespace_details, :dashboard_notification_at, :datetime_with_timezone
|
||||
remove_column :namespace_details, :dashboard_enforcement_at, :datetime_with_timezone
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1 @@
|
|||
a4aec1b330059217fc5f93be7c3e4a6369b402b4919a10a26de4ba8352dee9a7
|
||||
|
|
@ -18974,9 +18974,7 @@ CREATE TABLE namespace_details (
|
|||
updated_at timestamp with time zone,
|
||||
cached_markdown_version integer,
|
||||
description text,
|
||||
description_html text,
|
||||
dashboard_notification_at timestamp with time zone,
|
||||
dashboard_enforcement_at timestamp with time zone
|
||||
description_html text
|
||||
);
|
||||
|
||||
CREATE TABLE namespace_ldap_settings (
|
||||
|
|
|
|||
|
|
@ -58,7 +58,7 @@ When a PAT is revoked from the credentials inventory, the instance notifies the
|
|||
|
||||
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/243833) in GitLab 14.8.
|
||||
|
||||
1. On the left sidebar, expand the top-most chevron (**{chevron-down}**).
|
||||
1. On the left sidebar, select **Search or go to**.
|
||||
1. Select **Admin Area**.
|
||||
1. Select **Credentials**.
|
||||
1. Select the **Project Access Tokens** tab.
|
||||
|
|
@ -72,7 +72,7 @@ The project access token is revoked and a background worker is queued to delete
|
|||
|
||||
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/225248) in GitLab 13.5.
|
||||
|
||||
1. On the left sidebar, expand the top-most chevron (**{chevron-down}**).
|
||||
1. On the left sidebar, select **Search or go to**.
|
||||
1. Select **Admin Area**.
|
||||
1. Select **Credentials**.
|
||||
1. Select the **SSH Keys** tab.
|
||||
|
|
|
|||
|
|
@ -164,6 +164,24 @@ You can also use personal, project, or group access tokens with OAuth-compliant
|
|||
curl --header "Authorization: Bearer <your_access_token>" "https://gitlab.example.com/api/v4/projects"
|
||||
```
|
||||
|
||||
### Job tokens
|
||||
|
||||
You can use job tokens to authenticate with [specific API endpoints](../../ci/jobs/ci_job_token.md)
|
||||
by passing the token in the `job_token` parameter or the `JOB-TOKEN` header.
|
||||
To pass the token in GitLab CI/CD jobs, use the `CI_JOB_TOKEN` variable.
|
||||
|
||||
Example of using the job token in a parameter:
|
||||
|
||||
```shell
|
||||
curl --location --output artifacts.zip "https://gitlab.example.com/api/v4/projects/1/jobs/42/artifacts?job_token=$CI_JOB_TOKEN"
|
||||
```
|
||||
|
||||
Example of using the job token in a header:
|
||||
|
||||
```shell
|
||||
curl --header "JOB-TOKEN:$CI_JOB_TOKEN" "https://gitlab.example.com/api/v4/projects/1/releases"
|
||||
```
|
||||
|
||||
### Session cookie
|
||||
|
||||
Signing in to the main GitLab application sets a `_gitlab_session` cookie. The
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ module Gitlab
|
|||
Gitlab::Redis::DbLoadBalancing,
|
||||
Gitlab::Redis::FeatureFlag,
|
||||
Gitlab::Redis::Queues,
|
||||
Gitlab::Redis::Pubsub,
|
||||
Gitlab::Redis::RateLimiting,
|
||||
Gitlab::Redis::RepositoryCache,
|
||||
Gitlab::Redis::Sessions,
|
||||
|
|
|
|||
|
|
@ -19,8 +19,12 @@ module Gitlab
|
|||
end
|
||||
|
||||
class MethodMissingError < StandardError
|
||||
def initialize(cmd)
|
||||
@cmd = cmd
|
||||
end
|
||||
|
||||
def message
|
||||
'Method missing. Falling back to execute method on the redis default store in Rails.env.production.'
|
||||
"Method missing #{@cmd}. Falling back to execute method on the redis default store in Rails.env.production."
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -38,6 +42,17 @@ module Gitlab
|
|||
|
||||
SKIP_LOG_METHOD_MISSING_FOR_COMMANDS = %i[info].freeze
|
||||
|
||||
# _client and without_reconnect are Redis::Client methods which may be called through multistore
|
||||
REDIS_CLIENT_COMMANDS = %i[
|
||||
_client
|
||||
without_reconnect
|
||||
].freeze
|
||||
|
||||
PUBSUB_SUBSCRIBE_COMMANDS = %i[
|
||||
subscribe
|
||||
unsubscribe
|
||||
].freeze
|
||||
|
||||
READ_COMMANDS = %i[
|
||||
exists
|
||||
exists?
|
||||
|
|
@ -71,6 +86,7 @@ module Gitlab
|
|||
incr
|
||||
incrby
|
||||
mapped_hmset
|
||||
publish
|
||||
rpush
|
||||
sadd
|
||||
sadd?
|
||||
|
|
@ -126,7 +142,7 @@ module Gitlab
|
|||
end
|
||||
|
||||
# rubocop:disable GitlabSecurity/PublicSend
|
||||
READ_COMMANDS.each do |name|
|
||||
(READ_COMMANDS + REDIS_CLIENT_COMMANDS + PUBSUB_SUBSCRIBE_COMMANDS).each do |name|
|
||||
define_method(name) do |*args, **kwargs, &block|
|
||||
if use_primary_and_secondary_stores?
|
||||
read_command(name, *args, **kwargs, &block)
|
||||
|
|
@ -246,9 +262,9 @@ module Gitlab
|
|||
def log_method_missing(command_name, *_args)
|
||||
return if SKIP_LOG_METHOD_MISSING_FOR_COMMANDS.include?(command_name)
|
||||
|
||||
raise MethodMissingError if Rails.env.test? || Rails.env.development?
|
||||
raise MethodMissingError, command_name if Rails.env.test? || Rails.env.development?
|
||||
|
||||
log_error(MethodMissingError.new, command_name)
|
||||
log_error(MethodMissingError.new(command_name), command_name)
|
||||
increment_method_missing_count(command_name)
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,13 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Gitlab
|
||||
module Redis
|
||||
class Pubsub < ::Gitlab::Redis::Wrapper
|
||||
class << self
|
||||
def config_fallback
|
||||
SharedState
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -14675,6 +14675,9 @@ msgstr ""
|
|||
msgid "DORA4Metrics|Merge request throughput"
|
||||
msgstr ""
|
||||
|
||||
msgid "DORA4Metrics|Metrics comparison for %{name}"
|
||||
msgstr ""
|
||||
|
||||
msgid "DORA4Metrics|Metrics comparison for %{name} group"
|
||||
msgstr ""
|
||||
|
||||
|
|
|
|||
|
|
@ -136,7 +136,7 @@
|
|||
"deckar01-task_list": "^2.3.1",
|
||||
"dexie": "^3.2.3",
|
||||
"diff": "^3.4.0",
|
||||
"dompurify": "^2.4.5",
|
||||
"dompurify": "^3.0.5",
|
||||
"dropzone": "^4.2.0",
|
||||
"editorconfig": "^0.15.3",
|
||||
"emoji-regex": "^10.0.0",
|
||||
|
|
@ -210,7 +210,7 @@
|
|||
"visibilityjs": "^1.2.4",
|
||||
"vue": "2.7.14",
|
||||
"vue-apollo": "^3.0.7",
|
||||
"vue-loader": "15.10.1",
|
||||
"vue-loader": "15.10.2",
|
||||
"vue-observe-visibility": "^1.0.0",
|
||||
"vue-resize": "^1.0.1",
|
||||
"vue-router": "3.6.5",
|
||||
|
|
@ -223,7 +223,7 @@
|
|||
"web-streams-polyfill": "^3.2.1",
|
||||
"web-vitals": "^0.2.4",
|
||||
"webpack": "^4.46.0",
|
||||
"webpack-bundle-analyzer": "^4.9.0",
|
||||
"webpack-bundle-analyzer": "^4.9.1",
|
||||
"webpack-cli": "^4.10.0",
|
||||
"webpack-stats-plugin": "^0.3.1",
|
||||
"worker-loader": "^2.0.0",
|
||||
|
|
@ -290,7 +290,7 @@
|
|||
"timezone-mock": "^1.0.8",
|
||||
"vite": "^4.4.9",
|
||||
"vite-plugin-ruby": "^3.2.2",
|
||||
"vite-svg-loader": "^3.4.0",
|
||||
"vite-svg-loader": "^3.6.0",
|
||||
"vue-loader-vue3": "npm:vue-loader@17",
|
||||
"vue-test-utils-compat": "0.0.14",
|
||||
"vuex-mock-store": "^0.1.0",
|
||||
|
|
|
|||
|
|
@ -138,6 +138,10 @@ module QA
|
|||
element :close_button
|
||||
end
|
||||
|
||||
view 'app/assets/javascripts/jobs/components/table/cells/job_cell.vue' do
|
||||
element 'fork-icon'
|
||||
end
|
||||
|
||||
def start_review
|
||||
click_element(:start_review_button)
|
||||
|
||||
|
|
@ -476,6 +480,10 @@ module QA
|
|||
def mr_widget_text
|
||||
find_element(:mr_widget_content).text
|
||||
end
|
||||
|
||||
def has_fork_icon?
|
||||
has_element?('fork-icon', skip_finished_loading_check: true)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -149,6 +149,14 @@ module QA
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
def has_no_skipped_job_in_group?
|
||||
within_element(:jobs_dropdown_menu) do
|
||||
all_elements(:job_item_container, minimum: 1).all? do
|
||||
has_no_selector?('.ci-status-icon-skipped')
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -27,7 +27,9 @@ module QA
|
|||
Flow::Login.sign_in_unless_signed_in(user: fork.user)
|
||||
Page::Project::Show.perform(&:new_merge_request)
|
||||
Page::MergeRequest::New.perform(&:create_merge_request)
|
||||
Support::WaitForRequests.wait_for_requests
|
||||
Support::Waiter.wait_until(message: 'Waiting for fork icon to appear') do
|
||||
Page::MergeRequest::Show.perform(&:has_fork_icon?)
|
||||
end
|
||||
mr_url = current_url
|
||||
|
||||
# Sign back in as original user
|
||||
|
|
|
|||
|
|
@ -31,6 +31,7 @@ module QA
|
|||
incident
|
||||
framework
|
||||
delete_issue_button
|
||||
skipped_job_in_group
|
||||
].each do |predicate|
|
||||
RSpec::Matchers.define "have_#{predicate}" do |*args, **kwargs|
|
||||
match do |page_object|
|
||||
|
|
|
|||
|
|
@ -3,7 +3,8 @@
|
|||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Groups::LabelsController, feature_category: :team_planning do
|
||||
let_it_be(:group) { create(:group) }
|
||||
let_it_be(:root_group) { create(:group) }
|
||||
let_it_be(:group) { create(:group, parent: root_group) }
|
||||
let_it_be(:user) { create(:user) }
|
||||
let_it_be(:project) { create(:project, namespace: group) }
|
||||
|
||||
|
|
@ -142,14 +143,30 @@ RSpec.describe Groups::LabelsController, feature_category: :team_planning do
|
|||
end
|
||||
end
|
||||
|
||||
context 'when feature flag is enabled' do
|
||||
it 'allows setting lock_on_merge' do
|
||||
shared_examples 'allows setting lock_on_merge' do
|
||||
it do
|
||||
update_request
|
||||
|
||||
expect(response).to redirect_to(group_labels_path)
|
||||
expect(label.reload.lock_on_merge).to be_truthy
|
||||
end
|
||||
end
|
||||
|
||||
context 'when feature flag for group is enabled' do
|
||||
before do
|
||||
stub_feature_flags(enforce_locked_labels_on_merge: group)
|
||||
end
|
||||
|
||||
it_behaves_like 'allows setting lock_on_merge'
|
||||
end
|
||||
|
||||
context 'when feature flag for ancestor group is enabled' do
|
||||
before do
|
||||
stub_feature_flags(enforce_locked_labels_on_merge: root_group)
|
||||
end
|
||||
|
||||
it_behaves_like 'allows setting lock_on_merge'
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -317,14 +317,30 @@ RSpec.describe Projects::LabelsController, feature_category: :team_planning do
|
|||
end
|
||||
end
|
||||
|
||||
context 'when feature flag is enabled' do
|
||||
it 'allows setting lock_on_merge' do
|
||||
shared_examples 'allows setting lock_on_merge' do
|
||||
it do
|
||||
update_request
|
||||
|
||||
expect(response).to redirect_to(namespace_project_labels_path)
|
||||
expect(label.reload.lock_on_merge).to be_truthy
|
||||
end
|
||||
end
|
||||
|
||||
context 'when feature flag is enabled' do
|
||||
before do
|
||||
stub_feature_flags(enforce_locked_labels_on_merge: project)
|
||||
end
|
||||
|
||||
it_behaves_like 'allows setting lock_on_merge'
|
||||
end
|
||||
|
||||
context 'when feature flag for ancestor group is enabled' do
|
||||
before do
|
||||
stub_feature_flags(enforce_locked_labels_on_merge: group)
|
||||
end
|
||||
|
||||
it_behaves_like 'allows setting lock_on_merge'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -718,14 +718,14 @@ RSpec.describe 'New/edit issue', :js, feature_category: :team_planning do
|
|||
end
|
||||
|
||||
describe 'when user has made changes' do
|
||||
it 'shows a warning and can leave page', quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/410497' do
|
||||
it 'shows a warning and can leave page' do
|
||||
content = 'new issue content'
|
||||
find('body').send_keys('e')
|
||||
fill_in 'issue-description', with: content
|
||||
|
||||
click_link 'Boards'
|
||||
|
||||
page.driver.browser.switch_to.alert.accept
|
||||
click_link 'Boards' do
|
||||
page.driver.browser.switch_to.alert.accept
|
||||
end
|
||||
|
||||
expect(page).not_to have_content(content)
|
||||
end
|
||||
|
|
|
|||
|
|
@ -646,7 +646,7 @@ RSpec.describe 'Merge request > User sees merge widget', :js, feature_category:
|
|||
click_expand_button
|
||||
|
||||
within('[data-testid="widget-extension-collapsed-section"]') do
|
||||
click_link 'addTest'
|
||||
click_button 'View details'
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -693,7 +693,7 @@ RSpec.describe 'Merge request > User sees merge widget', :js, feature_category:
|
|||
click_expand_button
|
||||
|
||||
within('[data-testid="widget-extension-collapsed-section"]') do
|
||||
click_link 'Test#sum when a is 1 and b is 3 returns summary'
|
||||
click_button 'View details'
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -741,7 +741,7 @@ RSpec.describe 'Merge request > User sees merge widget', :js, feature_category:
|
|||
click_expand_button
|
||||
|
||||
within('[data-testid="widget-extension-collapsed-section"]') do
|
||||
click_link 'addTest'
|
||||
click_button 'View details'
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -788,7 +788,7 @@ RSpec.describe 'Merge request > User sees merge widget', :js, feature_category:
|
|||
click_expand_button
|
||||
|
||||
within('[data-testid="widget-extension-collapsed-section"]') do
|
||||
click_link 'addTest'
|
||||
click_button 'View details'
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -834,7 +834,7 @@ RSpec.describe 'Merge request > User sees merge widget', :js, feature_category:
|
|||
click_expand_button
|
||||
|
||||
within('[data-testid="widget-extension-collapsed-section"]') do
|
||||
click_link 'Test#sum when a is 4 and b is 4 returns summary'
|
||||
click_button 'View details'
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -881,7 +881,7 @@ RSpec.describe 'Merge request > User sees merge widget', :js, feature_category:
|
|||
click_expand_button
|
||||
|
||||
within('[data-testid="widget-extension-collapsed-section"]') do
|
||||
click_link 'addTest'
|
||||
click_button 'View details'
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,29 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Ci::TriggersFinder, feature_category: :continuous_integration do
|
||||
let_it_be(:project) { create(:project) }
|
||||
let_it_be(:current_user) { create(:user) }
|
||||
let_it_be(:trigger) { create(:ci_trigger, project: project) }
|
||||
|
||||
subject { described_class.new(current_user, project).execute }
|
||||
|
||||
describe "#execute" do
|
||||
context 'when the current user is authorized' do
|
||||
before_all do
|
||||
project.add_owner(current_user)
|
||||
end
|
||||
|
||||
it 'returns list of trigger tokens' do
|
||||
expect(subject).to contain_exactly(trigger)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the current user is not authorized' do
|
||||
it 'does not return trigger tokens' do
|
||||
expect(subject).to be_blank
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -1,13 +1,11 @@
|
|||
import { nextTick } from 'vue';
|
||||
import MockAdapter from 'axios-mock-adapter';
|
||||
import testReportExtension from '~/vue_merge_request_widget/extensions/test_report';
|
||||
import testReportExtension from '~/vue_merge_request_widget/extensions/test_report/index.vue';
|
||||
import { i18n } from '~/vue_merge_request_widget/extensions/test_report/constants';
|
||||
import { mountExtended } from 'helpers/vue_test_utils_helper';
|
||||
import { trimText } from 'helpers/text_helper';
|
||||
import waitForPromises from 'helpers/wait_for_promises';
|
||||
import axios from '~/lib/utils/axios_utils';
|
||||
import extensionsContainer from '~/vue_merge_request_widget/components/extensions/container';
|
||||
import { registerExtension } from '~/vue_merge_request_widget/components/extensions';
|
||||
import {
|
||||
HTTP_STATUS_INTERNAL_SERVER_ERROR,
|
||||
HTTP_STATUS_NO_CONTENT,
|
||||
|
|
@ -34,12 +32,10 @@ describe('Test report extension', () => {
|
|||
let wrapper;
|
||||
let mock;
|
||||
|
||||
registerExtension(testReportExtension);
|
||||
|
||||
const endpoint = '/root/repo/-/merge_requests/4/test_reports.json';
|
||||
|
||||
const mockApi = (statusCode, data = mixedResultsTestReports) => {
|
||||
mock.onGet(endpoint).reply(statusCode, data);
|
||||
mock.onGet(endpoint).reply(statusCode, data, {});
|
||||
};
|
||||
|
||||
const findToggleCollapsedButton = () => wrapper.findByTestId('toggle-button');
|
||||
|
|
@ -49,7 +45,7 @@ describe('Test report extension', () => {
|
|||
const findModal = () => wrapper.findComponent(TestCaseDetails);
|
||||
|
||||
const createComponent = () => {
|
||||
wrapper = mountExtended(extensionsContainer, {
|
||||
wrapper = mountExtended(testReportExtension, {
|
||||
propsData: {
|
||||
mr: {
|
||||
testResultsPath: endpoint,
|
||||
|
|
@ -84,7 +80,7 @@ describe('Test report extension', () => {
|
|||
expect(wrapper.text()).toContain(i18n.loading);
|
||||
});
|
||||
|
||||
it('with a 204 response, continues to display loading state', async () => {
|
||||
it('with a "no content" response, continues to display loading state', async () => {
|
||||
mockApi(HTTP_STATUS_NO_CONTENT, '');
|
||||
createComponent();
|
||||
|
||||
|
|
@ -269,7 +265,7 @@ describe('Test report extension', () => {
|
|||
beforeEach(async () => {
|
||||
await createExpandedWidgetWithData();
|
||||
|
||||
wrapper.findByTestId('modal-link').trigger('click');
|
||||
wrapper.findByTestId('extension-actions-button').trigger('click');
|
||||
});
|
||||
|
||||
it('opens a modal to display test case details', () => {
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import axios from 'axios';
|
|||
import MockAdapter from 'axios-mock-adapter';
|
||||
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
|
||||
import SourceViewer from '~/vue_shared/components/source_viewer/source_viewer.vue';
|
||||
import CodeownersValidation from 'ee_component/blob/components/codeowners_validation.vue';
|
||||
import { registerPlugins } from '~/vue_shared/components/source_viewer/plugins/index';
|
||||
import Chunk from '~/vue_shared/components/source_viewer/components/chunk.vue';
|
||||
import {
|
||||
|
|
@ -55,11 +56,13 @@ describe('Source Viewer component', () => {
|
|||
const fileType = 'javascript';
|
||||
const DEFAULT_BLOB_DATA = { language, rawTextBlob: content, path, blamePath, fileType };
|
||||
const highlightedContent = `<span data-testid='test-highlighted' id='LC1'>${content}</span><span id='LC2'></span>`;
|
||||
const currentRef = 'main';
|
||||
const projectPath = 'test/project';
|
||||
|
||||
const createComponent = async (blob = {}) => {
|
||||
wrapper = shallowMountExtended(SourceViewer, {
|
||||
router,
|
||||
propsData: { blob: { ...DEFAULT_BLOB_DATA, ...blob } },
|
||||
propsData: { blob: { ...DEFAULT_BLOB_DATA, ...blob }, currentRef, projectPath },
|
||||
});
|
||||
await waitForPromises();
|
||||
};
|
||||
|
|
@ -269,4 +272,18 @@ describe('Source Viewer component', () => {
|
|||
expect(LineHighlighter).toHaveBeenCalledWith({ scrollBehavior: 'auto' });
|
||||
});
|
||||
});
|
||||
|
||||
describe('Codeowners validation', () => {
|
||||
const findCodeownersValidation = () => wrapper.findComponent(CodeownersValidation);
|
||||
|
||||
it('does not render codeowners validation when file is not CODEOWNERS', async () => {
|
||||
await createComponent();
|
||||
expect(findCodeownersValidation().exists()).toBe(false);
|
||||
});
|
||||
|
||||
it('renders codeowners validation when file is CODEOWNERS', async () => {
|
||||
await createComponent({ name: CODEOWNERS_FILE_NAME });
|
||||
expect(findCodeownersValidation().exists()).toBe(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { GlDropdownDivider, GlModal, GlToggle } from '@gitlab/ui';
|
||||
import { GlDisclosureDropdown, GlDropdownDivider, GlModal, GlToggle } from '@gitlab/ui';
|
||||
import Vue from 'vue';
|
||||
import VueApollo from 'vue-apollo';
|
||||
|
||||
|
|
@ -55,7 +55,7 @@ describe('WorkItemActions component', () => {
|
|||
const findCopyReferenceButton = () => wrapper.findByTestId(TEST_ID_COPY_REFERENCE_ACTION);
|
||||
const findCopyCreateNoteEmailButton = () =>
|
||||
wrapper.findByTestId(TEST_ID_COPY_CREATE_NOTE_EMAIL_ACTION);
|
||||
const findDropdownItems = () => wrapper.findAll('[data-testid="work-item-actions-dropdown"] > *');
|
||||
const findDropdownItems = () => wrapper.findAll('[data-testid="disclosure-content"] > *');
|
||||
const findDropdownItemsActual = () =>
|
||||
findDropdownItems().wrappers.map((x) => {
|
||||
if (x.is(GlDropdownDivider)) {
|
||||
|
|
@ -143,6 +143,7 @@ describe('WorkItemActions component', () => {
|
|||
show: modalShowSpy,
|
||||
},
|
||||
}),
|
||||
GlDisclosureDropdown,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
|
@ -208,7 +209,7 @@ describe('WorkItemActions component', () => {
|
|||
it('emits `toggleWorkItemConfidentiality` event when clicked', () => {
|
||||
createComponent();
|
||||
|
||||
findConfidentialityToggleButton().vm.$emit('click');
|
||||
findConfidentialityToggleButton().vm.$emit('action');
|
||||
|
||||
expect(wrapper.emitted('toggleWorkItemConfidentiality')[0]).toEqual([true]);
|
||||
});
|
||||
|
|
@ -228,7 +229,7 @@ describe('WorkItemActions component', () => {
|
|||
it('shows confirm modal when clicked', () => {
|
||||
createComponent();
|
||||
|
||||
findDeleteButton().vm.$emit('click');
|
||||
findDeleteButton().vm.$emit('action');
|
||||
|
||||
expect(modalShowSpy).toHaveBeenCalled();
|
||||
});
|
||||
|
|
@ -359,7 +360,7 @@ describe('WorkItemActions component', () => {
|
|||
await waitForPromises();
|
||||
|
||||
expect(findPromoteButton().exists()).toBe(true);
|
||||
findPromoteButton().vm.$emit('click');
|
||||
findPromoteButton().vm.$emit('action');
|
||||
|
||||
await waitForPromises();
|
||||
|
||||
|
|
@ -378,7 +379,7 @@ describe('WorkItemActions component', () => {
|
|||
await waitForPromises();
|
||||
|
||||
expect(findPromoteButton().exists()).toBe(true);
|
||||
findPromoteButton().vm.$emit('click');
|
||||
findPromoteButton().vm.$emit('action');
|
||||
|
||||
await waitForPromises();
|
||||
|
||||
|
|
@ -394,7 +395,7 @@ describe('WorkItemActions component', () => {
|
|||
createComponent();
|
||||
|
||||
expect(findCopyReferenceButton().exists()).toBe(true);
|
||||
findCopyReferenceButton().vm.$emit('click');
|
||||
findCopyReferenceButton().vm.$emit('action');
|
||||
|
||||
expect(toast).toHaveBeenCalledWith('Reference copied');
|
||||
});
|
||||
|
|
@ -416,7 +417,7 @@ describe('WorkItemActions component', () => {
|
|||
createComponent();
|
||||
|
||||
expect(findCopyCreateNoteEmailButton().exists()).toBe(true);
|
||||
findCopyCreateNoteEmailButton().vm.$emit('click');
|
||||
findCopyCreateNoteEmailButton().vm.$emit('action');
|
||||
|
||||
expect(toast).toHaveBeenCalledWith('Email address copied');
|
||||
});
|
||||
|
|
|
|||
|
|
@ -27,7 +27,8 @@ RSpec.describe 'ActionCableSubscriptionAdapterIdentifier override' do
|
|||
|
||||
sub = ActionCable.server.pubsub.send(:redis_connection)
|
||||
|
||||
expect(sub.connection[:id]).to eq('unix:///home/localuser/redis/redis.socket/0')
|
||||
expect(sub.is_a?(::Gitlab::Redis::MultiStore)).to eq(true)
|
||||
expect(sub.secondary_store.connection[:id]).to eq('unix:///home/localuser/redis/redis.socket/0')
|
||||
expect(ActionCable.server.config.cable[:id]).to be_nil
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -1130,4 +1130,104 @@ RSpec.describe Gitlab::Redis::MultiStore, feature_category: :redis do
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
# NOTE: for pub/sub, unit tests are favoured over integration tests to avoid long polling
|
||||
# with threads which could lead to flaky specs. The multiplexing behaviour are verified in
|
||||
# 'with WRITE redis commands' and 'with READ redis commands' contexts.
|
||||
context 'with pub/sub commands' do
|
||||
let(:channel_name) { 'chanA' }
|
||||
let(:message) { "msg" }
|
||||
|
||||
shared_examples 'publishes to stores' do
|
||||
it 'publishes to one or more stores' do
|
||||
expect(stores).to all(receive(:publish))
|
||||
|
||||
multi_store.publish(channel_name, message)
|
||||
end
|
||||
end
|
||||
|
||||
shared_examples 'subscribes and unsubscribes' do
|
||||
it 'subscribes to the default store' do
|
||||
expect(default_store).to receive(:subscribe)
|
||||
expect(non_default_store).not_to receive(:subscribe)
|
||||
|
||||
multi_store.subscribe(channel_name)
|
||||
end
|
||||
|
||||
it 'unsubscribes to the default store' do
|
||||
expect(default_store).to receive(:unsubscribe)
|
||||
expect(non_default_store).not_to receive(:unsubscribe)
|
||||
|
||||
multi_store.unsubscribe
|
||||
end
|
||||
end
|
||||
|
||||
context 'when using both stores' do
|
||||
before do
|
||||
stub_feature_flags(use_primary_and_secondary_stores_for_test_store: true)
|
||||
end
|
||||
|
||||
it_behaves_like 'publishes to stores' do
|
||||
let(:stores) { [primary_store, secondary_store] }
|
||||
end
|
||||
|
||||
context 'with primary store set as default' do
|
||||
before do
|
||||
stub_feature_flags(use_primary_store_as_default_for_test_store: true)
|
||||
end
|
||||
|
||||
it_behaves_like 'subscribes and unsubscribes' do
|
||||
let(:default_store) { primary_store }
|
||||
let(:non_default_store) { secondary_store }
|
||||
end
|
||||
end
|
||||
|
||||
context 'with secondary store set as default' do
|
||||
before do
|
||||
stub_feature_flags(use_primary_store_as_default_for_test_store: false)
|
||||
end
|
||||
|
||||
it_behaves_like 'subscribes and unsubscribes' do
|
||||
let(:default_store) { secondary_store }
|
||||
let(:non_default_store) { primary_store }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when only using the primary store' do
|
||||
before do
|
||||
stub_feature_flags(
|
||||
use_primary_and_secondary_stores_for_test_store: false,
|
||||
use_primary_store_as_default_for_test_store: true
|
||||
)
|
||||
end
|
||||
|
||||
it_behaves_like 'subscribes and unsubscribes' do
|
||||
let(:default_store) { primary_store }
|
||||
let(:non_default_store) { secondary_store }
|
||||
end
|
||||
|
||||
it_behaves_like 'publishes to stores' do
|
||||
let(:stores) { [primary_store] }
|
||||
end
|
||||
end
|
||||
|
||||
context 'when only using the secondary store' do
|
||||
before do
|
||||
stub_feature_flags(
|
||||
use_primary_and_secondary_stores_for_test_store: false,
|
||||
use_primary_store_as_default_for_test_store: false
|
||||
)
|
||||
end
|
||||
|
||||
it_behaves_like 'subscribes and unsubscribes' do
|
||||
let(:default_store) { secondary_store }
|
||||
let(:non_default_store) { primary_store }
|
||||
end
|
||||
|
||||
it_behaves_like 'publishes to stores' do
|
||||
let(:stores) { [secondary_store] }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -0,0 +1,8 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Gitlab::Redis::Pubsub, feature_category: :redis do
|
||||
include_examples "redis_new_instance_shared_examples", 'pubsub', Gitlab::Redis::SharedState
|
||||
include_examples "redis_shared_examples"
|
||||
end
|
||||
|
|
@ -3309,6 +3309,13 @@ RSpec.describe Group, feature_category: :groups_and_projects do
|
|||
end
|
||||
end
|
||||
|
||||
describe '#supports_lock_on_merge?' do
|
||||
it_behaves_like 'checks self and root ancestor feature flag' do
|
||||
let(:feature_flag) { :enforce_locked_labels_on_merge }
|
||||
let(:feature_flag_method) { :supports_lock_on_merge? }
|
||||
end
|
||||
end
|
||||
|
||||
describe 'group shares' do
|
||||
let!(:sub_group) { create(:group, parent: group) }
|
||||
let!(:sub_sub_group) { create(:group, parent: sub_group) }
|
||||
|
|
|
|||
|
|
@ -9164,6 +9164,13 @@ RSpec.describe Project, factory_default: :keep, feature_category: :groups_and_pr
|
|||
end
|
||||
end
|
||||
|
||||
describe '#supports_lock_on_merge?' do
|
||||
it_behaves_like 'checks self (project) and root ancestor feature flag' do
|
||||
let(:feature_flag) { :enforce_locked_labels_on_merge }
|
||||
let(:feature_flag_method) { :supports_lock_on_merge? }
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def finish_job(export_job)
|
||||
|
|
|
|||
|
|
@ -56,15 +56,21 @@ RSpec.describe 'getting project information', feature_category: :groups_and_proj
|
|||
expect { post_graphql(query, current_user: current_user) }.not_to exceed_query_limit(baseline)
|
||||
end
|
||||
|
||||
context 'when other project member is not authorized to see the full token' do
|
||||
context 'when another project member or owner who is not also the token owner' do
|
||||
before do
|
||||
project.add_maintainer(other_user)
|
||||
project.add_owner(other_user)
|
||||
post_graphql(query, current_user: other_user)
|
||||
end
|
||||
|
||||
it 'shows truncated token' do
|
||||
expect(graphql_data_at(:project, :pipeline_triggers,
|
||||
:nodes).first['token']).to eql pipeline_trigger.token[0, 4]
|
||||
it 'is not authorized and shows truncated token' do
|
||||
expect(graphql_data_at(:project, :pipeline_triggers, :nodes).first).to match({
|
||||
'id' => pipeline_trigger.to_global_id.to_s,
|
||||
'canAccessProject' => true,
|
||||
'description' => pipeline_trigger.description,
|
||||
'hasTokenExposed' => false,
|
||||
'lastUsed' => nil,
|
||||
'token' => pipeline_trigger.token[0, 4]
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -35,9 +35,11 @@ module Capybara
|
|||
end
|
||||
|
||||
module WaitForAllRequestsAfterClickLink
|
||||
def click_link(locator = nil, **options)
|
||||
def click_link(locator = nil, **options, &block)
|
||||
super
|
||||
|
||||
yield if block
|
||||
|
||||
wait_for_all_requests
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
RSpec.shared_examples 'search results filtered by archived' do |feature_flag_name|
|
||||
RSpec.shared_examples 'search results filtered by archived' do |feature_flag_name, migration_name|
|
||||
context 'when filter not provided (all behavior)' do
|
||||
let(:filters) { {} }
|
||||
|
||||
|
|
@ -42,4 +42,19 @@ RSpec.shared_examples 'search results filtered by archived' do |feature_flag_nam
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
if migration_name.present?
|
||||
context "when the #{migration_name} is not completed" do
|
||||
let(:filters) { {} }
|
||||
|
||||
before do
|
||||
set_elasticsearch_migration_to(migration_name.to_s, including: false)
|
||||
end
|
||||
|
||||
it 'returns archived and unarchived results' do
|
||||
expect(results.objects(scope)).to include unarchived_result
|
||||
expect(results.objects(scope)).to include archived_result
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@
|
|||
RSpec.shared_examples 'checks self and root ancestor feature flag' do
|
||||
let_it_be(:root_group) { create(:group) }
|
||||
let_it_be(:group) { create(:group, parent: root_group) }
|
||||
let_it_be(:project) { create(:project, group: group) }
|
||||
|
||||
subject { group.public_send(feature_flag_method) }
|
||||
|
||||
|
|
@ -41,3 +40,47 @@ RSpec.shared_examples 'checks self and root ancestor feature flag' do
|
|||
it { is_expected.to be_truthy }
|
||||
end
|
||||
end
|
||||
|
||||
RSpec.shared_examples 'checks self (project) and root ancestor feature flag' do
|
||||
let_it_be(:root_group) { create(:group) }
|
||||
let_it_be(:group) { create(:group, parent: root_group) }
|
||||
let_it_be(:project) { create(:project, group: group) }
|
||||
|
||||
subject { project.public_send(feature_flag_method) }
|
||||
|
||||
context 'when FF is enabled for the root group' do
|
||||
before do
|
||||
stub_feature_flags(feature_flag => root_group)
|
||||
end
|
||||
|
||||
it { is_expected.to be_truthy }
|
||||
end
|
||||
|
||||
context 'when FF is enabled for the group' do
|
||||
before do
|
||||
stub_feature_flags(feature_flag => group)
|
||||
end
|
||||
|
||||
it { is_expected.to be_truthy }
|
||||
end
|
||||
|
||||
context 'when FF is enabled for the project' do
|
||||
before do
|
||||
stub_feature_flags(feature_flag => project)
|
||||
end
|
||||
|
||||
it { is_expected.to be_truthy }
|
||||
end
|
||||
|
||||
context 'when FF is disabled globally' do
|
||||
before do
|
||||
stub_feature_flags(feature_flag => false)
|
||||
end
|
||||
|
||||
it { is_expected.to be_falsey }
|
||||
end
|
||||
|
||||
context 'when FF is enabled globally' do
|
||||
it { is_expected.to be_truthy }
|
||||
end
|
||||
end
|
||||
|
|
|
|||
86
yarn.lock
86
yarn.lock
|
|
@ -1829,10 +1829,10 @@
|
|||
resolved "https://registry.yarnpkg.com/@pkgjs/parseargs/-/parseargs-0.11.0.tgz#a77ea742fab25775145434eb1d2328cf5013ac33"
|
||||
integrity sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==
|
||||
|
||||
"@polka/url@^1.0.0-next.9":
|
||||
version "1.0.0-next.12"
|
||||
resolved "https://registry.yarnpkg.com/@polka/url/-/url-1.0.0-next.12.tgz#431ec342a7195622f86688bbda82e3166ce8cb28"
|
||||
integrity sha512-6RglhutqrGFMO1MNUXp95RBuYIuc8wTnMAV5MUhLmjTOy78ncwOw7RgeQ/HeymkKXRhZd0s2DNrM1rL7unk3MQ==
|
||||
"@polka/url@^1.0.0-next.20":
|
||||
version "1.0.0-next.21"
|
||||
resolved "https://registry.yarnpkg.com/@polka/url/-/url-1.0.0-next.21.tgz#5de5a2385a35309427f6011992b544514d559aa1"
|
||||
integrity sha512-a5Sab1C4/icpTZVzZc5Ghpz88yQtGOyNqYXcZgOssB2uuAr+wF/MvN6bgtW32q7HHrvBki+BsZ0OuNv6EV3K9g==
|
||||
|
||||
"@popperjs/core@^2.11.2", "@popperjs/core@^2.9.0":
|
||||
version "2.11.5"
|
||||
|
|
@ -5636,11 +5636,6 @@ dommatrix@^1.0.3:
|
|||
resolved "https://registry.yarnpkg.com/dommatrix/-/dommatrix-1.0.3.tgz#e7c18e8d6f3abdd1fef3dd4aa74c4d2e620a0525"
|
||||
integrity sha512-l32Xp/TLgWb8ReqbVJAFIvXmY7go4nTxxlWiAFyhoQw9RKEOHBZNnyGvJWqDVSPmq3Y9HlM4npqF/T6VMOXhww==
|
||||
|
||||
dompurify@^2.4.5:
|
||||
version "2.4.5"
|
||||
resolved "https://registry.yarnpkg.com/dompurify/-/dompurify-2.4.5.tgz#0e89a27601f0bad978f9a924e7a05d5d2cccdd87"
|
||||
integrity sha512-jggCCd+8Iqp4Tsz0nIvpcb22InKEBrGz5dw3EQJMs8HPJDsKbFIO3STYtAvCfDx26Muevn1MHVI0XxjgFfmiSA==
|
||||
|
||||
dompurify@^3.0.5:
|
||||
version "3.0.5"
|
||||
resolved "https://registry.yarnpkg.com/dompurify/-/dompurify-3.0.5.tgz#eb3d9cfa10037b6e73f32c586682c4b2ab01fbed"
|
||||
|
|
@ -8862,6 +8857,11 @@ lodash.debounce@^4.0.8:
|
|||
resolved "https://registry.yarnpkg.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz#82d79bff30a67c4005ffd5e2515300ad9ca4d7af"
|
||||
integrity sha1-gteb/zCmfEAF/9XiUVMArZyk168=
|
||||
|
||||
lodash.escape@^4.0.1:
|
||||
version "4.0.1"
|
||||
resolved "https://registry.yarnpkg.com/lodash.escape/-/lodash.escape-4.0.1.tgz#c9044690c21e04294beaa517712fded1fa88de98"
|
||||
integrity sha512-nXEOnb/jK9g0DYMr1/Xvq6l5xMD7GDG55+GSYIYmS0G4tBk/hURD4JR9WCavs04t33WmJx9kCyp9vJ+mr4BOUw==
|
||||
|
||||
lodash.find@^4.6.0:
|
||||
version "4.6.0"
|
||||
resolved "https://registry.yarnpkg.com/lodash.find/-/lodash.find-4.6.0.tgz#cb0704d47ab71789ffa0de8b97dd926fb88b13b1"
|
||||
|
|
@ -8947,6 +8947,11 @@ lodash.pick@^4.4.0:
|
|||
resolved "https://registry.yarnpkg.com/lodash.pick/-/lodash.pick-4.4.0.tgz#52f05610fff9ded422611441ed1fc123a03001b3"
|
||||
integrity sha1-UvBWEP/53tQiYRRB7R/BI6AwAbM=
|
||||
|
||||
lodash.pullall@^4.2.0:
|
||||
version "4.2.0"
|
||||
resolved "https://registry.yarnpkg.com/lodash.pullall/-/lodash.pullall-4.2.0.tgz#9d98b8518b7c965b0fae4099bd9fb7df8bbf38ba"
|
||||
integrity sha512-VhqxBKH0ZxPpLhiu68YD1KnHmbhQJQctcipvmFnqIBDYzcIHzf3Zpu0tpeOKtR4x76p9yohc506eGdOjTmyIBg==
|
||||
|
||||
lodash.snakecase@^4.1.1:
|
||||
version "4.1.1"
|
||||
resolved "https://registry.yarnpkg.com/lodash.snakecase/-/lodash.snakecase-4.1.1.tgz#39d714a35357147837aefd64b5dcbb16becd8f8d"
|
||||
|
|
@ -9759,11 +9764,6 @@ mime@1.6.0:
|
|||
resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1"
|
||||
integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==
|
||||
|
||||
mime@^2.3.1:
|
||||
version "2.4.4"
|
||||
resolved "https://registry.yarnpkg.com/mime/-/mime-2.4.4.tgz#bd7b91135fc6b01cde3e9bae33d659b63d8857e5"
|
||||
integrity sha512-LRxmNwziLPT828z+4YkNzloCFC2YM4wrB99k+AV5ZbEyfGNWfG8SO1FUXLmLDBSo89NrJZ4DIWeLjy1CHGhMGA==
|
||||
|
||||
mimic-fn@^2.1.0:
|
||||
version "2.1.0"
|
||||
resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b"
|
||||
|
|
@ -10014,6 +10014,11 @@ mri@^1.1.0:
|
|||
resolved "https://registry.yarnpkg.com/mri/-/mri-1.2.0.tgz#6721480fec2a11a4889861115a48b6cbe7cc8f0b"
|
||||
integrity sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==
|
||||
|
||||
mrmime@^1.0.0:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/mrmime/-/mrmime-1.0.1.tgz#5f90c825fad4bdd41dc914eff5d1a8cfdaf24f27"
|
||||
integrity sha512-hzzEagAgDyoU1Q6yg5uI+AorQgdvMCur3FcKf7NhMKWsaYg+RnbTyHRa/9IlLF9rf455MOCtcqqrQQ83pPP7Uw==
|
||||
|
||||
ms@2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8"
|
||||
|
|
@ -11991,14 +11996,14 @@ simple-update-notifier@^1.0.7:
|
|||
dependencies:
|
||||
semver "~7.0.0"
|
||||
|
||||
sirv@^1.0.7:
|
||||
version "1.0.11"
|
||||
resolved "https://registry.yarnpkg.com/sirv/-/sirv-1.0.11.tgz#81c19a29202048507d6ec0d8ba8910fda52eb5a4"
|
||||
integrity sha512-SR36i3/LSWja7AJNRBz4fF/Xjpn7lQFI30tZ434dIy+bitLYSP+ZEenHg36i23V2SGEz+kqjksg0uOGZ5LPiqg==
|
||||
sirv@^2.0.3:
|
||||
version "2.0.3"
|
||||
resolved "https://registry.yarnpkg.com/sirv/-/sirv-2.0.3.tgz#ca5868b87205a74bef62a469ed0296abceccd446"
|
||||
integrity sha512-O9jm9BsID1P+0HOi81VpXPoDxYP374pkOLzACAoyUQ/3OUVndNpsz6wMnY2z+yOxzbllCKZrM+9QrWsv4THnyA==
|
||||
dependencies:
|
||||
"@polka/url" "^1.0.0-next.9"
|
||||
mime "^2.3.1"
|
||||
totalist "^1.0.0"
|
||||
"@polka/url" "^1.0.0-next.20"
|
||||
mrmime "^1.0.0"
|
||||
totalist "^3.0.0"
|
||||
|
||||
sisteransi@^1.0.4:
|
||||
version "1.0.5"
|
||||
|
|
@ -12785,10 +12790,10 @@ toidentifier@1.0.1:
|
|||
resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.1.tgz#3be34321a88a820ed1bd80dfaa33e479fbb8dd35"
|
||||
integrity sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==
|
||||
|
||||
totalist@^1.0.0:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/totalist/-/totalist-1.1.0.tgz#a4d65a3e546517701e3e5c37a47a70ac97fe56df"
|
||||
integrity sha512-gduQwd1rOdDMGxFG1gEvhV88Oirdo2p+KjoYFU7k2g+i7n6AFFbDQ5kMPUsW0pNbfQsB/cwXvT1i4Bue0s9g5g==
|
||||
totalist@^3.0.0:
|
||||
version "3.0.1"
|
||||
resolved "https://registry.yarnpkg.com/totalist/-/totalist-3.0.1.tgz#ba3a3d600c915b1a97872348f79c127475f6acf8"
|
||||
integrity sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==
|
||||
|
||||
touch@^3.1.0:
|
||||
version "3.1.0"
|
||||
|
|
@ -13368,7 +13373,7 @@ vite-plugin-ruby@^3.2.2:
|
|||
debug "^4.3.4"
|
||||
fast-glob "^3.2.12"
|
||||
|
||||
vite-svg-loader@^3.4.0:
|
||||
vite-svg-loader@^3.6.0:
|
||||
version "3.6.0"
|
||||
resolved "https://registry.yarnpkg.com/vite-svg-loader/-/vite-svg-loader-3.6.0.tgz#71d246cba5e808c7f183a2a56a9dde6856bb0c92"
|
||||
integrity sha512-bZJffcgCREW57kNkgMhuNqeDznWXyQwJ3wKrRhHLMMzwDnP5jr3vXW3cqsmquRR7VTP5mLdKj1/zzPPooGUuPw==
|
||||
|
|
@ -13448,10 +13453,10 @@ vue-hot-reload-api@^2.3.0:
|
|||
hash-sum "^2.0.0"
|
||||
loader-utils "^2.0.0"
|
||||
|
||||
vue-loader@15.10.1:
|
||||
version "15.10.1"
|
||||
resolved "https://registry.yarnpkg.com/vue-loader/-/vue-loader-15.10.1.tgz#c451c4cd05a911aae7b5dbbbc09fb913fb3cca18"
|
||||
integrity sha512-SaPHK1A01VrNthlix6h1hq4uJu7S/z0kdLUb6klubo738NeQoLbS6V9/d8Pv19tU0XdQKju3D1HSKuI8wJ5wMA==
|
||||
vue-loader@15.10.2:
|
||||
version "15.10.2"
|
||||
resolved "https://registry.yarnpkg.com/vue-loader/-/vue-loader-15.10.2.tgz#6dccfda8661caa7f5415806a5e386fd3258d8112"
|
||||
integrity sha512-ndeSe/8KQc/nlA7TJ+OBhv2qalmj1s+uBs7yHDRFaAXscFTApBzY9F1jES3bautmgWjDlDct0fw8rPuySDLwxw==
|
||||
dependencies:
|
||||
"@vue/component-compiler-utils" "^3.1.0"
|
||||
hash-sum "^1.0.2"
|
||||
|
|
@ -13647,20 +13652,27 @@ webidl-conversions@^7.0.0:
|
|||
resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-7.0.0.tgz#256b4e1882be7debbf01d05f0aa2039778ea080a"
|
||||
integrity sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==
|
||||
|
||||
webpack-bundle-analyzer@^4.9.0:
|
||||
version "4.9.0"
|
||||
resolved "https://registry.yarnpkg.com/webpack-bundle-analyzer/-/webpack-bundle-analyzer-4.9.0.tgz#fc093c4ab174fd3dcbd1c30b763f56d10141209d"
|
||||
integrity sha512-+bXGmO1LyiNx0i9enBu3H8mv42sj/BJWhZNFwjz92tVnBa9J3JMGo2an2IXlEleoDOPn/Hofl5hr/xCpObUDtw==
|
||||
webpack-bundle-analyzer@^4.9.1:
|
||||
version "4.9.1"
|
||||
resolved "https://registry.yarnpkg.com/webpack-bundle-analyzer/-/webpack-bundle-analyzer-4.9.1.tgz#d00bbf3f17500c10985084f22f1a2bf45cb2f09d"
|
||||
integrity sha512-jnd6EoYrf9yMxCyYDPj8eutJvtjQNp8PHmni/e/ulydHBWhT5J3menXt3HEkScsu9YqMAcG4CfFjs3rj5pVU1w==
|
||||
dependencies:
|
||||
"@discoveryjs/json-ext" "0.5.7"
|
||||
acorn "^8.0.4"
|
||||
acorn-walk "^8.0.0"
|
||||
chalk "^4.1.0"
|
||||
commander "^7.2.0"
|
||||
escape-string-regexp "^4.0.0"
|
||||
gzip-size "^6.0.0"
|
||||
lodash "^4.17.20"
|
||||
is-plain-object "^5.0.0"
|
||||
lodash.debounce "^4.0.8"
|
||||
lodash.escape "^4.0.1"
|
||||
lodash.flatten "^4.4.0"
|
||||
lodash.invokemap "^4.6.0"
|
||||
lodash.pullall "^4.2.0"
|
||||
lodash.uniqby "^4.7.0"
|
||||
opener "^1.5.2"
|
||||
sirv "^1.0.7"
|
||||
picocolors "^1.0.0"
|
||||
sirv "^2.0.3"
|
||||
ws "^7.3.1"
|
||||
|
||||
webpack-cli@^4.10.0:
|
||||
|
|
|
|||
Loading…
Reference in New Issue