Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
584f7c6eba
commit
e033caddff
|
|
@ -1,6 +1,21 @@
|
|||
include:
|
||||
- local: .gitlab/ci/rails/shared.gitlab-ci.yml
|
||||
|
||||
db:setup pg14:
|
||||
stage: prepare
|
||||
needs: []
|
||||
extends:
|
||||
- .use-pg14
|
||||
- .default-before_script
|
||||
- .ruby-cache
|
||||
- .rails:rules:setup-test-env
|
||||
script:
|
||||
- source scripts/utils.sh
|
||||
- run_timed_command "pg_dumpall -h postgres -U postgres > pg_dumpall.sql"
|
||||
artifacts:
|
||||
paths:
|
||||
- pg_dumpall.sql
|
||||
|
||||
db:rollback single-db-ci-connection:
|
||||
extends:
|
||||
- db:rollback
|
||||
|
|
|
|||
|
|
@ -210,6 +210,10 @@ include:
|
|||
- !reference [.rspec-base, after_script]
|
||||
|
||||
.rspec-base-pg14:
|
||||
needs:
|
||||
- !reference [.rspec-base, needs]
|
||||
- job: "db:setup pg14"
|
||||
optional: true
|
||||
extends:
|
||||
- .rspec-base
|
||||
- .use-pg14
|
||||
|
|
|
|||
|
|
@ -1,22 +0,0 @@
|
|||
---
|
||||
# Cop supports --autocorrect.
|
||||
RSpec/BeEmpty:
|
||||
Exclude:
|
||||
- 'ee/spec/models/concerns/elastic/application_versioned_search_spec.rb'
|
||||
- 'ee/spec/models/ee/group_spec.rb'
|
||||
- 'ee/spec/models/ee/user_spec.rb'
|
||||
- 'ee/spec/models/product_analytics/visualization_spec.rb'
|
||||
- 'ee/spec/requests/api/graphql/ai/feature_settings/feature_settings_spec.rb'
|
||||
- 'ee/spec/services/releases/update_service_spec.rb'
|
||||
- 'ee/spec/support/shared_examples/lib/sidebars/menus_shared_examples.rb'
|
||||
- 'ee/spec/support/shared_examples/models/authz/member_roles_shared_examples.rb'
|
||||
- 'ee/spec/support/shared_examples/quick_actions/merge_request/unassign_reviewer_shared_examples.rb'
|
||||
- 'spec/helpers/nav/new_dropdown_helper_spec.rb'
|
||||
- 'spec/helpers/users_helper_spec.rb'
|
||||
- 'spec/lib/click_house/iterator_spec.rb'
|
||||
- 'spec/lib/gitlab/checks/changes_access_spec.rb'
|
||||
- 'spec/lib/gitlab/ci/jwt_spec.rb'
|
||||
- 'spec/lib/gitlab/ci/variables/builder/release_spec.rb'
|
||||
- 'spec/lib/gitlab/mail_room/mail_room_spec.rb'
|
||||
- 'spec/lib/gitlab/search_results_spec.rb'
|
||||
- 'spec/lib/search/empty_search_results_spec.rb'
|
||||
|
|
@ -709,7 +709,7 @@
|
|||
{"name":"sprockets-rails","version":"3.5.1","platform":"ruby","checksum":"c44626cb3887a1a8b572ca258685db33b4ebd041aa73428a716eac444ee5ef48"},
|
||||
{"name":"ssh_data","version":"1.3.0","platform":"ruby","checksum":"ec7c1e95a3aebeee412147998f4c147b4b05da6ed0aafda6083f9449318eaac0"},
|
||||
{"name":"ssrf_filter","version":"1.0.8","platform":"ruby","checksum":"03f49f54837e407d43ee93ec733a8a94dc1bcf8185647ac61606e63aaedaa0db"},
|
||||
{"name":"stackprof","version":"0.2.26","platform":"ruby","checksum":"ee408cbcccd9422aabd66edff8b76a77d67955f2ee1b674961b5dfaa2cc7b8bd"},
|
||||
{"name":"stackprof","version":"0.2.27","platform":"ruby","checksum":"aff6d28656c852e74cf632cc2046f849033dc1dedffe7cb8c030d61b5745e80c"},
|
||||
{"name":"state_machines","version":"0.5.0","platform":"ruby","checksum":"23e6249d374a920b528dccade403518b4abbd83841a3e2c9ef13e6f1a009b102"},
|
||||
{"name":"state_machines-activemodel","version":"0.8.0","platform":"ruby","checksum":"e932dab190d4be044fb5f9cab01a3ea0b092c5f113d4676c6c0a0d49bf738d2c"},
|
||||
{"name":"state_machines-activerecord","version":"0.8.0","platform":"ruby","checksum":"072fb701b8ab03de0608297f6c55dc34ed096e556fa8f77e556f3c461c71aab6"},
|
||||
|
|
|
|||
|
|
@ -1803,7 +1803,7 @@ GEM
|
|||
sprockets (>= 3.0.0)
|
||||
ssh_data (1.3.0)
|
||||
ssrf_filter (1.0.8)
|
||||
stackprof (0.2.26)
|
||||
stackprof (0.2.27)
|
||||
state_machines (0.5.0)
|
||||
state_machines-activemodel (0.8.0)
|
||||
activemodel (>= 5.1)
|
||||
|
|
|
|||
|
|
@ -720,7 +720,7 @@
|
|||
{"name":"sprockets-rails","version":"3.5.1","platform":"ruby","checksum":"c44626cb3887a1a8b572ca258685db33b4ebd041aa73428a716eac444ee5ef48"},
|
||||
{"name":"ssh_data","version":"1.3.0","platform":"ruby","checksum":"ec7c1e95a3aebeee412147998f4c147b4b05da6ed0aafda6083f9449318eaac0"},
|
||||
{"name":"ssrf_filter","version":"1.0.8","platform":"ruby","checksum":"03f49f54837e407d43ee93ec733a8a94dc1bcf8185647ac61606e63aaedaa0db"},
|
||||
{"name":"stackprof","version":"0.2.26","platform":"ruby","checksum":"ee408cbcccd9422aabd66edff8b76a77d67955f2ee1b674961b5dfaa2cc7b8bd"},
|
||||
{"name":"stackprof","version":"0.2.27","platform":"ruby","checksum":"aff6d28656c852e74cf632cc2046f849033dc1dedffe7cb8c030d61b5745e80c"},
|
||||
{"name":"state_machines","version":"0.5.0","platform":"ruby","checksum":"23e6249d374a920b528dccade403518b4abbd83841a3e2c9ef13e6f1a009b102"},
|
||||
{"name":"state_machines-activemodel","version":"0.8.0","platform":"ruby","checksum":"e932dab190d4be044fb5f9cab01a3ea0b092c5f113d4676c6c0a0d49bf738d2c"},
|
||||
{"name":"state_machines-activerecord","version":"0.8.0","platform":"ruby","checksum":"072fb701b8ab03de0608297f6c55dc34ed096e556fa8f77e556f3c461c71aab6"},
|
||||
|
|
|
|||
|
|
@ -1836,7 +1836,7 @@ GEM
|
|||
sprockets (>= 3.0.0)
|
||||
ssh_data (1.3.0)
|
||||
ssrf_filter (1.0.8)
|
||||
stackprof (0.2.26)
|
||||
stackprof (0.2.27)
|
||||
state_machines (0.5.0)
|
||||
state_machines-activemodel (0.8.0)
|
||||
activemodel (>= 5.1)
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ import {
|
|||
WIDGET_TYPE_AWARD_EMOJI,
|
||||
WIDGET_TYPE_HIERARCHY,
|
||||
WIDGET_TYPE_CUSTOM_FIELDS,
|
||||
WIDGET_TYPE_LINKED_ITEMS,
|
||||
CUSTOM_FIELDS_TYPE_NUMBER,
|
||||
CUSTOM_FIELDS_TYPE_TEXT,
|
||||
CUSTOM_FIELDS_TYPE_SINGLE_SELECT,
|
||||
|
|
@ -136,9 +137,6 @@ export const config = {
|
|||
},
|
||||
},
|
||||
},
|
||||
LinkedWorkItemType: {
|
||||
keyFields: ['linkId'],
|
||||
},
|
||||
WorkItem: {
|
||||
fields: {
|
||||
// @todo: Mocking CUSTOM_FIELDS widget while not suported by backend
|
||||
|
|
@ -474,6 +472,30 @@ export const config = {
|
|||
};
|
||||
}
|
||||
|
||||
// this ensures that we don’t override linkedItems.workItem when updating parent
|
||||
if (incomingWidget?.type === WIDGET_TYPE_LINKED_ITEMS) {
|
||||
if (!incomingWidget.linkedItems) {
|
||||
return existingWidget;
|
||||
}
|
||||
|
||||
const incomindNodes = incomingWidget.linkedItems?.nodes || [];
|
||||
const existingNodes = existingWidget.linkedItems?.nodes || [];
|
||||
|
||||
const resultNodes = incomindNodes.map((incomingNode) => {
|
||||
const existingNode =
|
||||
existingNodes.find((n) => n.linkId === incomingNode.linkId) ?? {};
|
||||
return { ...existingNode, ...incomingNode };
|
||||
});
|
||||
|
||||
return {
|
||||
...incomingWidget,
|
||||
linkedItems: {
|
||||
...incomingWidget.linkedItems,
|
||||
nodes: resultNodes,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
return { ...existingWidget, ...incomingWidget };
|
||||
});
|
||||
},
|
||||
|
|
|
|||
|
|
@ -443,7 +443,11 @@ export default class MergeRequestTabs {
|
|||
let newStatePathname = pathname.replace(this.actionRegex, '');
|
||||
|
||||
// Append the new action if we're on a tab other than 'notes'
|
||||
if (this.currentAction !== 'show' && this.currentAction !== 'new') {
|
||||
if (
|
||||
this.currentAction !== 'show' &&
|
||||
this.currentAction !== 'new' &&
|
||||
this.currentAction !== 'reports'
|
||||
) {
|
||||
newStatePathname += `/${this.currentAction}`;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,6 @@
|
|||
<script>
|
||||
import MRWidgetService from 'ee_else_ce/vue_merge_request_widget/services/mr_widget_service';
|
||||
import MRWidgetStore from 'ee_else_ce/vue_merge_request_widget/stores/mr_widget_store';
|
||||
import {
|
||||
BLOCKERS_ROUTE,
|
||||
CODE_QUALITY_ROUTE,
|
||||
|
|
@ -23,9 +25,21 @@ export default {
|
|||
inject: ['hasPolicies'],
|
||||
data() {
|
||||
return {
|
||||
blockersCounter: 2,
|
||||
mr: null,
|
||||
};
|
||||
},
|
||||
created() {
|
||||
if (
|
||||
window.gl?.mrWidgetData?.merge_request_cached_widget_path &&
|
||||
window.gl?.mrWidgetData?.merge_request_widget_path
|
||||
) {
|
||||
MRWidgetService.fetchInitialData()
|
||||
.then(({ data }) => {
|
||||
this.mr = new MRWidgetStore({ ...window.gl.mrWidgetData, ...data });
|
||||
})
|
||||
.catch(() => {});
|
||||
}
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
|
|
@ -50,7 +64,7 @@ export default {
|
|||
</nav>
|
||||
</aside>
|
||||
<section class="md:gl-pt-5">
|
||||
<router-view />
|
||||
<router-view :mr="mr" />
|
||||
</section>
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -1,9 +1,26 @@
|
|||
<script>
|
||||
import { GlLoadingIcon } from '@gitlab/ui';
|
||||
import ReportWidgetContainer from 'ee_else_ce/vue_merge_request_widget/components/widget/app.vue';
|
||||
|
||||
export default {
|
||||
name: 'MergeRequestReportsIndexPage',
|
||||
components: {
|
||||
GlLoadingIcon,
|
||||
ReportWidgetContainer,
|
||||
},
|
||||
props: {
|
||||
mr: {
|
||||
type: Object,
|
||||
required: false,
|
||||
default: null,
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div></div>
|
||||
<div v-if="mr">
|
||||
<report-widget-container :mr="mr" reports-tab-content />
|
||||
</div>
|
||||
<gl-loading-icon v-else size="lg" />
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -1,11 +1,6 @@
|
|||
import BlockersPage from 'ee_else_ce/merge_requests/reports/pages/blockers_page.vue';
|
||||
import IndexComponent from './pages/index.vue';
|
||||
import {
|
||||
BLOCKERS_ROUTE,
|
||||
CODE_QUALITY_ROUTE,
|
||||
LICENSE_COMPLIANCE_ROUTE,
|
||||
SECURITY_ROUTE,
|
||||
} from './constants';
|
||||
import { BLOCKERS_ROUTE } from './constants';
|
||||
|
||||
export default [
|
||||
{
|
||||
|
|
@ -14,18 +9,8 @@ export default [
|
|||
component: BlockersPage,
|
||||
},
|
||||
{
|
||||
path: '/?type=code-quality',
|
||||
name: CODE_QUALITY_ROUTE,
|
||||
component: IndexComponent,
|
||||
},
|
||||
{
|
||||
path: '/?type=security',
|
||||
name: SECURITY_ROUTE,
|
||||
component: IndexComponent,
|
||||
},
|
||||
{
|
||||
path: '/?type=license-compliance',
|
||||
name: LICENSE_COMPLIANCE_ROUTE,
|
||||
name: 'report',
|
||||
path: '/:report',
|
||||
component: IndexComponent,
|
||||
},
|
||||
];
|
||||
|
|
|
|||
|
|
@ -221,16 +221,16 @@ export default {
|
|||
/>
|
||||
<div
|
||||
v-if="showPlaceholder"
|
||||
class="gl-filter-blur-1 gl-absolute gl-bottom-0 gl-left-0 gl-right-0 gl-top-0"
|
||||
class="gl-absolute gl-bottom-0 gl-left-0 gl-right-0 gl-top-0 gl-backdrop-blur-sm"
|
||||
data-testid="placeholder-overlay"
|
||||
>
|
||||
<div
|
||||
class="gl-absolute gl-bottom-0 gl-left-0 gl-right-0 gl-top-0 gl-z-2 gl-bg-white gl-opacity-5"
|
||||
class="gl-absolute gl-bottom-0 gl-left-0 gl-right-0 gl-top-0 gl-z-2 gl-bg-overlay"
|
||||
></div>
|
||||
<div class="gl-relative gl-z-3 gl-flex gl-h-full gl-items-center gl-justify-center">
|
||||
<div class="gl-max-w-34">
|
||||
<h4 data-testid="filename">{{ filename }}</h4>
|
||||
<p data-testid="description">
|
||||
<div class="gl-max-w-34 gl-rounded-base gl-bg-overlap gl-p-6">
|
||||
<h4 data-testid="filename" class="gl-heading-4">{{ filename }}</h4>
|
||||
<p data-testid="description" class="gl-mb-0 gl-text-subtle">
|
||||
{{ $options.i18n.overlayMessage }}
|
||||
</p>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -13,7 +13,6 @@ import IssueToken from './issue_token.vue';
|
|||
const SPACE_FACTOR = 1;
|
||||
|
||||
export default {
|
||||
TYPE_ISSUE,
|
||||
name: 'RelatedIssuableInput',
|
||||
components: {
|
||||
GlFormGroup,
|
||||
|
|
@ -64,6 +63,11 @@ export default {
|
|||
required: false,
|
||||
default: false,
|
||||
},
|
||||
inline: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
|
@ -84,6 +88,9 @@ export default {
|
|||
allowAutoComplete() {
|
||||
return Object.keys(this.autoCompleteSources).length > 0;
|
||||
},
|
||||
showDescription() {
|
||||
return !this.inline && this.issuableType === TYPE_ISSUE;
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.setupAutoComplete();
|
||||
|
|
@ -182,7 +189,7 @@ export default {
|
|||
</script>
|
||||
|
||||
<template>
|
||||
<gl-form-group>
|
||||
<gl-form-group :label-class="inline ? 'gl-hidden' : ''">
|
||||
<div
|
||||
ref="issuableFormWrapper"
|
||||
:class="{ focus: isInputFocused }"
|
||||
|
|
@ -229,7 +236,7 @@ export default {
|
|||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<template v-if="issuableType === $options.TYPE_ISSUE" #description>
|
||||
<template v-if="showDescription" #description>
|
||||
<span :id="`${inputId}-description`">
|
||||
{{
|
||||
__(
|
||||
|
|
|
|||
|
|
@ -18,33 +18,51 @@ export default {
|
|||
import('~/vue_merge_request_widget/widgets/accessibility/index.vue'),
|
||||
},
|
||||
mixins: [glFeatureFlagsMixin()],
|
||||
provide() {
|
||||
return {
|
||||
reportsTabContent: this.reportsTabContent,
|
||||
};
|
||||
},
|
||||
props: {
|
||||
mr: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
reportsTabContent: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
collapsed: this.glFeatures.mrReportsTab,
|
||||
collapsed: this.reportsTabContent ? false : this.glFeatures.mrReportsTab,
|
||||
findingsCount: 0,
|
||||
loadedCount: 0,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
testReportWidget() {
|
||||
if (!this.isViewingReport('test-summary')) return undefined;
|
||||
|
||||
return this.mr.testResultsPath && 'MrTestReportWidget';
|
||||
},
|
||||
|
||||
terraformPlansWidget() {
|
||||
if (!this.isViewingReport('terraform')) return undefined;
|
||||
|
||||
return this.mr.terraformReportsPath && 'MrTerraformWidget';
|
||||
},
|
||||
|
||||
codeQualityWidget() {
|
||||
if (!this.isViewingReport('code-quality')) return undefined;
|
||||
|
||||
return this.mr.codequalityReportsPath ? 'MrCodeQualityWidget' : undefined;
|
||||
},
|
||||
|
||||
accessibilityWidget() {
|
||||
if (!this.isViewingReport('accessibility')) return undefined;
|
||||
|
||||
return this.mr.accessibilityReportPath ? 'MrAccessibilityWidget' : undefined;
|
||||
},
|
||||
|
||||
|
|
@ -69,7 +87,17 @@ export default {
|
|||
return false;
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
if (this.reportsTabContent && !this.widgets.length) {
|
||||
this.$router.push({ path: '/' });
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
isViewingReport(reportName) {
|
||||
if (!this.reportsTabContent) return true;
|
||||
|
||||
return this.$router.currentRoute.params.report === reportName;
|
||||
},
|
||||
onLoadedReport(findings) {
|
||||
this.findingsCount += findings;
|
||||
this.loadedCount += 1;
|
||||
|
|
@ -82,12 +110,14 @@ export default {
|
|||
<section
|
||||
v-if="widgets.length"
|
||||
role="region"
|
||||
:aria-label="__('Merge request reports')"
|
||||
:aria-label="reportsTabContent ? null : __('Merge request reports')"
|
||||
data-testid="mr-widget-app"
|
||||
class="mr-section-container"
|
||||
:class="{
|
||||
'mr-section-container': !reportsTabContent,
|
||||
}"
|
||||
>
|
||||
<state-container
|
||||
v-if="glFeatures.mrReportsTab"
|
||||
v-if="glFeatures.mrReportsTab && !reportsTabContent"
|
||||
:status="statusIcon"
|
||||
is-collapsible
|
||||
collapse-on-desktop
|
||||
|
|
@ -123,7 +153,8 @@ export default {
|
|||
data-testid="reports-widgets-container"
|
||||
class="reports-widgets-container"
|
||||
:class="{
|
||||
'gl-border-t gl-relative gl-border-t-section gl-bg-subtle': glFeatures.mrReportsTab,
|
||||
'gl-border-t gl-relative gl-border-t-section gl-bg-subtle':
|
||||
glFeatures.mrReportsTab && !reportsTabContent,
|
||||
}"
|
||||
>
|
||||
<component
|
||||
|
|
@ -132,7 +163,9 @@ export default {
|
|||
:key="widget.name || index"
|
||||
:mr="mr"
|
||||
class="mr-widget-section"
|
||||
:class="{ 'gl-border-t gl-border-t-section': index > 0 }"
|
||||
:class="{
|
||||
'gl-border-t gl-border-t-section': index > 0 && !reportsTabContent,
|
||||
}"
|
||||
@loaded="onLoadedReport"
|
||||
/>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
<!-- eslint-disable vue/multi-word-component-names -->
|
||||
<script>
|
||||
import { GlButton, GlLink, GlTooltipDirective, GlLoadingIcon } from '@gitlab/ui';
|
||||
import { kebabCase } from 'lodash';
|
||||
import * as Sentry from '~/sentry/sentry_browser_wrapper';
|
||||
import { normalizeHeaders } from '~/lib/utils/common_utils';
|
||||
import { logError } from '~/lib/logger';
|
||||
|
|
@ -8,7 +9,7 @@ import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
|
|||
import SafeHtml from '~/vue_shared/directives/safe_html';
|
||||
import { sprintf, __ } from '~/locale';
|
||||
import Poll from '~/lib/utils/poll';
|
||||
import { mergeUrlParams } from '~/lib/utils/url_utility';
|
||||
import { joinPaths } from '~/lib/utils/url_utility';
|
||||
import HelpPopover from '~/vue_shared/components/help_popover.vue';
|
||||
import { DynamicScroller, DynamicScrollerItem } from 'vendor/vue-virtual-scroller';
|
||||
import { EXTENSION_ICONS } from '../../constants';
|
||||
|
|
@ -50,6 +51,7 @@ export default {
|
|||
SafeHtml,
|
||||
},
|
||||
mixins: [glFeatureFlagsMixin()],
|
||||
inject: { reportsTabContent: { default: false } },
|
||||
props: {
|
||||
loadingText: {
|
||||
type: String,
|
||||
|
|
@ -175,7 +177,7 @@ export default {
|
|||
data() {
|
||||
return {
|
||||
isExpandedForTheFirstTime: true,
|
||||
isCollapsed: true,
|
||||
isCollapsed: !this.reportsTabContent,
|
||||
isLoadingCollapsedContent: true,
|
||||
isLoadingExpandedContent: false,
|
||||
summaryError: null,
|
||||
|
|
@ -212,9 +214,9 @@ export default {
|
|||
return [
|
||||
{
|
||||
text: __('View report'),
|
||||
href: mergeUrlParams(
|
||||
{ type: this.widgetName.replace(WIDGET_PREFIX, '') },
|
||||
href: joinPaths(
|
||||
window.gl?.mrWidgetData?.reportsTabPath || '',
|
||||
kebabCase(this.widgetName.replace(WIDGET_PREFIX, '')),
|
||||
),
|
||||
onClick(action, e) {
|
||||
e.preventDefault();
|
||||
|
|
@ -254,6 +256,10 @@ export default {
|
|||
this.summaryError = this.errorText;
|
||||
}
|
||||
|
||||
if (this.reportsTabContent) {
|
||||
this.fetchExpandedContent();
|
||||
}
|
||||
|
||||
this.isLoadingCollapsedContent = false;
|
||||
},
|
||||
methods: {
|
||||
|
|
@ -341,7 +347,13 @@ export default {
|
|||
|
||||
<template>
|
||||
<section class="media-section" data-testid="widget-extension">
|
||||
<div class="gl-flex gl-px-5 gl-py-4 gl-pr-4" :class="{ 'gl-pl-9': glFeatures.mrReportsTab }">
|
||||
<div
|
||||
v-if="!reportsTabContent"
|
||||
:class="{
|
||||
'gl-pl-9': glFeatures.mrReportsTab,
|
||||
'gl-flex gl-px-5 gl-py-4 gl-pr-4': !reportsTabContent,
|
||||
}"
|
||||
>
|
||||
<status-icon
|
||||
:level="glFeatures.mrReportsTab ? 2 : 1"
|
||||
:name="widgetName"
|
||||
|
|
@ -415,11 +427,15 @@ export default {
|
|||
</div>
|
||||
</div>
|
||||
<div
|
||||
v-if="!glFeatures.mrReportsTab && (!isCollapsed || contentError)"
|
||||
class="gl-border-t gl-relative gl-border-t-section gl-bg-subtle"
|
||||
v-if="!isCollapsed || contentError"
|
||||
:class="{ 'gl-border-t gl-relative gl-border-t-section gl-bg-subtle': !reportsTabContent }"
|
||||
data-testid="widget-extension-collapsed-section"
|
||||
>
|
||||
<div v-if="isLoadingExpandedContent" class="report-block-container gl-text-center">
|
||||
<div
|
||||
v-if="isLoadingExpandedContent"
|
||||
class="gl-text-center"
|
||||
:class="{ 'report-block-container': !reportsTabContent, 'gl-py-5': reportsTabContent }"
|
||||
>
|
||||
<gl-loading-icon size="sm" inline /> {{ loadingText }}
|
||||
</div>
|
||||
<div v-else class="gl-flex gl-pl-5" :class="{ 'gl-pr-5': $scopedSlots.content }">
|
||||
|
|
@ -439,7 +455,8 @@ export default {
|
|||
v-if="contentWithKeyField"
|
||||
:items="contentWithKeyField"
|
||||
:min-item-size="32"
|
||||
:style="{ maxHeight: '170px' }"
|
||||
:style="{ maxHeight: reportsTabContent ? null : '170px' }"
|
||||
:page-mode="glFeatures.mrReportsTab && reportsTabContent"
|
||||
data-testid="dynamic-content-scroller"
|
||||
class="gl-pr-5"
|
||||
>
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@ import { GlAlert, GlButton, GlBadge } from '@gitlab/ui';
|
|||
import { cloneDeep } from 'lodash';
|
||||
|
||||
import { s__, n__, sprintf } from '~/locale';
|
||||
import { fetchPolicies } from '~/lib/graphql';
|
||||
import CrudComponent from '~/vue_shared/components/crud_component.vue';
|
||||
|
||||
import workItemLinkedItemsQuery from '../../graphql/work_item_linked_items.query.graphql';
|
||||
|
|
@ -72,8 +71,6 @@ export default {
|
|||
apollo: {
|
||||
linkedWorkItems: {
|
||||
query: workItemLinkedItemsQuery,
|
||||
fetchPolicy: fetchPolicies.NETWORK_ONLY,
|
||||
nextFetchPolicy: fetchPolicies.CACHE_FIRST,
|
||||
variables() {
|
||||
return {
|
||||
fullPath: this.workItemFullPath,
|
||||
|
|
|
|||
|
|
@ -33,11 +33,7 @@ input[type='search'] {
|
|||
/* stylelint-enable property-no-vendor-prefix */
|
||||
|
||||
.form-actions {
|
||||
margin-top: 0;
|
||||
margin-bottom: -$gl-padding;
|
||||
padding: $gl-padding;
|
||||
background-color: $gray-10;
|
||||
border-top: 1px solid $border-color;
|
||||
@apply gl-mt-0 -gl-mb-5 gl-p-5 gl-bg-subtle gl-border-t;
|
||||
}
|
||||
|
||||
label {
|
||||
|
|
|
|||
|
|
@ -48,7 +48,7 @@ module WorkItems
|
|||
raise Gitlab::Graphql::Errors::ArgumentError, ::Types::IssuableStateEnum::INVALID_LOCKED_MESSAGE
|
||||
}
|
||||
argument :types,
|
||||
[Types::IssueTypeEnum],
|
||||
[::Types::IssueTypeEnum],
|
||||
as: :issue_types,
|
||||
description: 'Filter work items by the given work item types.',
|
||||
required: false
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ module Types
|
|||
description: 'Timestamp the link was created.', null: false,
|
||||
method: :issue_link_created_at
|
||||
|
||||
field :work_item_state, Types::WorkItemStateEnum,
|
||||
field :work_item_state, ::Types::WorkItemStateEnum,
|
||||
description: 'State of the linked work item.', null: false, method: :state
|
||||
|
||||
field :link_id, ::Types::GlobalIDType[::WorkItems::RelatedWorkItemLink],
|
||||
|
|
|
|||
|
|
@ -16,13 +16,13 @@ module Types
|
|||
field :name, GraphQL::Types::String,
|
||||
null: false,
|
||||
description: 'Name of the work item type.'
|
||||
field :widget_definitions, [Types::WorkItems::WidgetDefinitionInterface],
|
||||
field :widget_definitions, [::Types::WorkItems::WidgetDefinitionInterface],
|
||||
null: true,
|
||||
description: 'Available widgets for the work item type.',
|
||||
method: :widgets,
|
||||
experiment: { milestone: '16.7' }
|
||||
|
||||
field :supported_conversion_types, [Types::WorkItems::TypeType],
|
||||
field :supported_conversion_types, [::Types::WorkItems::TypeType],
|
||||
null: true,
|
||||
description: 'Supported conversion types for the work item type.',
|
||||
experiment: { milestone: '17.8' }
|
||||
|
|
|
|||
|
|
@ -32,11 +32,11 @@ module Types
|
|||
field :has_parent, GraphQL::Types::Boolean,
|
||||
null: false, method: :has_parent?, description: 'Indicates if the work item has a parent.'
|
||||
|
||||
field :rolled_up_counts_by_type, [Types::WorkItems::WorkItemTypeCountsByStateType],
|
||||
field :rolled_up_counts_by_type, [::Types::WorkItems::WorkItemTypeCountsByStateType],
|
||||
null: false, description: 'Counts of descendant work items by work item type and state.',
|
||||
experiment: { milestone: '17.3' }
|
||||
|
||||
field :depth_limit_reached_by_type, [Types::WorkItems::WorkItemTypeDepthLimitReachedByType],
|
||||
field :depth_limit_reached_by_type, [::Types::WorkItems::WorkItemTypeDepthLimitReachedByType],
|
||||
null: false, description: 'Depth limit reached by allowed work item type.',
|
||||
experiment: { milestone: '17.4' }
|
||||
|
||||
|
|
|
|||
|
|
@ -6,11 +6,11 @@ module Types
|
|||
class LabelsUpdateInputType < BaseInputObject
|
||||
graphql_name 'WorkItemWidgetLabelsUpdateInput'
|
||||
|
||||
argument :add_label_ids, [Types::GlobalIDType[::Label]],
|
||||
argument :add_label_ids, [::Types::GlobalIDType[::Label]],
|
||||
required: false,
|
||||
description: 'Global IDs of labels to be added to the work item.',
|
||||
prepare: ->(label_ids, _ctx) { label_ids.map(&:model_id) }
|
||||
argument :remove_label_ids, [Types::GlobalIDType[::Label]],
|
||||
argument :remove_label_ids, [::Types::GlobalIDType[::Label]],
|
||||
required: false,
|
||||
description: 'Global IDs of labels to be removed from the work item.',
|
||||
prepare: ->(label_ids, _ctx) { label_ids.map(&:model_id) }
|
||||
|
|
|
|||
|
|
@ -75,6 +75,10 @@ module WorkItems
|
|||
has_many :allowed_parent_types_by_name, -> { order_by_name_asc },
|
||||
through: :parent_restrictions, class_name: 'WorkItems::Type',
|
||||
foreign_key: :parent_type_id, source: :parent_type
|
||||
has_many :user_preferences,
|
||||
class_name: 'WorkItems::Types::UserPreference',
|
||||
primary_key: :correct_id,
|
||||
inverse_of: :work_item_type
|
||||
|
||||
before_validation :strip_whitespace
|
||||
after_save :clear_reactive_cache!
|
||||
|
|
|
|||
|
|
@ -0,0 +1,17 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module WorkItems
|
||||
module Types
|
||||
class UserPreference < ApplicationRecord
|
||||
self.table_name = 'work_item_type_user_preferences'
|
||||
|
||||
belongs_to :user
|
||||
belongs_to :namespace
|
||||
belongs_to :work_item_type,
|
||||
class_name: 'WorkItems::Type',
|
||||
primary_key: :correct_id,
|
||||
inverse_of: :user_preferences,
|
||||
optional: true
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -20,6 +20,8 @@ module Ci
|
|||
calculate_aggregate_duration_percentiles(query, result)
|
||||
|
||||
ServiceResponse.success(payload: { aggregate: result })
|
||||
rescue ::ClickHouse::Client::DatabaseError => e
|
||||
ServiceResponse.error(message: e.message)
|
||||
end
|
||||
|
||||
def calculate_aggregate_count(query, result)
|
||||
|
|
@ -34,7 +36,7 @@ module Ci
|
|||
|
||||
query = query
|
||||
.select(:status, query.count_pipelines_function.as('count'))
|
||||
.by_status(status_groups.flat_map(&STATUS_GROUP_TO_STATUSES).compact)
|
||||
.by_status(selected_statuses)
|
||||
.group_by_status
|
||||
|
||||
result_by_status = ::ClickHouse::Client.select(query.to_sql, :main).map(&:values).to_h
|
||||
|
|
|
|||
|
|
@ -61,6 +61,10 @@ module Ci
|
|||
duration_percentiles.map { |p| :"p#{p}" }
|
||||
end
|
||||
|
||||
def selected_statuses
|
||||
status_groups.flat_map(&STATUS_GROUP_TO_STATUSES).compact
|
||||
end
|
||||
|
||||
def validate_arguments
|
||||
if (duration_percentiles & ALLOWED_PERCENTILES) != duration_percentiles
|
||||
return ServiceResponse.error(message: 'Invalid duration percentiles specified')
|
||||
|
|
|
|||
|
|
@ -35,6 +35,11 @@ module Ci
|
|||
time_series = create_empty_time_series
|
||||
query = base_query
|
||||
|
||||
if status_groups.any?
|
||||
calculate_time_series_count(query, time_series)
|
||||
calculate_time_series_status_group_counts(query, time_series)
|
||||
end
|
||||
|
||||
calculate_time_series_duration_percentiles(query, time_series)
|
||||
collect_metrics
|
||||
|
||||
|
|
@ -43,12 +48,49 @@ module Ci
|
|||
{ label: date, **value }
|
||||
end
|
||||
})
|
||||
rescue ::ClickHouse::Client::DatabaseError => e
|
||||
ServiceResponse.error(message: e.message)
|
||||
end
|
||||
|
||||
def timespan_bin_query_function(query)
|
||||
query.timestamp_bin_function(time_series_period)
|
||||
end
|
||||
|
||||
def calculate_time_series_count(query, time_series)
|
||||
return if status_groups.exclude?(:any)
|
||||
|
||||
all_query = query
|
||||
.select(timespan_bin_query_function(query), query.count_pipelines_function.as('all'))
|
||||
.group_by_timestamp_bin
|
||||
|
||||
all_count_result =
|
||||
execute_select_query(all_query)
|
||||
.to_h do |entry|
|
||||
[parse_in_utc(entry[:timestamp]), { count: { any: entry[:all] } }]
|
||||
end
|
||||
|
||||
time_series.deep_merge!(all_count_result)
|
||||
end
|
||||
|
||||
def calculate_time_series_status_group_counts(query, time_series)
|
||||
return unless status_groups.intersect?(STATUS_GROUPS)
|
||||
|
||||
query = query
|
||||
.select(timespan_bin_query_function(query), :status, query.count_pipelines_function.as('count'))
|
||||
.by_status(selected_statuses)
|
||||
.group_by_timestamp_bin
|
||||
.group_by_status
|
||||
|
||||
# Produce a chronological set of hashes
|
||||
# such as `{ Time.utc(2023, 1, 1) => { count: { success: 1, failed: 0, other: 1, any: 3 } }`
|
||||
counts_by_timestamp =
|
||||
execute_select_query(query)
|
||||
.group_by { |entry| parse_in_utc(entry[:timestamp]) }
|
||||
.transform_values { |counts_by_status| { count: group_and_sum_counts(counts_by_status) } }
|
||||
|
||||
time_series.deep_merge!(counts_by_timestamp)
|
||||
end
|
||||
|
||||
def calculate_time_series_duration_percentiles(query, time_series)
|
||||
return if duration_percentiles.empty?
|
||||
|
||||
|
|
@ -56,11 +98,9 @@ module Ci
|
|||
timespan_bin_query_function(query),
|
||||
*duration_percentiles.map { |p| query.duration_quantile_function(p) }
|
||||
).group_by_timestamp_bin
|
||||
duration_by_date_result = ::ClickHouse::Client.select(duration_by_date_query.to_sql, :main)
|
||||
|
||||
time_series.deep_merge!(
|
||||
duration_by_date_result
|
||||
.map(&:symbolize_keys)
|
||||
execute_select_query(duration_by_date_query)
|
||||
.group_by { |entry| parse_in_utc(entry[:timestamp]) }
|
||||
.transform_values { |hash| hash.sole.excluding(:timestamp) } # Keep only percentiles
|
||||
.transform_values do |percentiles_by_date|
|
||||
|
|
@ -97,6 +137,7 @@ module Ci
|
|||
{}.tap do |time_series|
|
||||
while current < to_time
|
||||
time_series[current] = {
|
||||
count: status_groups.index_with(0),
|
||||
duration_statistics: round_percentiles(duration_percentile_symbols.index_with(0))
|
||||
}.compact_blank
|
||||
|
||||
|
|
@ -117,6 +158,24 @@ module Ci
|
|||
to_utc(Time.parse(timestamp)) # rubocop:disable Rails/TimeZone -- false positive, to_utc takes care of this
|
||||
end
|
||||
|
||||
def group_and_sum_counts(counts_by_status_and_time)
|
||||
# This method receives an array of hashes representing counts per job status per day,
|
||||
# e.g. [
|
||||
# { :timestamp => "2023-01-01 00:00:00", :status => "canceled", :count => 2 },
|
||||
# { :timestamp => "2023-01-01 00:00:00", :status => "skipped", :count => 1 },
|
||||
# { :timestamp => "2023-01-01 00:00:00", :status => "success", :count => 2 }
|
||||
# ] and returns a hash synthesizing the information as
|
||||
# { 2023-01-01 00:00:00 UTC => {:count => { :other=>3, :success=>2 } } }
|
||||
counts_by_status_and_time
|
||||
.to_h { |h| h.slice(:status, :count).values } # Create hash from status to count
|
||||
.group_by { |status, _count| STATUS_TO_STATUS_GROUP[status] } # Group by status group
|
||||
.transform_values { |pairs| pairs.sum(&:last) } # Sum counts from all statuses in group
|
||||
end
|
||||
|
||||
def execute_select_query(query)
|
||||
::ClickHouse::Client.select(query.to_sql, :main).map(&:symbolize_keys)
|
||||
end
|
||||
|
||||
def collect_metrics
|
||||
track_internal_event(
|
||||
'collect_time_series_pipeline_analytics',
|
||||
|
|
|
|||
|
|
@ -2,11 +2,11 @@
|
|||
- header_title _("New project"), new_project_path
|
||||
- add_to_breadcrumbs s_('ProjectsNew|Import project'), new_project_path(anchor: 'import_project')
|
||||
|
||||
%h1.page-title.gl-text-size-h-display.gl-flex.gl-items-center
|
||||
.gl-flex.gl-items-center.gl-justify-center
|
||||
= sprite_icon('tanuki', css_class: 'gl-mr-3', size: 48)
|
||||
= _('Import an exported GitLab project')
|
||||
%hr
|
||||
= render ::Layouts::PageHeadingComponent.new('') do |c|
|
||||
- c.with_heading do
|
||||
%span.gl-inline-flex.gl-items-center.gl-gap-3
|
||||
= sprite_icon('tanuki', size: 32)
|
||||
= _('Import an exported GitLab project')
|
||||
|
||||
= form_tag import_gitlab_project_path, class: 'new_project', multipart: true do
|
||||
= render 'import/shared/new_project_form'
|
||||
|
|
@ -20,7 +20,7 @@
|
|||
.form-group
|
||||
= file_field_tag :file, class: ''
|
||||
.row
|
||||
.form-actions.col-sm-12
|
||||
.col-sm-12.gl-mt-5
|
||||
= render Pajamas::ButtonComponent.new(type: :submit, variant: :confirm, button_options: { class: 'gl-mr-2', data: { testid: 'import-project-button' }}) do
|
||||
= _('Import project')
|
||||
= render Pajamas::ButtonComponent.new(href: new_project_path) do
|
||||
|
|
|
|||
|
|
@ -761,6 +761,8 @@
|
|||
- 1
|
||||
- - requirements_management_process_requirements_reports
|
||||
- 1
|
||||
- - sbom_create_occurrences_vulnerabilities
|
||||
- 1
|
||||
- - sbom_process_transfer_events
|
||||
- 1
|
||||
- - sbom_process_vulnerabilities
|
||||
|
|
|
|||
|
|
@ -4,12 +4,7 @@ classes:
|
|||
- Ci::RunningBuild
|
||||
feature_categories:
|
||||
- continuous_integration
|
||||
description: Running builds metadata. Despite the generic `RunningBuild` name, in
|
||||
this first iteration it applies only to shared runners. The decision to insert all
|
||||
of the running builds here was deferred to avoid the pressure on the database as
|
||||
at this time that was not necessary. We can reconsider the decision to limit this
|
||||
only to shared runners when there is more evidence that inserting all of the running
|
||||
builds there is worth the additional pressure.
|
||||
description: Running builds metadata.
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/62912
|
||||
milestone: '14.0'
|
||||
gitlab_schema: gitlab_ci
|
||||
|
|
|
|||
|
|
@ -8,14 +8,6 @@ gitlab_schema: gitlab_main_cell
|
|||
description: An Experiment Metadata record holds extra information about the experiment
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/104267
|
||||
milestone: '15.7'
|
||||
desired_sharding_key:
|
||||
project_id:
|
||||
references: projects
|
||||
backfill_via:
|
||||
parent:
|
||||
foreign_key: experiment_id
|
||||
table: ml_experiments
|
||||
sharding_key: project_id
|
||||
belongs_to: experiment
|
||||
desired_sharding_key_migration_job_name: BackfillMlExperimentMetadataProjectId
|
||||
table_size: small
|
||||
sharding_key:
|
||||
project_id: projects
|
||||
|
|
|
|||
|
|
@ -0,0 +1,12 @@
|
|||
---
|
||||
table_name: work_item_type_user_preferences
|
||||
classes:
|
||||
- WorkItems::Types::UserPreference
|
||||
feature_categories:
|
||||
- team_planning
|
||||
description: User preferences per work item type and namespace
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/176869
|
||||
milestone: '17.8'
|
||||
gitlab_schema: gitlab_main_cell
|
||||
sharding_key:
|
||||
namespace_id: namespaces
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class CreateWorkItemTypeUserPreferences < Gitlab::Database::Migration[2.2]
|
||||
milestone '17.9'
|
||||
|
||||
UNIQUE_INDEX_NAME = 'uniq_preference_by_user_namespace_and_work_item_type'
|
||||
|
||||
# rubocop:disable Migration/EnsureFactoryForTable -- https://gitlab.com/gitlab-org/gitlab/-/issues/468630
|
||||
# the factory exist in spec/factories/work_items/user_preference.rb
|
||||
def change
|
||||
create_table :work_item_type_user_preferences do |t|
|
||||
t.timestamps_with_timezone null: false
|
||||
|
||||
t.bigint :user_id, null: false
|
||||
t.bigint :namespace_id, null: false
|
||||
t.bigint :work_item_type_id
|
||||
t.text :sort, limit: 255
|
||||
|
||||
t.index %i[user_id namespace_id work_item_type_id], name: UNIQUE_INDEX_NAME
|
||||
end
|
||||
end
|
||||
# rubocop:enable Migration/EnsureFactoryForTable
|
||||
end
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class AddForeignKeysToWorkItemTypeUserPreferences < Gitlab::Database::Migration[2.2]
|
||||
milestone '17.9'
|
||||
|
||||
disable_ddl_transaction!
|
||||
|
||||
def up
|
||||
add_concurrent_foreign_key :work_item_type_user_preferences,
|
||||
:users,
|
||||
column: :user_id,
|
||||
on_delete: :cascade
|
||||
|
||||
add_concurrent_index :work_item_type_user_preferences, :namespace_id
|
||||
add_concurrent_foreign_key :work_item_type_user_preferences,
|
||||
:namespaces,
|
||||
column: :namespace_id,
|
||||
on_delete: :cascade
|
||||
|
||||
add_concurrent_index :work_item_type_user_preferences, :work_item_type_id
|
||||
add_concurrent_foreign_key :work_item_type_user_preferences,
|
||||
:work_item_types,
|
||||
target_column: :correct_id,
|
||||
column: :work_item_type_id,
|
||||
on_delete: :cascade
|
||||
end
|
||||
|
||||
def down
|
||||
with_lock_retries do
|
||||
remove_foreign_key_if_exists :work_item_type_user_preferences, :users, column: :user_id
|
||||
remove_foreign_key_if_exists :work_item_type_user_preferences, :namespaces, column: :namespace_id
|
||||
remove_foreign_key_if_exists :work_item_type_user_preferences, :work_item_types, column: :work_item_type_id
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class AddMlExperimentMetadataProjectIdNotNullConstraint < Gitlab::Database::Migration[2.2]
|
||||
disable_ddl_transaction!
|
||||
milestone '17.9'
|
||||
|
||||
def up
|
||||
add_not_null_constraint :ml_experiment_metadata, :project_id
|
||||
end
|
||||
|
||||
def down
|
||||
remove_not_null_constraint :ml_experiment_metadata, :project_id
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1 @@
|
|||
96f5050ea1850a89793874cae18ee6cb041e2f22c16ecd50958cd6e0ff989478
|
||||
|
|
@ -0,0 +1 @@
|
|||
806872796ca346c4a6a62442798b2be02602a3a3cf4e42816d6bc3240075e5c5
|
||||
|
|
@ -0,0 +1 @@
|
|||
4c5c6a0805f9e43ef76364ce1282c88b2a334dbefc6e50b1fe95f0dd4ec85d18
|
||||
|
|
@ -16102,7 +16102,8 @@ CREATE TABLE ml_experiment_metadata (
|
|||
value text NOT NULL,
|
||||
project_id bigint,
|
||||
CONSTRAINT check_112fe5002d CHECK ((char_length(name) <= 255)),
|
||||
CONSTRAINT check_a91c633d68 CHECK ((char_length(value) <= 5000))
|
||||
CONSTRAINT check_a91c633d68 CHECK ((char_length(value) <= 5000)),
|
||||
CONSTRAINT check_ca9b8315ef CHECK ((project_id IS NOT NULL))
|
||||
);
|
||||
|
||||
CREATE SEQUENCE ml_experiment_metadata_id_seq
|
||||
|
|
@ -23475,6 +23476,26 @@ CREATE SEQUENCE work_item_type_custom_fields_id_seq
|
|||
|
||||
ALTER SEQUENCE work_item_type_custom_fields_id_seq OWNED BY work_item_type_custom_fields.id;
|
||||
|
||||
CREATE TABLE work_item_type_user_preferences (
|
||||
id bigint NOT NULL,
|
||||
created_at timestamp with time zone NOT NULL,
|
||||
updated_at timestamp with time zone NOT NULL,
|
||||
user_id bigint NOT NULL,
|
||||
namespace_id bigint NOT NULL,
|
||||
work_item_type_id bigint,
|
||||
sort text,
|
||||
CONSTRAINT check_7f4a25cee7 CHECK ((char_length(sort) <= 255))
|
||||
);
|
||||
|
||||
CREATE SEQUENCE work_item_type_user_preferences_id_seq
|
||||
START WITH 1
|
||||
INCREMENT BY 1
|
||||
NO MINVALUE
|
||||
NO MAXVALUE
|
||||
CACHE 1;
|
||||
|
||||
ALTER SEQUENCE work_item_type_user_preferences_id_seq OWNED BY work_item_type_user_preferences.id;
|
||||
|
||||
CREATE TABLE work_item_types (
|
||||
id bigint NOT NULL,
|
||||
base_type smallint DEFAULT 0 NOT NULL,
|
||||
|
|
@ -25610,6 +25631,8 @@ ALTER TABLE ONLY work_item_text_field_values ALTER COLUMN id SET DEFAULT nextval
|
|||
|
||||
ALTER TABLE ONLY work_item_type_custom_fields ALTER COLUMN id SET DEFAULT nextval('work_item_type_custom_fields_id_seq'::regclass);
|
||||
|
||||
ALTER TABLE ONLY work_item_type_user_preferences ALTER COLUMN id SET DEFAULT nextval('work_item_type_user_preferences_id_seq'::regclass);
|
||||
|
||||
ALTER TABLE ONLY work_item_widget_definitions ALTER COLUMN id SET DEFAULT nextval('work_item_widget_definitions_id_seq'::regclass);
|
||||
|
||||
ALTER TABLE ONLY workspace_variables ALTER COLUMN id SET DEFAULT nextval('workspace_variables_id_seq'::regclass);
|
||||
|
|
@ -28663,6 +28686,9 @@ ALTER TABLE ONLY work_item_text_field_values
|
|||
ALTER TABLE ONLY work_item_type_custom_fields
|
||||
ADD CONSTRAINT work_item_type_custom_fields_pkey PRIMARY KEY (id);
|
||||
|
||||
ALTER TABLE ONLY work_item_type_user_preferences
|
||||
ADD CONSTRAINT work_item_type_user_preferences_pkey PRIMARY KEY (id);
|
||||
|
||||
ALTER TABLE ONLY work_item_types
|
||||
ADD CONSTRAINT work_item_types_pkey PRIMARY KEY (id);
|
||||
|
||||
|
|
@ -34912,6 +34938,10 @@ CREATE INDEX index_work_item_type_custom_fields_on_custom_field_id ON work_item_
|
|||
|
||||
CREATE INDEX index_work_item_type_custom_fields_on_work_item_type_id ON work_item_type_custom_fields USING btree (work_item_type_id);
|
||||
|
||||
CREATE INDEX index_work_item_type_user_preferences_on_namespace_id ON work_item_type_user_preferences USING btree (namespace_id);
|
||||
|
||||
CREATE INDEX index_work_item_type_user_preferences_on_work_item_type_id ON work_item_type_user_preferences USING btree (work_item_type_id);
|
||||
|
||||
CREATE INDEX index_work_item_types_on_base_type_and_id ON work_item_types USING btree (base_type, id);
|
||||
|
||||
CREATE UNIQUE INDEX index_work_item_types_on_correct_id_unique ON work_item_types USING btree (correct_id);
|
||||
|
|
@ -35284,6 +35314,8 @@ CREATE UNIQUE INDEX uniq_pkgs_debian_project_distributions_project_id_and_codena
|
|||
|
||||
CREATE UNIQUE INDEX uniq_pkgs_debian_project_distributions_project_id_and_suite ON packages_debian_project_distributions USING btree (project_id, suite);
|
||||
|
||||
CREATE INDEX uniq_preference_by_user_namespace_and_work_item_type ON work_item_type_user_preferences USING btree (user_id, namespace_id, work_item_type_id);
|
||||
|
||||
CREATE UNIQUE INDEX unique_amazon_s3_configurations_namespace_id_and_bucket_name ON audit_events_amazon_s3_configurations USING btree (namespace_id, bucket_name);
|
||||
|
||||
CREATE UNIQUE INDEX unique_amazon_s3_configurations_namespace_id_and_name ON audit_events_amazon_s3_configurations USING btree (namespace_id, name);
|
||||
|
|
@ -37764,6 +37796,9 @@ ALTER TABLE ONLY merge_requests
|
|||
ALTER TABLE ONLY clusters_managed_resources
|
||||
ADD CONSTRAINT fk_068dba90c3 FOREIGN KEY (cluster_agent_id) REFERENCES cluster_agents(id) ON DELETE CASCADE;
|
||||
|
||||
ALTER TABLE ONLY work_item_type_user_preferences
|
||||
ADD CONSTRAINT fk_0748f95f41 FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE;
|
||||
|
||||
ALTER TABLE ONLY sbom_occurrences_vulnerabilities
|
||||
ADD CONSTRAINT fk_07b81e3a81 FOREIGN KEY (vulnerability_id) REFERENCES vulnerabilities(id) ON DELETE CASCADE;
|
||||
|
||||
|
|
@ -38055,6 +38090,9 @@ ALTER TABLE ONLY deployment_approvals
|
|||
ALTER TABLE ONLY audit_events_instance_external_audit_event_destinations
|
||||
ADD CONSTRAINT fk_2d3ebd0fbc FOREIGN KEY (stream_destination_id) REFERENCES audit_events_instance_external_streaming_destinations(id) ON DELETE SET NULL;
|
||||
|
||||
ALTER TABLE ONLY work_item_type_user_preferences
|
||||
ADD CONSTRAINT fk_2e37b4f066 FOREIGN KEY (namespace_id) REFERENCES namespaces(id) ON DELETE CASCADE;
|
||||
|
||||
ALTER TABLE ONLY notes
|
||||
ADD CONSTRAINT fk_2e82291620 FOREIGN KEY (review_id) REFERENCES reviews(id) ON DELETE SET NULL;
|
||||
|
||||
|
|
@ -38571,6 +38609,9 @@ ALTER TABLE ONLY topics
|
|||
ALTER TABLE ONLY work_item_text_field_values
|
||||
ADD CONSTRAINT fk_79c719630f FOREIGN KEY (namespace_id) REFERENCES namespaces(id) ON DELETE CASCADE;
|
||||
|
||||
ALTER TABLE ONLY work_item_type_user_preferences
|
||||
ADD CONSTRAINT fk_79e0353950 FOREIGN KEY (work_item_type_id) REFERENCES work_item_types(correct_id) ON DELETE CASCADE;
|
||||
|
||||
ALTER TABLE ONLY packages_maven_metadata
|
||||
ADD CONSTRAINT fk_7a170ee0a3 FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE;
|
||||
|
||||
|
|
|
|||
|
|
@ -83,6 +83,29 @@ You can add parameters to block definitions:
|
|||
|
||||
Markdown does not support any parameters, and always uses PNG format.
|
||||
|
||||
## Include diagram files
|
||||
|
||||
You can include or embed a PlantUML diagram from separate files in the repository using
|
||||
the `include` directive. Use this to maintain complex diagrams in dedicated files, or to
|
||||
reuse diagrams. For example:
|
||||
|
||||
- **Markdown**:
|
||||
|
||||
````markdown
|
||||
```plantuml
|
||||
::include{file=diagram.puml}
|
||||
```
|
||||
````
|
||||
|
||||
- **AsciiDoc**:
|
||||
|
||||
```plaintext
|
||||
[plantuml, format="png", id="myDiagram", width="200px"]
|
||||
----
|
||||
include::diagram.puml[]
|
||||
----
|
||||
```
|
||||
|
||||
## Configure your PlantUML server
|
||||
|
||||
Before you can enable PlantUML in GitLab, set up your own PlantUML
|
||||
|
|
|
|||
|
|
@ -25,29 +25,34 @@ Install one of the following GitLab-approved LLM models:
|
|||
|
||||
<!-- vale gitlab_base.Spelling = NO -->
|
||||
|
||||
| Model family | Model | Code completion | Code generation | GitLab Duo Chat |
|
||||
|--------------|------------------------------------------------------------------------------------|-----------------|-----------------|---------|
|
||||
| Mistral Codestral | [Codestral 22B v0.1](https://huggingface.co/mistralai/Codestral-22B-v0.1) | **{check-circle}** Yes | **{check-circle}** Yes | **{dotted-circle}** No |
|
||||
| Mistral | [Mistral 7B-it v0.3](https://huggingface.co/mistralai/Mistral-7B-Instruct-v0.3) | **{check-circle}** Yes | **{check-circle}** Yes | **{check-circle}** Yes |
|
||||
| Mistral | [Mixtral 8x7B-it v0.1](https://huggingface.co/mistralai/Mixtral-8x7B-Instruct-v0.1) | **{check-circle}** Yes | **{check-circle}** Yes | **{check-circle}** Yes |
|
||||
| Mistral | [Mixtral 8x22B-it v0.1](https://huggingface.co/mistralai/Mixtral-8x22B-Instruct-v0.1) | **{check-circle}** Yes | **{check-circle}** Yes | **{check-circle}** Yes |
|
||||
| Claude 3 | [Claude 3.5 Sonnet](https://www.anthropic.com/news/claude-3-5-sonnet) | **{check-circle}** Yes | **{check-circle}** Yes | **{check-circle}** Yes |
|
||||
| GPT | [GPT-4 Turbo](https://learn.microsoft.com/en-us/azure/ai-services/openai/concepts/models?tabs=python-secure#gpt-4) | **{check-circle}** Yes | **{check-circle}** Yes | **{check-circle}** Yes |
|
||||
| GPT | [GPT-4o](https://learn.microsoft.com/en-us/azure/ai-services/openai/concepts/models?tabs=python-secure#gpt-4o-and-gpt-4-turbo) | **{check-circle}** Yes | **{check-circle}** Yes | **{check-circle}** Yes |
|
||||
| GPT | [GPT-4o-mini](https://learn.microsoft.com/en-us/azure/ai-services/openai/concepts/models?tabs=python-secure#gpt-4o-and-gpt-4-turbo) | **{check-circle}** Yes | **{check-circle}** Yes | **{check-circle}** Yes |
|
||||
| Model family | Model | Supported platforms | Status | Code completion | Code generation | GitLab Duo Chat |
|
||||
|--------------|-------|---------------------|--------|-----------------|-----------------|-----------------|
|
||||
|Mistral Codestral | [Codestral 22B v0.1](https://huggingface.co/mistralai/Codestral-22B-v0.1) | [vLLM](supported_llm_serving_platforms.md#for-self-hosted-model-deployments) | Generally available | 🟢 Green | 🟢 Green | N/A |
|
||||
| Mistral | [Mistral 7B-it v0.3](https://huggingface.co/mistralai/Mistral-7B-Instruct-v0.3) | [vLLM](supported_llm_serving_platforms.md#for-self-hosted-model-deployments) | Generally available | 🟢 Green | 🟢 Green | 🔴 Red |
|
||||
| Mistral | [Mixtral 8x7B-it v0.1](https://huggingface.co/mistralai/Mixtral-8x7B-Instruct-v0.1) | [vLLM](supported_llm_serving_platforms.md#for-self-hosted-model-deployments) <br> [AWS Bedrock](https://aws.amazon.com/bedrock/mistral/) | Generally available | 🟢 Green | 🟢 Green | 🟡 Amber |
|
||||
| Mistral | [Mixtral 8x22B-it v0.1](https://huggingface.co/mistralai/Mixtral-8x22B-Instruct-v0.1) | [vLLM](supported_llm_serving_platforms.md#for-self-hosted-model-deployments) | Generally available | 🟢 Green | 🟢 Green | 🟢 Green |
|
||||
| Claude 3 | [Claude 3.5 Sonnet](https://www.anthropic.com/news/claude-3-5-sonnet) | [AWS Bedrock](https://aws.amazon.com/bedrock/claude/) | Generally available | 🟢 Green | 🟢 Green | 🟢 Green |
|
||||
| GPT | [GPT-4 Turbo](https://learn.microsoft.com/en-us/azure/ai-services/openai/concepts/models?tabs=python-secure#gpt-4) | [Azure OpenAI](https://learn.microsoft.com/en-us/azure/ai-services/openai/overview) | Generally available | 🟢 Green | 🟢 Green | 🟡 Amber |
|
||||
| GPT | [GPT-4o](https://learn.microsoft.com/en-us/azure/ai-services/openai/concepts/models?tabs=python-secure#gpt-4o-and-gpt-4-turbo) | [Azure OpenAI](https://learn.microsoft.com/en-us/azure/ai-services/openai/overview) | Generally available | 🟢 Green | 🟢 Green | 🟢 Green |
|
||||
| GPT | [GPT-4o-mini](https://learn.microsoft.com/en-us/azure/ai-services/openai/concepts/models?tabs=python-secure#gpt-4o-and-gpt-4-turbo) | [Azure OpenAI](https://learn.microsoft.com/en-us/azure/ai-services/openai/overview) | Generally available | 🟢 Green | 🟢 Green | 🟡 Amber |
|
||||
|
||||
Legend:
|
||||
|
||||
- 🟢 Green - Strongly recommended. The model can handle the feature without any loss of quality.
|
||||
- 🟡 Amber - Recommended. The model supports the feature, but there might be minor compromises or limitations.
|
||||
- 🔴 Red - Not recommended. The model is unsuitable for the feature, likely resulting in significant quality loss or performance issues.
|
||||
|
||||
The following models are under evaluation, and support is limited:
|
||||
|
||||
| Model family | Model | Code completion | Code generation | GitLab Duo Chat |
|
||||
|--------------- |---------------------------------------------------------------------|-----------------|-----------------|---------|
|
||||
| CodeGemma | [CodeGemma 2b](https://huggingface.co/google/codegemma-2b) | **{check-circle}** Yes | **{dotted-circle}** No | **{dotted-circle}** No |
|
||||
| CodeGemma | [CodeGemma 7b-it](https://huggingface.co/google/codegemma-7b-it) | **{dotted-circle}** No | **{check-circle}** Yes | **{dotted-circle}** No |
|
||||
| CodeGemma | [CodeGemma 7b-code](https://huggingface.co/google/codegemma-7b) | **{check-circle}** Yes | **{dotted-circle}** No | **{dotted-circle}** No |
|
||||
| Code Llama | [Code-Llama 13b-code](https://huggingface.co/meta-llama/CodeLlama-13b-hf) | **{check-circle}** Yes | **{dotted-circle}** No | **{dotted-circle}** No |
|
||||
| Code Llama | [Code-Llama 13b](https://huggingface.co/meta-llama/CodeLlama-13b-Instruct-hf) | **{dotted-circle}** No | **{check-circle}** Yes | **{dotted-circle}** No |
|
||||
| DeepSeek Coder | [DeepSeek Coder 33b Instruct](https://huggingface.co/deepseek-ai/deepseek-coder-33b-instruct) | **{check-circle}** Yes | **{check-circle}** Yes | **{dotted-circle}** No |
|
||||
| DeepSeek Coder | [DeepSeek Coder 33b Base](https://huggingface.co/deepseek-ai/deepseek-coder-33b-base) | **{check-circle}** Yes | **{dotted-circle}** No | **{dotted-circle}** No |
|
||||
| Mistral | [Mistral 7B-it v0.2](https://huggingface.co/mistralai/Mistral-7B-Instruct-v0.2) | **{check-circle}** Yes | **{check-circle}** Yes | **{check-circle}** Yes |
|
||||
| Model family | Model | Supported platforms | Status | Code completion | Code generation | GitLab Duo Chat |
|
||||
|--------------- |-------|---------------------|--------|-----------------|-----------------|-----------------|
|
||||
| CodeGemma | [CodeGemma 2b](https://huggingface.co/google/codegemma-2b) | [vLLM](supported_llm_serving_platforms.md#for-self-hosted-model-deployments) | Beta | **{check-circle}** Yes | **{dotted-circle}** No | **{dotted-circle}** No |
|
||||
| CodeGemma | [CodeGemma 7b-it](https://huggingface.co/google/codegemma-7b-it) | [vLLM](supported_llm_serving_platforms.md#for-self-hosted-model-deployments) | Beta | **{dotted-circle}** No | **{check-circle}** Yes | **{dotted-circle}** No |
|
||||
| CodeGemma | [CodeGemma 7b-code](https://huggingface.co/google/codegemma-7b) | [vLLM](supported_llm_serving_platforms.md#for-self-hosted-model-deployments) | Beta | **{check-circle}** Yes | **{dotted-circle}** No | **{dotted-circle}** No |
|
||||
| Code Llama | [Code-Llama 13b](https://huggingface.co/meta-llama/CodeLlama-13b-Instruct-hf) | [vLLM](supported_llm_serving_platforms.md#for-self-hosted-model-deployments) | Beta | **{dotted-circle}** No | **{check-circle}** Yes | **{dotted-circle}** No |
|
||||
| DeepSeek Coder | [DeepSeek Coder 33b Instruct](https://huggingface.co/deepseek-ai/deepseek-coder-33b-instruct) | [vLLM](supported_llm_serving_platforms.md#for-self-hosted-model-deployments) | Beta | **{check-circle}** Yes | **{check-circle}** Yes | **{dotted-circle}** No |
|
||||
| DeepSeek Coder | [DeepSeek Coder 33b Base](https://huggingface.co/deepseek-ai/deepseek-coder-33b-base) | [vLLM](supported_llm_serving_platforms.md#for-self-hosted-model-deployments) | Beta | **{check-circle}** Yes | **{dotted-circle}** No | **{dotted-circle}** No |
|
||||
| Mistral | [Mistral 7B-it v0.2](https://huggingface.co/mistralai/Mistral-7B-Instruct-v0.2) | [vLLM](supported_llm_serving_platforms.md#for-self-hosted-model-deployments) <br> [AWS Bedrock](https://aws.amazon.com/bedrock/mistral/) | Beta | **{check-circle}** Yes | **{check-circle}** Yes | **{check-circle}** Yes |
|
||||
|
||||
<!-- vale gitlab_base.Spelling = YES -->
|
||||
|
||||
|
|
|
|||
|
|
@ -73,54 +73,14 @@ The option is not available when an artifact has no expiry set.
|
|||
|
||||
By default, the [latest artifacts are always kept](#keep-artifacts-from-most-recent-successful-jobs).
|
||||
|
||||
### With a dynamically defined name
|
||||
### With an explicitly defined artifact name
|
||||
|
||||
You can use [CI/CD variables](../variables/index.md) to dynamically define the
|
||||
artifacts file's name.
|
||||
|
||||
For example, to create an archive with a name of the current job:
|
||||
You can explicitly customize artifact names using the [`artifacts:name`](../yaml/index.md#artifactsname) configuration:
|
||||
|
||||
```yaml
|
||||
job:
|
||||
artifacts:
|
||||
name: "$CI_JOB_NAME"
|
||||
paths:
|
||||
- binaries/
|
||||
```
|
||||
|
||||
To create an archive with a name of the current branch or tag including only
|
||||
the binaries directory:
|
||||
|
||||
```yaml
|
||||
job:
|
||||
artifacts:
|
||||
name: "$CI_COMMIT_REF_NAME"
|
||||
paths:
|
||||
- binaries/
|
||||
```
|
||||
|
||||
If your branch-name contains forward slashes
|
||||
(for example `feature/my-feature`) use `$CI_COMMIT_REF_SLUG`
|
||||
instead of `$CI_COMMIT_REF_NAME` for proper naming of the artifact.
|
||||
|
||||
### With a Windows runner or shell executor
|
||||
|
||||
If you use Windows Batch to run your shell scripts you must replace `$` with `%`:
|
||||
|
||||
```yaml
|
||||
job:
|
||||
artifacts:
|
||||
name: "%CI_JOB_STAGE%-%CI_COMMIT_REF_NAME%"
|
||||
paths:
|
||||
- binaries/
|
||||
```
|
||||
|
||||
If you use Windows PowerShell to run your shell scripts you must replace `$` with `$env:`:
|
||||
|
||||
```yaml
|
||||
job:
|
||||
artifacts:
|
||||
name: "$env:CI_JOB_STAGE-$env:CI_COMMIT_REF_NAME"
|
||||
name: "job1-artifacts-file"
|
||||
paths:
|
||||
- binaries/
|
||||
```
|
||||
|
|
@ -179,6 +139,34 @@ artifacts:
|
|||
- "*.txt"
|
||||
```
|
||||
|
||||
### With variable expansion
|
||||
|
||||
Variable expansion is supported for:
|
||||
|
||||
- [`artifacts:name`](../yaml/index.md#artifactsname)
|
||||
- [`artifacts:paths`](../yaml/index.md#artifactspaths)
|
||||
- [`artifacts:exclude`](../yaml/index.md#artifactsexclude)
|
||||
|
||||
Instead of using shell, GitLab Runner uses its
|
||||
[internal variable expansion mechanism](../variables/where_variables_can_be_used.md#gitlab-runner-internal-variable-expansion-mechanism).
|
||||
Only [CI/CD variables](../variables/index.md) are supported in this context.
|
||||
|
||||
For example, to create an archive using the current branch or tag name
|
||||
including only files from a directory named after the current project:
|
||||
|
||||
```yaml
|
||||
job:
|
||||
artifacts:
|
||||
name: "$CI_COMMIT_REF_NAME"
|
||||
paths:
|
||||
- binaries/${CI_PROJECT_NAME}/"
|
||||
```
|
||||
|
||||
When your branch name contains forward slashes (for example, `feature/my-feature`),
|
||||
use `$CI_COMMIT_REF_SLUG` instead of `$CI_COMMIT_REF_NAME` to ensure proper artifact naming.
|
||||
|
||||
Variables are expanded before [globs](https://en.wikipedia.org/wiki/Glob_(programming)).
|
||||
|
||||
## Fetching artifacts
|
||||
|
||||
By default, jobs fetch all artifacts from jobs defined in previous stages. These artifacts are downloaded into the job's working directory.
|
||||
|
|
|
|||
|
|
@ -966,7 +966,7 @@ job1:
|
|||
|
||||
The metadata renders in a plain text `.json` file stored with the artifact. The
|
||||
filename is `{ARTIFACT_NAME}-metadata.json`. `ARTIFACT_NAME` is the
|
||||
[name for the artifact](../jobs/job_artifacts.md#with-a-dynamically-defined-name)
|
||||
[name for the artifact](../jobs/job_artifacts.md#with-an-explicitly-defined-artifact-name)
|
||||
defined in the `.gitlab-ci.yml` file. If the name is not defined, the default filename is
|
||||
`artifacts-metadata.json`.
|
||||
|
||||
|
|
|
|||
|
|
@ -32,6 +32,7 @@ There are two places defined variables can be used. On the:
|
|||
| [`after_script`](../yaml/index.md#after_script) | yes | Script execution shell | The variable expansion is made by the [execution shell environment](#execution-shell-environment). |
|
||||
| [`artifacts:name`](../yaml/index.md#artifactsname) | yes | Runner | The variable expansion is made by GitLab Runner's [internal variable expansion mechanism](#gitlab-runner-internal-variable-expansion-mechanism). |
|
||||
| [`artifacts:paths`](../yaml/index.md#artifactspaths) | yes | Runner | The variable expansion is made by GitLab Runner's [internal variable expansion mechanism](#gitlab-runner-internal-variable-expansion-mechanism). |
|
||||
| [`artifacts:exclude`](../yaml/index.md#artifactsexclude) | yes | Runner | The variable expansion is made by GitLab Runner's [internal variable expansion mechanism](#gitlab-runner-internal-variable-expansion-mechanism). |
|
||||
| [`before_script`](../yaml/index.md#before_script) | yes | Script execution shell | The variable expansion is made by the [execution shell environment](#execution-shell-environment) |
|
||||
| [`cache:key`](../yaml/index.md#cachekey) | yes | Runner | The variable expansion is made by GitLab Runner's [internal variable expansion mechanism](#gitlab-runner-internal-variable-expansion-mechanism). |
|
||||
| [`cache:paths`](../yaml/index.md#cachepaths) | yes | Runner | The variable expansion is made by GitLab Runner's [internal variable expansion mechanism](#gitlab-runner-internal-variable-expansion-mechanism). |
|
||||
|
|
@ -107,6 +108,11 @@ the expansion is done only once, so nested variables may or may not work, depend
|
|||
ordering of variables definitions, and whether [nested variable expansion](#nested-variable-expansion)
|
||||
is enabled in GitLab.
|
||||
|
||||
For artifacts and cache uploads, the runner uses
|
||||
[mvdan.cc/sh/v3/expand](https://pkg.go.dev/mvdan.cc/sh/v3/expand) for variable
|
||||
expansion instead of Go's `os.Expand()` because `mvdan.cc/sh/v3/expand` supports
|
||||
[parameter expansion](https://www.gnu.org/software/bash/manual/html_node/Shell-Parameter-Expansion.html).
|
||||
|
||||
### Execution shell environment
|
||||
|
||||
This is an expansion phase that takes place during the `script` execution.
|
||||
|
|
|
|||
|
|
@ -1459,7 +1459,7 @@ job:
|
|||
|
||||
**Related topics**:
|
||||
|
||||
- [Use CI/CD variables to define the artifacts name](../jobs/job_artifacts.md#with-a-dynamically-defined-name).
|
||||
- [Use CI/CD variables to define the artifacts configuration](../jobs/job_artifacts.md#with-variable-expansion)
|
||||
|
||||
#### `artifacts:public`
|
||||
|
||||
|
|
|
|||
|
|
@ -14,6 +14,9 @@ At a high level, to add a new relation to the direct transfer importer, you must
|
|||
1. Add a label for the newly created relation to display in the UI.
|
||||
1. Ensure sufficient test coverage.
|
||||
|
||||
NOTE:
|
||||
To mitigate the risk of introducing bugs and performance issues, newly added relations should be put behind a feature flag.
|
||||
|
||||
## Export from source
|
||||
|
||||
There are a few types of relations we export:
|
||||
|
|
|
|||
|
|
@ -6,6 +6,9 @@ info: Any user with at least the Maintainer role can merge updates to this conte
|
|||
|
||||
# Import/Export development documentation
|
||||
|
||||
NOTE:
|
||||
To mitigate the risk of introducing bugs and performance issues, newly added relations should be put behind a feature flag.
|
||||
|
||||
General development guidelines and tips for the [Import/Export feature](../user/project/settings/import_export.md).
|
||||
|
||||
<i class="fa fa-youtube-play youtube" aria-hidden="true"></i> This document is originally based on the [Import/Export 201 presentation available on YouTube](https://www.youtube.com/watch?v=V3i1OfExotE).
|
||||
|
|
|
|||
|
|
@ -130,7 +130,7 @@ deploy-pages: # a user-defined job that builds your pages and saves them to the
|
|||
`rules`, you're checking that this commit was made on the default branch. Typically, you wouldn't want to build and
|
||||
deploy the live site from another branch.
|
||||
|
||||
You don't need to add anything else to this file. When you're ready, select **Commit changes** at the bottom of the page.
|
||||
You don't need to add anything else to this file. When you're ready, select **Commit changes** at the top of the page.
|
||||
|
||||
You've just triggered a pipeline to build your Hugo site!
|
||||
|
||||
|
|
@ -142,7 +142,7 @@ From the left-hand navigation, select **Build > Pipelines**.
|
|||
|
||||
You'll see that GitLab has run your `test` and `deploy-pages` jobs.
|
||||
|
||||
To view your site, on the left-hand navigation, select **Settings > Pages**
|
||||
To view your site, on the left-hand navigation, select **Deploy > Pages**
|
||||
|
||||
The `pages` job in your pipeline has deployed the contents of your `public` directory to GitLab Pages. Under **Access pages**, you should see the link in the format: `https://<your-namespace>.gitlab.io/<project-path>`.
|
||||
|
||||
|
|
@ -152,7 +152,7 @@ Select the displayed link to view your site.
|
|||
|
||||
When you first view your Hugo site, the stylesheet won't work. Don't worry, you need to make a small change in your Hugo configuration file. Hugo needs to know the URL of your GitLab Pages site so it can build relative links to stylesheets and other assets:
|
||||
|
||||
1. In your local Hugo site, open your `config.yaml` or `config.toml` file.
|
||||
1. In your local Hugo site, pull the latest changes, and open your `config.yaml` or `config.toml` file.
|
||||
1. Change the value of the `BaseURL` parameter to match the URL that appears in your GitLab Pages settings.
|
||||
1. Push your changed file to GitLab, and your pipeline is triggered again.
|
||||
|
||||
|
|
|
|||
|
|
@ -247,6 +247,14 @@ pages, change the filename from `.adoc` to `.asciidoc`.
|
|||
include::basics.adoc[]
|
||||
```
|
||||
|
||||
```plaintext
|
||||
// you can also include other files from you repository
|
||||
[,language]
|
||||
----
|
||||
include::my_code_file.language[]
|
||||
----
|
||||
```
|
||||
|
||||
To guarantee good system performance and prevent malicious documents from causing
|
||||
problems, GitLab enforces a maximum limit on the number of include directives
|
||||
processed in any one document. By default, a document can have up to 32 include directives, which is
|
||||
|
|
@ -504,6 +512,15 @@ Bob -> Alice : hello
|
|||
----
|
||||
```
|
||||
|
||||
To include PlantUML diagrams stored in separate files:
|
||||
|
||||
```plaintext
|
||||
[plantuml, format="png", id="myDiagram", width="200px"]
|
||||
----
|
||||
include::diagram.puml[]
|
||||
----
|
||||
```
|
||||
|
||||
### Multimedia
|
||||
|
||||
```plaintext
|
||||
|
|
|
|||
|
|
@ -1019,6 +1019,20 @@ graph TB
|
|||
PlantUML integration is enabled on GitLab.com. To make PlantUML available in self-managed
|
||||
installation of GitLab, a GitLab administrator [must enable it](../administration/integration/plantuml.md).
|
||||
|
||||
After you enable PlantUML, diagram delimiters `@startuml`/`@enduml` aren't required, as these
|
||||
are replaced by the `plantuml` block. For example:
|
||||
|
||||
````markdown
|
||||
```plantuml
|
||||
Bob -> Alice : hello
|
||||
Alice -> Bob : hi
|
||||
```
|
||||
````
|
||||
|
||||
You can include or embed a PlantUML diagram from separate files in the repository using
|
||||
the `::include` directive.
|
||||
For more information, see [Include diagtram files](../administration/integration/plantuml.md#include-diagram-files).
|
||||
|
||||
### Kroki
|
||||
|
||||
To make Kroki available in GitLab, a GitLab administrator must enable it.
|
||||
|
|
@ -1447,28 +1461,28 @@ display a color chip next to the color code. For example:
|
|||
|
||||
[View this topic rendered in GitLab](https://gitlab.com/gitlab-org/gitlab/-/blob/master/doc/user/markdown.md#emoji).
|
||||
|
||||
Sometimes you want to <img src="https://gitlab.com/gitlab-org/gitlab-foss/raw/master/public/-/emojis/2/monkey.png" width="20px" height="20px" style="display:inline;margin:0;border:0;padding:0;" title=":monkey:" alt=":monkey:">
|
||||
around a bit and add some <img src="https://gitlab.com/gitlab-org/gitlab-foss/raw/master/public/-/emojis/2/star2.png" width="20px" height="20px" style="display:inline;margin:0;border:0;padding:0;" title=":star2:" alt=":star2:">
|
||||
to your <img src="https://gitlab.com/gitlab-org/gitlab-foss/raw/master/public/-/emojis/2/speech_balloon.png" width="20px" height="20px" style="display:inline;margin:0;border:0;padding:0;" title=":speech_balloon:" alt=":speech_balloon:">.
|
||||
Sometimes you want to <img src="https://gitlab.com/gitlab-org/gitlab/-/raw/master/public/-/emojis/4/monkey.png" width="20px" height="20px" style="display:inline;margin:0;border:0;padding:0;" title=":monkey:" alt=":monkey:">
|
||||
around a bit and add some <img src="https://gitlab.com/gitlab-org/gitlab/-/raw/master/public/-/emojis/4/star2.png" width="20px" height="20px" style="display:inline;margin:0;border:0;padding:0;" title=":star2:" alt=":star2:">
|
||||
to your <img src="https://gitlab.com/gitlab-org/gitlab/-/raw/master/public/-/emojis/4/speech_balloon.png" width="20px" height="20px" style="display:inline;margin:0;border:0;padding:0;" title=":speech_balloon:" alt=":speech_balloon:">.
|
||||
Well we have a gift for you:
|
||||
|
||||
<img src="https://gitlab.com/gitlab-org/gitlab-foss/raw/master/public/-/emojis/2/zap.png" width="20px" height="20px" style="display:inline;margin:0;border:0;padding:0;" title=":zap:" alt=":zap:">
|
||||
<img src="https://gitlab.com/gitlab-org/gitlab/-/raw/master/public/-/emojis/4/zap.png" width="20px" height="20px" style="display:inline;margin:0;border:0;padding:0;" title=":zap:" alt=":zap:">
|
||||
You can use emoji anywhere GitLab Flavored Markdown is supported.
|
||||
<img src="https://gitlab.com/gitlab-org/gitlab-foss/raw/master/public/-/emojis/2/v.png" width="20px" height="20px" style="display:inline;margin:0;border:0;padding:0;" title=":v:" alt=":v:">
|
||||
<img src="https://gitlab.com/gitlab-org/gitlab/-/raw/master/public/-/emojis/4/v.png" width="20px" height="20px" style="display:inline;margin:0;border:0;padding:0;" title=":v:" alt=":v:">
|
||||
|
||||
You can use it to point out a <img src="https://gitlab.com/gitlab-org/gitlab-foss/raw/master/public/-/emojis/2/bug.png" width="20px" height="20px" style="display:inline;margin:0;border:0;padding:0;" title=":bug:" alt=":bug:">
|
||||
or warn about <img src="https://gitlab.com/gitlab-org/gitlab-foss/raw/master/public/-/emojis/2/speak_no_evil.png" width="20px" height="20px" style="display:inline;margin:0;border:0;padding:0;" title=":speak_no_evil:" alt=":speak_no_evil:">
|
||||
patches. If someone improves your really <img src="https://gitlab.com/gitlab-org/gitlab-foss/raw/master/public/-/emojis/2/snail.png" width="20px" height="20px" style="display:inline;margin:0;border:0;padding:0;" title=":snail:" alt=":snail:">
|
||||
code, send them some <img src="https://gitlab.com/gitlab-org/gitlab-foss/raw/master/public/-/emojis/2/birthday.png" width="20px" height="20px" style="display:inline;margin:0;border:0;padding:0;" title=":birthday:" alt=":birthday:">.
|
||||
People <img src="https://gitlab.com/gitlab-org/gitlab-foss/raw/master/public/-/emojis/2/heart.png" width="20px" height="20px" style="display:inline;margin:0;border:0;padding:0;" title=":heart:" alt=":heart:">
|
||||
You can use it to point out a <img src="https://gitlab.com/gitlab-org/gitlab/-/raw/master/public/-/emojis/4/bug.png" width="20px" height="20px" style="display:inline;margin:0;border:0;padding:0;" title=":bug:" alt=":bug:">
|
||||
or warn about <img src="https://gitlab.com/gitlab-org/gitlab/-/raw/master/public/-/emojis/4/speak_no_evil.png" width="20px" height="20px" style="display:inline;margin:0;border:0;padding:0;" title=":speak_no_evil:" alt=":speak_no_evil:">
|
||||
patches. If someone improves your really <img src="https://gitlab.com/gitlab-org/gitlab/-/raw/master/public/-/emojis/4/snail.png" width="20px" height="20px" style="display:inline;margin:0;border:0;padding:0;" title=":snail:" alt=":snail:">
|
||||
code, send them some <img src="https://gitlab.com/gitlab-org/gitlab/-/raw/master/public/-/emojis/4/birthday.png" width="20px" height="20px" style="display:inline;margin:0;border:0;padding:0;" title=":birthday:" alt=":birthday:">.
|
||||
People <img src="https://gitlab.com/gitlab-org/gitlab/-/raw/master/public/-/emojis/4/heart.png" width="20px" height="20px" style="display:inline;margin:0;border:0;padding:0;" title=":heart:" alt=":heart:">
|
||||
you for that.
|
||||
|
||||
If you're new to this, don't be <img src="https://gitlab.com/gitlab-org/gitlab-foss/raw/master/public/-/emojis/2/fearful.png" width="20px" height="20px" style="display:inline;margin:0;border:0;padding:0;" title=":fearful:" alt=":fearful:">.
|
||||
You can join the emoji <img src="https://gitlab.com/gitlab-org/gitlab-foss/raw/master/public/-/emojis/2/family.png" width="20px" height="20px" style="display:inline;margin:0;border:0;padding:0;" title=":family:" alt=":family:">.
|
||||
If you're new to this, don't be <img src="https://gitlab.com/gitlab-org/gitlab/-/raw/master/public/-/emojis/4/fearful.png" width="20px" height="20px" style="display:inline;margin:0;border:0;padding:0;" title=":fearful:" alt=":fearful:">.
|
||||
You can join the emoji <img src="https://gitlab.com/gitlab-org/gitlab/-/raw/master/public/-/emojis/4/family.png" width="20px" height="20px" style="display:inline;margin:0;border:0;padding:0;" title=":family:" alt=":family:">.
|
||||
Just look up one of the supported codes.
|
||||
|
||||
Consult the [TanukiEmoji reference](https://gitlab-org.gitlab.io/ruby/gems/tanuki_emoji/) for a list
|
||||
of all supported emoji codes. <img src="https://gitlab.com/gitlab-org/gitlab-foss/raw/master/public/-/emojis/4/thumbsup.png" width="20px" height="20px" style="display:inline;margin:0;border:0;padding:0;" title=":thumbsup:" alt=":thumbsup:">
|
||||
of all supported emoji codes. <img src="https://gitlab.com/gitlab-org/gitlab/-/raw/master/public/-/emojis/4/thumbsup.png" width="20px" height="20px" style="display:inline;margin:0;border:0;padding:0;" title=":thumbsup:" alt=":thumbsup:">
|
||||
|
||||
The above paragraphs in raw Markdown:
|
||||
|
||||
|
|
@ -1628,6 +1642,35 @@ To use includes from separate wiki pages or external URLs, administrators can en
|
|||
::include{file=https://example.org/installation.md}
|
||||
```
|
||||
|
||||
### Use includes in code blocks
|
||||
|
||||
You can use the `::include` directive inside code blocks to add content from files in your repository.
|
||||
For example, if your repository contains a file `javascript_code.js`:
|
||||
|
||||
```javascript
|
||||
var s = "JavaScript syntax highlighting";
|
||||
alert(s);
|
||||
```
|
||||
|
||||
You can include it in your Markdown file:
|
||||
|
||||
````markdown
|
||||
Our script contains:
|
||||
|
||||
```javascript
|
||||
::include{file=javascript_code.js}
|
||||
```
|
||||
````
|
||||
|
||||
The content renders as:
|
||||
|
||||
Our script contains:
|
||||
|
||||
```javascript
|
||||
var s = "JavaScript syntax highlighting";
|
||||
alert(s);
|
||||
```
|
||||
|
||||
## Escape characters
|
||||
|
||||
[View this topic rendered in GitLab](https://gitlab.com/gitlab-org/gitlab/-/blob/master/doc/user/markdown.md#escape-characters).
|
||||
|
|
|
|||
|
|
@ -24,13 +24,7 @@ module Gitlab
|
|||
def cached_application_settings
|
||||
return in_memory_application_settings if ENV['IN_MEMORY_APPLICATION_SETTINGS'] == 'true'
|
||||
|
||||
begin
|
||||
::ApplicationSetting.cached
|
||||
rescue StandardError
|
||||
# In case Redis isn't running
|
||||
# or the Redis UNIX socket file is not available
|
||||
# or the DB is not running (we use migrations in the cache key)
|
||||
end
|
||||
::ApplicationSetting.cached
|
||||
end
|
||||
|
||||
def uncached_application_settings
|
||||
|
|
|
|||
|
|
@ -52461,6 +52461,9 @@ msgstr ""
|
|||
msgid "SecurityReports|All tools"
|
||||
msgstr ""
|
||||
|
||||
msgid "SecurityReports|Attach to existing issue"
|
||||
msgstr ""
|
||||
|
||||
msgid "SecurityReports|CVSS v%{version}:"
|
||||
msgstr ""
|
||||
|
||||
|
|
|
|||
|
|
@ -198,6 +198,21 @@ function setup_db_praefect() {
|
|||
function setup_db() {
|
||||
section_start "setup-db" "Setting up DBs"
|
||||
|
||||
if [[ -f pg_dumpall.sql ]] && ! [[ "$DECOMPOSED_DB" =~ "false" ]]; then
|
||||
echo "Found pg_dumpall.sql, applying!"
|
||||
|
||||
psql -h postgres -U postgres -q < pg_dumpall.sql > /dev/null
|
||||
rm pg_dumpall.sql
|
||||
|
||||
section_end "setup-db"
|
||||
return 0
|
||||
fi
|
||||
|
||||
if [[ -f pg_dumpall.sql ]]; then
|
||||
echo "Found pg_dumpall.sql but we're not using a standard multi-db (decomposed) setup. Performing a regular db setup instead."
|
||||
rm pg_dumpall.sql
|
||||
fi
|
||||
|
||||
setup_db_user_only
|
||||
run_timed_command_with_metric "bundle exec rake db:drop db:create db:schema:load db:migrate gitlab:db:lock_writes" "setup_db"
|
||||
setup_db_praefect
|
||||
|
|
|
|||
|
|
@ -0,0 +1,9 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
FactoryBot.define do
|
||||
factory :work_item_user_preference, class: 'WorkItems::UserPreference' do
|
||||
association :user
|
||||
association :namespace
|
||||
association :work_item_type
|
||||
end
|
||||
end
|
||||
|
|
@ -1,3 +1,4 @@
|
|||
import { GlFormGroup } from '@gitlab/ui';
|
||||
import { shallowMount } from '@vue/test-utils';
|
||||
import { TEST_HOST } from 'helpers/test_constants';
|
||||
import GfmAutoComplete from '~/gfm_auto_complete';
|
||||
|
|
@ -14,7 +15,7 @@ describe('RelatedIssuableInput', () => {
|
|||
issues: `${TEST_HOST}/h5bp/html5-boilerplate/-/autocomplete_sources/issues`,
|
||||
};
|
||||
|
||||
const mountComponent = (props = {}) => {
|
||||
const mountComponent = (props = {}, stubs) => {
|
||||
wrapper = shallowMount(RelatedIssuableInput, {
|
||||
propsData: {
|
||||
inputValue: '',
|
||||
|
|
@ -25,6 +26,7 @@ describe('RelatedIssuableInput', () => {
|
|||
...props,
|
||||
},
|
||||
attachTo: document.body,
|
||||
stubs,
|
||||
});
|
||||
};
|
||||
|
||||
|
|
@ -96,4 +98,28 @@ describe('RelatedIssuableInput', () => {
|
|||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('description', () => {
|
||||
const findDescription = () => wrapper.find('span');
|
||||
|
||||
it('shows description text', () => {
|
||||
mountComponent(undefined, { GlFormGroup });
|
||||
|
||||
expect(findDescription().text()).toBe(
|
||||
'Only issues can be linked from this form. You can also link this issue from an epic or task.',
|
||||
);
|
||||
});
|
||||
|
||||
it('hides description when inline prop is true', () => {
|
||||
mountComponent({ inline: true }, { GlFormGroup });
|
||||
|
||||
expect(findDescription().exists()).toBe(false);
|
||||
});
|
||||
|
||||
it('hides description when issuableType is not TYPE_ISSUE', () => {
|
||||
mountComponent({ issuableType: 'MergeRequest' }, { GlFormGroup });
|
||||
|
||||
expect(findDescription().exists()).toBe(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -513,7 +513,7 @@ describe('~/vue_merge_request_widget/components/widget/widget.vue', () => {
|
|||
});
|
||||
|
||||
expect(findActionButtons().props('tertiaryButtons')).toEqual([
|
||||
expect.objectContaining({ href: 'reportsTabPath?type=Test', text: 'View report' }),
|
||||
expect.objectContaining({ href: 'reportsTabPath/test', text: 'View report' }),
|
||||
]);
|
||||
});
|
||||
|
||||
|
|
@ -531,11 +531,7 @@ describe('~/vue_merge_request_widget/components/widget/widget.vue', () => {
|
|||
await nextTick();
|
||||
|
||||
expect(window.mrTabs.tabShown).toHaveBeenCalledWith('reports');
|
||||
expect(window.history.replaceState).toHaveBeenCalledWith(
|
||||
null,
|
||||
null,
|
||||
'reportsTabPath?type=Test',
|
||||
);
|
||||
expect(window.history.replaceState).toHaveBeenCalledWith(null, null, 'reportsTabPath/test');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -166,7 +166,7 @@ RSpec.describe Nav::NewDropdownHelper, feature_category: :navigation do
|
|||
end
|
||||
|
||||
it 'does not have new organization menu item' do
|
||||
expect(view_model[:menu_sections]).to match_array([])
|
||||
expect(view_model[:menu_sections]).to be_empty
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -176,7 +176,7 @@ RSpec.describe Nav::NewDropdownHelper, feature_category: :navigation do
|
|||
end
|
||||
|
||||
it 'does not have new organization menu item' do
|
||||
expect(view_model[:menu_sections]).to match_array([])
|
||||
expect(view_model[:menu_sections]).to be_empty
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -111,7 +111,7 @@ RSpec.describe UsersHelper, feature_category: :user_management do
|
|||
end
|
||||
|
||||
it 'is empty' do
|
||||
expect(profile_actions).to match_array []
|
||||
expect(profile_actions).to be_empty
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Banzai::Pipeline::FullPipeline, feature_category: :markdown do
|
||||
include RepoHelpers
|
||||
using RSpec::Parameterized::TableSyntax
|
||||
|
||||
it_behaves_like 'sanitize pipeline'
|
||||
|
|
@ -259,4 +260,59 @@ RSpec.describe Banzai::Pipeline::FullPipeline, feature_category: :markdown do
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'when using include in code segements' do
|
||||
let_it_be(:project) { create(:project, :repository) }
|
||||
let_it_be(:ref) { 'markdown' }
|
||||
let_it_be(:requested_path) { '/' }
|
||||
let_it_be(:commit) { project.commit(ref) }
|
||||
let_it_be(:context) do
|
||||
{
|
||||
commit: commit,
|
||||
project: project,
|
||||
ref: ref,
|
||||
text_source: :blob,
|
||||
requested_path: requested_path,
|
||||
no_sourcepos: true
|
||||
}
|
||||
end
|
||||
|
||||
let_it_be(:project_files) do
|
||||
{
|
||||
'diagram.puml' => "@startuml\nBob -> Sara : Hello\n@enduml",
|
||||
'code.yaml' => "---\ntest: true"
|
||||
}
|
||||
end
|
||||
|
||||
let(:input) do
|
||||
<<~MD
|
||||
```plantuml
|
||||
::include{file=diagram.puml}
|
||||
```
|
||||
```yaml
|
||||
::include{file=code.yaml}
|
||||
```
|
||||
MD
|
||||
end
|
||||
|
||||
around do |example|
|
||||
create_and_delete_files(project, project_files, branch_name: ref) do
|
||||
example.run
|
||||
end
|
||||
end
|
||||
|
||||
subject(:output) { described_class.call(input, context)[:output].to_html }
|
||||
|
||||
it 'renders PlanUML' do
|
||||
stub_application_setting(plantuml_enabled: true, plantuml_url: "http://localhost:8080")
|
||||
|
||||
is_expected.to include 'http://localhost:8080/png/U9npA2v9B2efpStXSifFKj2rKmXEB4fKi5BmICt9oUToICrB0Sa10FOW35C0'
|
||||
end
|
||||
|
||||
it 'renders code' do
|
||||
is_expected.to include 'language-yaml'
|
||||
is_expected.to include '<span class="na">test</span>'
|
||||
is_expected.to include '<span class="kc">true</span>'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -88,7 +88,7 @@ RSpec.describe ClickHouse::Iterator, :click_house, feature_category: :database d
|
|||
end
|
||||
|
||||
it 'returns no data' do
|
||||
expect(collect_ids_with_batch_size(3)).to match_array([])
|
||||
expect(collect_ids_with_batch_size(3)).to be_empty
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -46,33 +46,6 @@ RSpec.describe Gitlab::ApplicationSettingFetcher, feature_category: :cell do
|
|||
context 'when ENV["IN_MEMORY_APPLICATION_SETTINGS"] is false' do
|
||||
let_it_be(:settings) { create(:application_setting) }
|
||||
|
||||
context 'and an error is raised' do
|
||||
before do
|
||||
# The cached method is called twice:
|
||||
# - ApplicationSettingFetcher
|
||||
# - ApplicationSetting (CachedAttribute module)
|
||||
# For this test, the first needs to raise an exception
|
||||
# The second is swallowed on production so that should not raise an exception
|
||||
# So we only let the first call raise an exception
|
||||
# Alternatively, we could mock Rails.env.production? but I prefer not to
|
||||
raise_exception = true
|
||||
allow(ApplicationSetting).to receive(:cached).twice do
|
||||
if raise_exception
|
||||
raise_exception = false
|
||||
raise(StandardError)
|
||||
else
|
||||
ApplicationSetting.last
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
it 'will retrieve uncached ApplicationSetting' do
|
||||
expect(ApplicationSetting).to receive(:current).and_call_original
|
||||
|
||||
expect(current_application_settings).to eq(settings)
|
||||
end
|
||||
end
|
||||
|
||||
context 'and settings in cache' do
|
||||
before do
|
||||
# Warm the cache
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ require 'nokogiri'
|
|||
|
||||
module Gitlab
|
||||
RSpec.describe Asciidoc, feature_category: :wiki do
|
||||
include RepoHelpers
|
||||
include FakeBlobHelpers
|
||||
|
||||
before do
|
||||
|
|
@ -1004,6 +1005,63 @@ module Gitlab
|
|||
end
|
||||
end
|
||||
|
||||
context 'when using include in code segements' do
|
||||
let_it_be(:project) { create(:project, :repository) }
|
||||
let_it_be(:ref) { 'markdown' }
|
||||
let_it_be(:requested_path) { '/' }
|
||||
let_it_be(:commit) { project.commit(ref) }
|
||||
let_it_be(:context) do
|
||||
{
|
||||
commit: commit,
|
||||
project: project,
|
||||
ref: ref,
|
||||
text_source: :blob,
|
||||
requested_path: requested_path,
|
||||
no_sourcepos: true
|
||||
}
|
||||
end
|
||||
|
||||
let_it_be(:project_files) do
|
||||
{
|
||||
'diagram.puml' => "@startuml\nBob -> Sara : Hello\n@enduml",
|
||||
'code.yaml' => "---\ntest: true"
|
||||
}
|
||||
end
|
||||
|
||||
let(:input) do
|
||||
<<~ADOC
|
||||
[plantuml]
|
||||
----
|
||||
include::diagram.puml[]
|
||||
----
|
||||
[,yaml]
|
||||
----
|
||||
include::code.yaml[]
|
||||
----
|
||||
ADOC
|
||||
end
|
||||
|
||||
subject(:output) { render(input, context) }
|
||||
|
||||
around do |example|
|
||||
create_and_delete_files(project, project_files, branch_name: ref) do
|
||||
example.run
|
||||
end
|
||||
end
|
||||
|
||||
it 'renders PlanUML' do
|
||||
stub_application_setting(plantuml_enabled: true, plantuml_url: "http://localhost:8080")
|
||||
|
||||
is_expected.to include 'http://localhost:8080/png/U9npA2v9B2efpStXSifFKj2rKmXEB4fKi5BmICt9oUToICrB0Se10EdD34a0'
|
||||
end
|
||||
|
||||
it 'renders code' do
|
||||
is_expected.to include 'language-yaml'
|
||||
is_expected.to include '<span class="na">test</span>'
|
||||
is_expected.to include '<span class="kc">true</span>'
|
||||
end
|
||||
end
|
||||
|
||||
it 'detects and converts to a wikilink' do
|
||||
tag = '[[text|url]]'
|
||||
html = render("See #{tag}", {})
|
||||
|
|
|
|||
|
|
@ -63,7 +63,7 @@ RSpec.describe Gitlab::Checks::ChangesAccess, feature_category: :source_code_man
|
|||
it 'calls #new_commits' do
|
||||
expect(project.repository).to receive(:new_commits).and_call_original
|
||||
|
||||
expect(subject.commits).to match_array([])
|
||||
expect(subject.commits).to be_empty
|
||||
end
|
||||
|
||||
context 'when change is for notes ref' do
|
||||
|
|
@ -72,7 +72,7 @@ RSpec.describe Gitlab::Checks::ChangesAccess, feature_category: :source_code_man
|
|||
end
|
||||
|
||||
it 'does not return any commits' do
|
||||
expect(subject.commits).to match_array([])
|
||||
expect(subject.commits).to be_empty
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ RSpec.describe Gitlab::Ci::Jwt, feature_category: :secrets_management do
|
|||
|
||||
it 'has correct values for the custom attributes' do
|
||||
aggregate_failures do
|
||||
expect(payload[:groups_direct]).to match_array([])
|
||||
expect(payload[:groups_direct]).to be_empty
|
||||
expect(payload[:namespace_id]).to eq(namespace.id.to_s)
|
||||
expect(payload[:namespace_path]).to eq(namespace.full_path)
|
||||
expect(payload[:project_id]).to eq(project.id.to_s)
|
||||
|
|
|
|||
|
|
@ -48,7 +48,7 @@ RSpec.describe Gitlab::Ci::Variables::Builder::Release do
|
|||
it 'returns without error' do
|
||||
builder = subject
|
||||
|
||||
expect(builder).to match_array([])
|
||||
expect(builder.to_a).to be_empty
|
||||
expect(builder.errors).to be_nil
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -93,6 +93,7 @@ work_item_type:
|
|||
- parent_restrictions
|
||||
- allowed_child_types_by_name
|
||||
- allowed_parent_types_by_name
|
||||
- user_preferences
|
||||
events:
|
||||
- author
|
||||
- project
|
||||
|
|
|
|||
|
|
@ -163,7 +163,7 @@ RSpec.describe Gitlab::MailRoom, feature_category: :build do
|
|||
let(:custom_config) { { enabled: false } }
|
||||
|
||||
it 'returns an empty array' do
|
||||
expect(described_class.enabled_mailbox_types).to match_array([])
|
||||
expect(described_class.enabled_mailbox_types).to be_empty
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -104,7 +104,7 @@ RSpec.describe Gitlab::SearchResults, feature_category: :global_search do
|
|||
|
||||
with_them do
|
||||
it 'returns an empty array' do
|
||||
expect(results.aggregations(scope)).to match_array([])
|
||||
expect(results.aggregations(scope)).to be_empty
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ RSpec.describe ::Search::EmptySearchResults, feature_category: :global_search do
|
|||
|
||||
describe '#objects' do
|
||||
it 'returns an empty array' do
|
||||
expect(results.objects).to match_array([])
|
||||
expect(results.objects).to be_empty
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -37,7 +37,7 @@ RSpec.describe ::Search::EmptySearchResults, feature_category: :global_search do
|
|||
|
||||
describe '#aggregations' do
|
||||
it 'returns an empty array' do
|
||||
expect(results.aggregations).to match_array([])
|
||||
expect(results.aggregations).to be_empty
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,11 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe WorkItems::Types::UserPreference, type: :model, feature_category: :team_planning do
|
||||
describe 'associations' do
|
||||
it { is_expected.to belong_to(:user) }
|
||||
it { is_expected.to belong_to(:namespace) }
|
||||
it { is_expected.to belong_to(:work_item_type).class_name('WorkItems::Type').inverse_of(:user_preferences) }
|
||||
end
|
||||
end
|
||||
|
|
@ -70,6 +70,7 @@ RSpec.describe 'Query.project.pipelineAnalytics', :aggregate_failures, :click_ho
|
|||
let(:fields) do
|
||||
<<~QUERY
|
||||
aggregate { #{period_fields} }
|
||||
timeSeries(period: DAY) { #{period_fields} }
|
||||
|
||||
weekPipelinesTotals
|
||||
weekPipelinesLabels
|
||||
|
|
|
|||
|
|
@ -34,12 +34,12 @@ RSpec.describe ::Ci::CollectAggregatePipelineAnalyticsService, :click_house, :en
|
|||
using RSpec::Parameterized::TableSyntax
|
||||
|
||||
where(:status_groups, :duration_percentiles, :expected_aggregate) do
|
||||
%i[any] | [] | { count: { any: 7 } }
|
||||
%i[any] | [50, 75] | { count: { any: 7 }, duration_statistics: { p50: 30.minutes, p75: 82.5.minutes } }
|
||||
%i[any success] | [] | { count: { any: 7, success: 2 } }
|
||||
%i[success other] | [] | { count: { success: 2, other: 2 } }
|
||||
%i[any] | [] | { count: { any: 8 } }
|
||||
%i[any] | [50, 75] | { count: { any: 8 }, duration_statistics: { p50: 30.minutes, p75: 63.75.minutes } }
|
||||
%i[any success] | [] | { count: { any: 8, success: 2 } }
|
||||
%i[success other] | [] | { count: { success: 2, other: 3 } }
|
||||
%i[failed] | [50, 75] |
|
||||
{ count: { failed: 2 }, duration_statistics: { p50: 30.minutes, p75: 82.5.minutes } }
|
||||
{ count: { failed: 2 }, duration_statistics: { p50: 30.minutes, p75: 63.75.minutes } }
|
||||
end
|
||||
|
||||
with_them do
|
||||
|
|
@ -60,7 +60,7 @@ RSpec.describe ::Ci::CollectAggregatePipelineAnalyticsService, :click_house, :en
|
|||
expect(result).to be_success
|
||||
expect(result.errors).to eq([])
|
||||
expect(result.payload[:aggregate]).to eq(
|
||||
count: { any: 7 }, duration_statistics: { p50: 30.minutes, p99: 67.8.hours }
|
||||
count: { any: 8 }, duration_statistics: { p50: 30.minutes, p99: 4026.minutes }
|
||||
)
|
||||
end
|
||||
end
|
||||
|
|
@ -81,7 +81,7 @@ RSpec.describe ::Ci::CollectAggregatePipelineAnalyticsService, :click_house, :en
|
|||
it 'does not include job starting 1 second before start of week' do
|
||||
expect(result).to be_success
|
||||
expect(result.errors).to eq([])
|
||||
expect(result.payload[:aggregate]).to eq(count: { any: 7 })
|
||||
expect(result.payload[:aggregate]).to eq(count: { any: 8 })
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -92,7 +92,7 @@ RSpec.describe ::Ci::CollectAggregatePipelineAnalyticsService, :click_house, :en
|
|||
it 'includes job starting 1 second before start of week' do
|
||||
expect(result).to be_success
|
||||
expect(result.errors).to eq([])
|
||||
expect(result.payload[:aggregate]).to eq(count: { any: 8 })
|
||||
expect(result.payload[:aggregate]).to eq(count: { any: 9 })
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ RSpec.describe ::Ci::CollectTimeSeriesPipelineAnalyticsService, :click_house, :e
|
|||
let(:expected_1st_day) { (from_time || 1.week.ago.utc).beginning_of_day }
|
||||
let(:time_series_period) { :day }
|
||||
let(:time_series_filters) do
|
||||
{ duration_statistics: duration_percentile_symbols }
|
||||
{ count: status_groups, duration_statistics: duration_percentile_symbols }
|
||||
end
|
||||
|
||||
let(:service) do
|
||||
|
|
@ -53,6 +53,7 @@ RSpec.describe ::Ci::CollectTimeSeriesPipelineAnalyticsService, :click_house, :e
|
|||
|
||||
let(:no_pipeline_statistics) do
|
||||
{
|
||||
count: { success: 0, failed: 0, other: 0, any: 0 },
|
||||
duration_statistics: duration_percentile_symbols.index_with(0.seconds)
|
||||
}
|
||||
end
|
||||
|
|
@ -61,21 +62,25 @@ RSpec.describe ::Ci::CollectTimeSeriesPipelineAnalyticsService, :click_house, :e
|
|||
[
|
||||
{
|
||||
label: Time.utc(2023, 1, 1),
|
||||
count: { success: 0, failed: 1, other: 0, any: 1 },
|
||||
duration_statistics: { p50: 45.minutes, p95: 45.minutes, p99: 45.minutes }
|
||||
},
|
||||
{ label: Time.utc(2023, 1, 2), **no_pipeline_statistics },
|
||||
{
|
||||
label: Time.utc(2023, 1, 3),
|
||||
duration_statistics: { p50: 3600.5.seconds, p95: 6840.05.seconds, p99: 7128.01.seconds }
|
||||
count: { success: 0, failed: 1, other: 2, any: 3 },
|
||||
duration_statistics: { p50: 60.seconds, p95: 6486.seconds, p99: 7057.2.seconds }
|
||||
},
|
||||
{ label: Time.utc(2023, 1, 4), **no_pipeline_statistics },
|
||||
{ label: Time.utc(2023, 1, 5), **no_pipeline_statistics },
|
||||
{
|
||||
label: Time.utc(2023, 1, 6),
|
||||
count: { success: 1, failed: 0, other: 0, any: 1 },
|
||||
duration_statistics: { p50: 3.days, p95: 3.days, p99: 3.days }
|
||||
},
|
||||
{
|
||||
label: Time.utc(2023, 1, 7),
|
||||
count: { success: 1, failed: 0, other: 1, any: 3 },
|
||||
duration_statistics: { p50: 30.minutes, p95: 30.minutes, p99: 30.minutes }
|
||||
}
|
||||
]
|
||||
|
|
@ -87,6 +92,7 @@ RSpec.describe ::Ci::CollectTimeSeriesPipelineAnalyticsService, :click_house, :e
|
|||
%i[any] | []
|
||||
%i[any] | [50, 95]
|
||||
%i[success other] | []
|
||||
%i[failed other] | [50, 99]
|
||||
%i[failed] | [50, 95]
|
||||
end
|
||||
|
||||
|
|
@ -128,8 +134,13 @@ RSpec.describe ::Ci::CollectTimeSeriesPipelineAnalyticsService, :click_house, :e
|
|||
expect(result).to be_success
|
||||
expect(result.errors).to eq([])
|
||||
expect(result.payload[:time_series]).to eq([
|
||||
{ label: Time.utc(2022, 12, 26), duration_statistics: { p50: 45.minutes, p95: 45.minutes } },
|
||||
{ label: Time.utc(2023, 1, 2), duration_statistics: { p50: 30.minutes, p95: 54.5.hours } }
|
||||
{
|
||||
label: Time.utc(2022, 12, 26), count: { any: 1 },
|
||||
duration_statistics: { p50: 45.minutes, p95: 45.minutes }
|
||||
},
|
||||
{
|
||||
label: Time.utc(2023, 1, 2), count: { any: 7 }, duration_statistics: { p50: 30.minutes, p95: 51.hours }
|
||||
}
|
||||
])
|
||||
end
|
||||
end
|
||||
|
|
@ -143,7 +154,9 @@ RSpec.describe ::Ci::CollectTimeSeriesPipelineAnalyticsService, :click_house, :e
|
|||
expect(result).to be_success
|
||||
expect(result.errors).to eq([])
|
||||
expect(result.payload[:time_series]).to eq([
|
||||
{ label: Time.utc(2023, 1, 1), duration_statistics: { p50: 30.minutes, p95: 51.hours } }
|
||||
{
|
||||
label: Time.utc(2023, 1, 1), count: { any: 8 }, duration_statistics: { p50: 30.minutes, p95: 47.5.hours }
|
||||
}
|
||||
])
|
||||
end
|
||||
end
|
||||
|
|
@ -194,7 +207,10 @@ RSpec.describe ::Ci::CollectTimeSeriesPipelineAnalyticsService, :click_house, :e
|
|||
it 'includes job starting 1 second before start of week' do
|
||||
expect(result).to be_success
|
||||
expect(result.errors).to eq([])
|
||||
expect(result.payload[:time_series]).to eq([{ label: Time.utc(2022, 12, 31) }, *expected_time_series])
|
||||
expect(result.payload[:time_series]).to eq([
|
||||
{ label: Time.utc(2022, 12, 31), count: { any: 1 } },
|
||||
*expected_time_series
|
||||
])
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -223,7 +239,7 @@ RSpec.describe ::Ci::CollectTimeSeriesPipelineAnalyticsService, :click_house, :e
|
|||
expect(result).to be_success
|
||||
expect(result.errors).to eq([])
|
||||
expect(result.payload[:time_series]).to eq([
|
||||
{ label: Time.utc(2023, 1, 1) },
|
||||
{ label: Time.utc(2023, 1, 1), count: { any: 3, failed: 1, success: 2 } },
|
||||
*no_pipeline_statistics_for_day_range(2..7)
|
||||
])
|
||||
end
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ RSpec.shared_context 'with pipelines executed on different projects' do
|
|||
create_pipeline(project1, :success, 1.5.days.before(ending_time), 3.days), # 2023-01-06
|
||||
create_pipeline(project1, :failed, 5.days.before(ending_time), 2.hours), # 2023-01-03
|
||||
create_pipeline(project1, :skipped, 5.days.before(ending_time), 1.second), # 2023-01-03
|
||||
create_pipeline(project1, :canceled, 5.days.before(ending_time), 1.minute), # 2023-01-03
|
||||
create_pipeline(project1, :failed, 1.week.before(ending_time), 45.minutes), # 2023-01-01
|
||||
create_pipeline(project1, :skipped, 1.second.before(starting_time), 45.minutes) # 2022-12-31
|
||||
]
|
||||
|
|
|
|||
|
|
@ -37,10 +37,23 @@ RSpec.shared_examples_for 'a pipeline analytics service' do
|
|||
include_examples 'returns Not allowed error'
|
||||
end
|
||||
|
||||
context 'when ClickHouse query raises error' do
|
||||
before do
|
||||
allow(::ClickHouse::Client).to receive(:select).with(anything, :main)
|
||||
.and_raise(::ClickHouse::Client::DatabaseError, 'some error')
|
||||
end
|
||||
|
||||
it 'returns error response', :aggregate_failures do
|
||||
expect { result }.not_to raise_error
|
||||
expect(result.error?).to be true
|
||||
expect(result.errors).to contain_exactly('some error')
|
||||
end
|
||||
end
|
||||
|
||||
context 'when project is not specified' do
|
||||
let(:project) { nil }
|
||||
|
||||
it 'returns error' do
|
||||
it 'returns error response', :aggregate_failures do
|
||||
expect(result.error?).to be true
|
||||
expect(result.errors).to contain_exactly('Project must be specified')
|
||||
end
|
||||
|
|
@ -49,7 +62,7 @@ RSpec.shared_examples_for 'a pipeline analytics service' do
|
|||
context 'when invalid duration percentiles are specified' do
|
||||
let(:duration_percentiles) { [50, 70, 90] }
|
||||
|
||||
it 'returns error', :aggregate_failures do
|
||||
it 'returns error response', :aggregate_failures do
|
||||
expect(result.error?).to be true
|
||||
expect(result.message).to eq 'Invalid duration percentiles specified'
|
||||
end
|
||||
|
|
|
|||
Loading…
Reference in New Issue