Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
8432be20de
commit
d409d12963
|
@ -5,4 +5,3 @@ Gitlab/DelegatePredicateMethods:
|
|||
- app/models/concerns/integrations/base_data_fields.rb
|
||||
- app/models/project.rb
|
||||
- ee/app/models/concerns/ee/ci/metadatable.rb
|
||||
- lib/gitlab/ci/trace/stream.rb
|
||||
|
|
|
@ -1,4 +0,0 @@
|
|||
---
|
||||
GraphQL/ArgumentName:
|
||||
Exclude:
|
||||
- ee/app/graphql/mutations/audit_events/external_audit_event_destinations/update.rb
|
|
@ -31,11 +31,6 @@ Rails/SaveBang:
|
|||
- spec/lib/gitlab/database/custom_structure_spec.rb
|
||||
- spec/lib/gitlab/database/partitioning_migration_helpers/table_management_helpers_spec.rb
|
||||
- spec/lib/gitlab/database_importers/self_monitoring/project/create_service_spec.rb
|
||||
- spec/lib/gitlab/gfm/reference_rewriter_spec.rb
|
||||
- spec/lib/gitlab/git_access_spec.rb
|
||||
- spec/lib/gitlab/import_export/avatar_saver_spec.rb
|
||||
- spec/lib/gitlab/import_export/base/relation_factory_spec.rb
|
||||
- spec/lib/gitlab/import_export/design_repo_restorer_spec.rb
|
||||
- spec/lib/gitlab/import_export/fast_hash_serializer_spec.rb
|
||||
- spec/lib/gitlab/import_export/fork_spec.rb
|
||||
- spec/lib/gitlab/import_export/group/legacy_tree_saver_spec.rb
|
||||
|
|
|
@ -1673,4 +1673,4 @@ DEPENDENCIES
|
|||
yajl-ruby (~> 1.4.1)
|
||||
|
||||
BUNDLED WITH
|
||||
2.1.4
|
||||
2.3.5
|
||||
|
|
|
@ -81,7 +81,7 @@ export default {
|
|||
</blob-filepath>
|
||||
</div>
|
||||
|
||||
<div class="gl-display-none gl-sm-display-flex">
|
||||
<div class="gl-display-none gl-sm-display-flex file-actions">
|
||||
<viewer-switcher v-if="showViewerSwitcher" v-model="viewer" />
|
||||
|
||||
<slot name="actions"></slot>
|
||||
|
|
|
@ -1,3 +0,0 @@
|
|||
import { initWorkItemsHierarchy } from '~/work_items_hierarchy/work_items_hierarchy_bundle';
|
||||
|
||||
initWorkItemsHierarchy();
|
|
@ -3,6 +3,7 @@ import { GlButton } from '@gitlab/ui';
|
|||
import { __ } from '~/locale';
|
||||
import createFlash from '~/flash';
|
||||
import getRefMixin from '~/repository/mixins/get_ref';
|
||||
import initSourcegraph from '~/sourcegraph';
|
||||
import { updateElementsVisibility } from '../utils/dom';
|
||||
import blobControlsQuery from '../queries/blob_controls.query.graphql';
|
||||
|
||||
|
@ -76,6 +77,9 @@ export default {
|
|||
showBlobControls(shouldShow) {
|
||||
updateElementsVisibility('.tree-controls', !shouldShow);
|
||||
},
|
||||
blobInfo() {
|
||||
initSourcegraph();
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
@ -97,6 +101,7 @@ export default {
|
|||
data-testid="permalink"
|
||||
:href="blobInfo.permalinkPath"
|
||||
:class="$options.buttonClassList"
|
||||
class="js-data-file-blob-permalink-url"
|
||||
>
|
||||
{{ $options.i18n.permalink }}
|
||||
</gl-button>
|
||||
|
|
|
@ -2,7 +2,6 @@
|
|||
import { GlSafeHtmlDirective } from '@gitlab/ui';
|
||||
import LineNumbers from '~/vue_shared/components/line_numbers.vue';
|
||||
import { sanitize } from '~/lib/dompurify';
|
||||
import '~/sourcegraph/load';
|
||||
|
||||
const LINE_SELECT_CLASS_NAME = 'hll';
|
||||
|
||||
|
|
|
@ -1,54 +1,5 @@
|
|||
import { __ } from '~/locale';
|
||||
|
||||
export const widgetTypes = {
|
||||
title: 'TITLE',
|
||||
};
|
||||
|
||||
export const WI_TITLE_TRACK_LABEL = 'item_title';
|
||||
|
||||
export const workItemTypes = {
|
||||
EPIC: {
|
||||
title: __('Epic'),
|
||||
icon: 'epic',
|
||||
color: '#694CC0',
|
||||
backgroundColor: '#E1D8F9',
|
||||
},
|
||||
ISSUE: {
|
||||
title: __('Issue'),
|
||||
icon: 'issues',
|
||||
color: '#1068BF',
|
||||
backgroundColor: '#CBE2F9',
|
||||
},
|
||||
TASK: {
|
||||
title: __('Task'),
|
||||
icon: 'task-done',
|
||||
color: '#217645',
|
||||
backgroundColor: '#C3E6CD',
|
||||
},
|
||||
INCIDENT: {
|
||||
title: __('Incident'),
|
||||
icon: 'issue-type-incident',
|
||||
backgroundColor: '#db2a0f',
|
||||
color: '#FDD4CD',
|
||||
iconSize: 16,
|
||||
},
|
||||
SUB_EPIC: {
|
||||
title: __('Child epic'),
|
||||
icon: 'epic',
|
||||
color: '#AB6100',
|
||||
backgroundColor: '#F5D9A8',
|
||||
},
|
||||
REQUIREMENT: {
|
||||
title: __('Requirement'),
|
||||
icon: 'requirements',
|
||||
color: '#0068c5',
|
||||
backgroundColor: '#c5e3fb',
|
||||
},
|
||||
TEST_CASE: {
|
||||
title: __('Test case'),
|
||||
icon: 'issue-type-test-case',
|
||||
backgroundColor: '#007a3f',
|
||||
color: '#bae8cb',
|
||||
iconSize: 16,
|
||||
},
|
||||
};
|
||||
|
|
|
@ -1,96 +0,0 @@
|
|||
<script>
|
||||
import { GlBanner } from '@gitlab/ui';
|
||||
import Cookies from 'js-cookie';
|
||||
import { parseBoolean } from '~/lib/utils/common_utils';
|
||||
import { workItemTypes } from '~/work_items/constants';
|
||||
import RESPONSE from '../static_response';
|
||||
import { WORK_ITEMS_SURVEY_COOKIE_NAME } from '../constants';
|
||||
import Hierarchy from './hierarchy.vue';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
GlBanner,
|
||||
Hierarchy,
|
||||
},
|
||||
inject: ['illustrationPath', 'licensePlan'],
|
||||
data() {
|
||||
return {
|
||||
bannerVisible: !parseBoolean(Cookies.get(WORK_ITEMS_SURVEY_COOKIE_NAME)),
|
||||
workItemHierarchy: RESPONSE[this.licensePlan],
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
hasUnavailableStructure() {
|
||||
return this.workItemTypes.unavailable.length > 0;
|
||||
},
|
||||
workItemTypes() {
|
||||
return this.workItemHierarchy.reduce(
|
||||
(itemTypes, item) => {
|
||||
const key = item.available ? 'available' : 'unavailable';
|
||||
itemTypes[key].push({
|
||||
...item,
|
||||
...workItemTypes[item.type],
|
||||
nestedTypes: item.nestedTypes
|
||||
? item.nestedTypes.map((type) => workItemTypes[type])
|
||||
: null,
|
||||
});
|
||||
return itemTypes;
|
||||
},
|
||||
{ available: [], unavailable: [] },
|
||||
);
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
handleClose() {
|
||||
Cookies.set(WORK_ITEMS_SURVEY_COOKIE_NAME, 'true', { expires: 365 * 10 });
|
||||
this.bannerVisible = false;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<gl-banner
|
||||
v-if="bannerVisible"
|
||||
class="gl-mt-4 gl-px-5!"
|
||||
:title="s__('Hierarchy|Help us improve work items in GitLab!')"
|
||||
:button-text="s__('Hierarchy|Take the work items survey')"
|
||||
button-link="https://forms.gle/u1BmRp8rTbwj52iq5"
|
||||
:svg-path="illustrationPath"
|
||||
@close="handleClose"
|
||||
>
|
||||
<p>
|
||||
{{
|
||||
s__(
|
||||
'Hierarchy|Is there a framework or type of work item you wish you had access to in GitLab? Give us your feedback and help us build the experiences valuable to you.',
|
||||
)
|
||||
}}
|
||||
</p>
|
||||
</gl-banner>
|
||||
<h3 class="gl-mt-5!">{{ s__('Hierarchy|Planning hierarchy') }}</h3>
|
||||
<p>
|
||||
{{
|
||||
s__(
|
||||
'Hierarchy|Deliver value more efficiently by breaking down necessary work into a hierarchical structure. This structure helps teams understand scope, priorities, and how work cascades up toward larger goals.',
|
||||
)
|
||||
}}
|
||||
</p>
|
||||
|
||||
<div class="gl-font-weight-bold gl-mb-2">{{ s__('Hierarchy|Current structure') }}</div>
|
||||
<p class="gl-mb-3!">{{ s__('Hierarchy|You can start using these items now.') }}</p>
|
||||
<hierarchy :work-item-types="workItemTypes.available" />
|
||||
|
||||
<div
|
||||
v-if="hasUnavailableStructure"
|
||||
data-testid="unavailable-structure"
|
||||
class="gl-font-weight-bold gl-mt-5 gl-mb-2"
|
||||
>
|
||||
{{ s__('Hierarchy|Unavailable structure') }}
|
||||
</div>
|
||||
<p v-if="hasUnavailableStructure" class="gl-mb-3!">
|
||||
{{ s__('Hierarchy|These items are unavailable in the current structure.') }}
|
||||
</p>
|
||||
<hierarchy :work-item-types="workItemTypes.unavailable" />
|
||||
</div>
|
||||
</template>
|
|
@ -1,119 +0,0 @@
|
|||
<script>
|
||||
import { GlIcon, GlBadge } from '@gitlab/ui';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
GlIcon,
|
||||
GlBadge,
|
||||
},
|
||||
props: {
|
||||
workItemTypes: {
|
||||
type: Array,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
isLastItem(index, workItem) {
|
||||
const hasMoreThanOneItem = workItem.nestedTypes.length > 1;
|
||||
const isLastItemInArray = index === workItem.nestedTypes.length - 1;
|
||||
|
||||
return isLastItemInArray && hasMoreThanOneItem;
|
||||
},
|
||||
nestedWorkItemTypeMargin(index, workItem) {
|
||||
const isLastItemInArray = index === workItem.nestedTypes.length - 1;
|
||||
const hasMoreThanOneItem = workItem.nestedTypes.length > 1;
|
||||
|
||||
if (isLastItemInArray && hasMoreThanOneItem) {
|
||||
return 'gl-ml-0';
|
||||
}
|
||||
|
||||
return 'gl-ml-6';
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<template>
|
||||
<div>
|
||||
<div
|
||||
v-for="workItem in workItemTypes"
|
||||
:key="workItem.id"
|
||||
class="gl-mb-3"
|
||||
:class="{ flex: !workItem.available }"
|
||||
>
|
||||
<span
|
||||
class="gl-border-gray-100 gl-border-1 gl-border-solid gl-rounded-base gl-pl-2 gl-pt-2 gl-pb-2 gl-pr-3 gl-display-inline-flex gl-align-items-center gl-justify-content-center gl-line-height-normal"
|
||||
data-testid="work-item-wrapper"
|
||||
>
|
||||
<span
|
||||
:style="{
|
||||
backgroundColor: workItem.backgroundColor,
|
||||
color: workItem.color,
|
||||
}"
|
||||
class="gl-rounded-base gl-mr-2 gl-display-inline-flex justify-content-center align-items-center hierarchy-icon-wrapper"
|
||||
>
|
||||
<gl-icon :size="workItem.iconSize || 12" :name="workItem.icon" />
|
||||
</span>
|
||||
|
||||
{{ workItem.title }}
|
||||
</span>
|
||||
|
||||
<gl-badge
|
||||
v-if="!workItem.available"
|
||||
variant="info"
|
||||
icon="license"
|
||||
size="sm"
|
||||
class="gl-ml-3 gl-align-self-center"
|
||||
>{{ workItem.license }}</gl-badge
|
||||
>
|
||||
|
||||
<div v-if="workItem.nestedTypes" :class="{ 'gl-relative': workItem.nestedTypes.length > 1 }">
|
||||
<svg
|
||||
v-if="workItem.nestedTypes.length > 1"
|
||||
class="hierarchy-rounded-arrow-tail gl-text-gray-400"
|
||||
data-testid="hierarchy-rounded-arrow-tail"
|
||||
width="2"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<line
|
||||
x1="0.75"
|
||||
y1="1"
|
||||
x2="0.75"
|
||||
y2="100%"
|
||||
stroke="currentColor"
|
||||
stroke-width="1.5"
|
||||
stroke-linecap="round"
|
||||
/>
|
||||
</svg>
|
||||
<template v-for="(nestedWorkItem, index) in workItem.nestedTypes">
|
||||
<div :key="nestedWorkItem.id" class="gl-display-block gl-mt-2 gl-ml-6">
|
||||
<gl-icon name="arrow-down" class="gl-text-gray-400" />
|
||||
</div>
|
||||
<gl-icon
|
||||
v-if="isLastItem(index, workItem)"
|
||||
:key="nestedWorkItem.id"
|
||||
name="level-up"
|
||||
class="gl-text-gray-400 gl-ml-2 hierarchy-rounded-arrow"
|
||||
/>
|
||||
<span
|
||||
:key="nestedWorkItem.id"
|
||||
class="gl-border-gray-100 gl-border-1 gl-border-solid gl-rounded-base gl-pl-2 gl-pt-2 gl-pb-2 gl-pr-3 gl-display-inline-flex gl-align-items-center gl-justify-content-center gl-mt-2 gl-line-height-normal"
|
||||
:class="nestedWorkItemTypeMargin(index, workItem)"
|
||||
>
|
||||
<span
|
||||
:style="{
|
||||
backgroundColor: nestedWorkItem.backgroundColor,
|
||||
color: nestedWorkItem.color,
|
||||
}"
|
||||
class="gl-rounded-base gl-mr-2 gl-display-inline-flex justify-content-center align-items-center hierarchy-icon-wrapper"
|
||||
>
|
||||
<gl-icon :size="nestedWorkItem.iconSize || 12" :name="nestedWorkItem.icon" />
|
||||
</span>
|
||||
|
||||
{{ nestedWorkItem.title }}
|
||||
</span>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
|
@ -1,12 +0,0 @@
|
|||
export const WORK_ITEMS_SURVEY_COOKIE_NAME = 'hide_work_items_hierarchy_survey';
|
||||
|
||||
/**
|
||||
* Hard-coded strings since we're rendering hierarchy
|
||||
* items from mock responses. Remove this when we
|
||||
* have a real hierarchy endpoint.
|
||||
*/
|
||||
export const LICENSE_PLAN = {
|
||||
FREE: 'free',
|
||||
PREMIUM: 'premium',
|
||||
ULTIMATE: 'ultimate',
|
||||
};
|
|
@ -1,10 +0,0 @@
|
|||
import { LICENSE_PLAN } from './constants';
|
||||
|
||||
export function inferLicensePlan({ hasSubEpics, hasEpics }) {
|
||||
if (hasSubEpics) {
|
||||
return LICENSE_PLAN.ULTIMATE;
|
||||
} else if (hasEpics) {
|
||||
return LICENSE_PLAN.PREMIUM;
|
||||
}
|
||||
return LICENSE_PLAN.FREE;
|
||||
}
|
|
@ -1,142 +0,0 @@
|
|||
const FREE_TIER = 'free';
|
||||
const ULTIMATE_TIER = 'ultimate';
|
||||
const PREMIUM_TIER = 'premium';
|
||||
|
||||
const RESPONSE = {
|
||||
[FREE_TIER]: [
|
||||
{
|
||||
id: '1',
|
||||
type: 'ISSUE',
|
||||
available: true,
|
||||
license: null,
|
||||
nestedTypes: null,
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
type: 'TASK',
|
||||
available: true,
|
||||
license: null,
|
||||
nestedTypes: null,
|
||||
},
|
||||
{
|
||||
id: '3',
|
||||
type: 'INCIDENT',
|
||||
available: true,
|
||||
license: null,
|
||||
nestedTypes: null,
|
||||
},
|
||||
{
|
||||
id: '4',
|
||||
type: 'EPIC',
|
||||
available: false,
|
||||
license: 'Premium', // eslint-disable-line @gitlab/require-i18n-strings
|
||||
nestedTypes: null,
|
||||
},
|
||||
{
|
||||
id: '5',
|
||||
type: 'SUB_EPIC',
|
||||
available: false,
|
||||
license: 'Ultimate', // eslint-disable-line @gitlab/require-i18n-strings
|
||||
nestedTypes: null,
|
||||
},
|
||||
{
|
||||
id: '6',
|
||||
type: 'REQUIREMENT',
|
||||
available: false,
|
||||
license: 'Ultimate', // eslint-disable-line @gitlab/require-i18n-strings
|
||||
nestedTypes: null,
|
||||
},
|
||||
{
|
||||
id: '7',
|
||||
type: 'TEST_CASE',
|
||||
available: false,
|
||||
license: 'Ultimate', // eslint-disable-line @gitlab/require-i18n-strings
|
||||
nestedTypes: null,
|
||||
},
|
||||
],
|
||||
|
||||
[PREMIUM_TIER]: [
|
||||
{
|
||||
id: '1',
|
||||
type: 'EPIC',
|
||||
available: true,
|
||||
license: null,
|
||||
nestedTypes: ['ISSUE'],
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
type: 'TASK',
|
||||
available: true,
|
||||
license: null,
|
||||
nestedTypes: null,
|
||||
},
|
||||
{
|
||||
id: '3',
|
||||
type: 'INCIDENT',
|
||||
available: true,
|
||||
license: null,
|
||||
nestedTypes: null,
|
||||
},
|
||||
{
|
||||
id: '5',
|
||||
type: 'SUB_EPIC',
|
||||
available: false,
|
||||
license: 'Ultimate', // eslint-disable-line @gitlab/require-i18n-strings
|
||||
nestedTypes: null,
|
||||
},
|
||||
{
|
||||
id: '6',
|
||||
type: 'REQUIREMENT',
|
||||
available: false,
|
||||
license: 'Ultimate', // eslint-disable-line @gitlab/require-i18n-strings
|
||||
nestedTypes: null,
|
||||
},
|
||||
{
|
||||
id: '7',
|
||||
type: 'TEST_CASE',
|
||||
available: false,
|
||||
license: 'Ultimate', // eslint-disable-line @gitlab/require-i18n-strings
|
||||
nestedTypes: null,
|
||||
},
|
||||
],
|
||||
|
||||
[ULTIMATE_TIER]: [
|
||||
{
|
||||
id: '1',
|
||||
type: 'EPIC',
|
||||
available: true,
|
||||
license: null,
|
||||
nestedTypes: ['SUB_EPIC', 'ISSUE'],
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
type: 'TASK',
|
||||
available: true,
|
||||
license: null,
|
||||
nestedTypes: null,
|
||||
},
|
||||
{
|
||||
id: '3',
|
||||
type: 'INCIDENT',
|
||||
available: true,
|
||||
license: null,
|
||||
nestedTypes: null,
|
||||
},
|
||||
{
|
||||
id: '6',
|
||||
type: 'REQUIREMENT',
|
||||
available: true,
|
||||
license: null,
|
||||
nestedTypes: null,
|
||||
},
|
||||
{
|
||||
id: '7',
|
||||
type: 'TEST_CASE',
|
||||
available: true,
|
||||
license: null,
|
||||
nestedTypes: null,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
export default RESPONSE;
|
|
@ -1,26 +0,0 @@
|
|||
import Vue from 'vue';
|
||||
import { parseBoolean } from '~/lib/utils/common_utils';
|
||||
import App from './components/app.vue';
|
||||
import { inferLicensePlan } from './hierarchy_util';
|
||||
|
||||
export const initWorkItemsHierarchy = () => {
|
||||
const el = document.querySelector('#js-work-items-hierarchy');
|
||||
|
||||
const { illustrationPath, hasEpics, hasSubEpics } = el.dataset;
|
||||
|
||||
const licensePlan = inferLicensePlan({
|
||||
hasEpics: parseBoolean(hasEpics),
|
||||
hasSubEpics: parseBoolean(hasSubEpics),
|
||||
});
|
||||
|
||||
return new Vue({
|
||||
el,
|
||||
provide: {
|
||||
illustrationPath,
|
||||
licensePlan,
|
||||
},
|
||||
render(createElement) {
|
||||
return createElement(App);
|
||||
},
|
||||
});
|
||||
};
|
|
@ -32,4 +32,3 @@
|
|||
@import './pages/storage_quota';
|
||||
@import './pages/tree';
|
||||
@import './pages/users';
|
||||
@import './pages/hierarchy';
|
||||
|
|
|
@ -255,18 +255,23 @@ span.highlight_word {
|
|||
|
||||
.hll { background-color: $white-hll-bg; }
|
||||
|
||||
.c { color: $white-c;
|
||||
.c,
|
||||
.hljs-comment { color: $white-c;
|
||||
font-style: italic; }
|
||||
|
||||
.err { color: $white-err;
|
||||
background-color: $white-err-bg; }
|
||||
.k { font-weight: $gl-font-weight-bold; }
|
||||
|
||||
.k,
|
||||
.hljs-variable.language_,
|
||||
.hljs-built_in { font-weight: $gl-font-weight-bold; }
|
||||
.o { font-weight: $gl-font-weight-bold; }
|
||||
|
||||
.cm { color: $white-cm;
|
||||
font-style: italic; }
|
||||
|
||||
.cp { color: $white-cp;
|
||||
.cp,
|
||||
.hljs-meta { color: $white-cp;
|
||||
font-weight: $gl-font-weight-bold; }
|
||||
|
||||
.c1 { color: $white-c1;
|
||||
|
@ -310,20 +315,34 @@ span.highlight_word {
|
|||
font-weight: $gl-font-weight-bold; }
|
||||
.gt { color: $white-gt; }
|
||||
.kc { font-weight: $gl-font-weight-bold; }
|
||||
.kd { font-weight: $gl-font-weight-bold; }
|
||||
|
||||
.kd,
|
||||
.hljs-keyword { font-weight: $gl-font-weight-bold; }
|
||||
.kn { font-weight: $gl-font-weight-bold; }
|
||||
.kp { font-weight: $gl-font-weight-bold; }
|
||||
.kr { font-weight: $gl-font-weight-bold; }
|
||||
|
||||
.kt { color: $white-kt;
|
||||
.kt,
|
||||
.hljs-type { color: $white-kt;
|
||||
font-weight: $gl-font-weight-bold; }
|
||||
.m { color: $white-m; }
|
||||
.s { color: $white-s; }
|
||||
.n { color: $white-n; }
|
||||
.na { color: $white-na; }
|
||||
.nb { color: $white-nb; }
|
||||
|
||||
.nc { color: $white-nc;
|
||||
.n,
|
||||
.hljs-built_in { color: $white-n; }
|
||||
|
||||
.na,
|
||||
.hljs-attr,
|
||||
.hljs-property,
|
||||
.hljs-title.function_ { color: $white-na; }
|
||||
|
||||
.nb,
|
||||
.hljs-title.class_,
|
||||
.hljs-literal { color: $white-nb; }
|
||||
|
||||
.nc,
|
||||
.hljs-title.class_,
|
||||
.hljs-built_in { color: $white-nc;
|
||||
font-weight: $gl-font-weight-bold; }
|
||||
.no { color: $white-no; }
|
||||
.ni { color: $white-ni; }
|
||||
|
@ -331,7 +350,9 @@ span.highlight_word {
|
|||
.ne { color: $white-ne;
|
||||
font-weight: $gl-font-weight-bold; }
|
||||
|
||||
.nf { color: $white-nf;
|
||||
.nf,
|
||||
.hljs-title,
|
||||
.hljs-title.function_ { color: $white-nf;
|
||||
font-weight: $gl-font-weight-bold; }
|
||||
.nn { color: $white-nn; }
|
||||
.nt { color: $white-nt; }
|
||||
|
@ -340,7 +361,9 @@ span.highlight_word {
|
|||
.w { color: $white-w; }
|
||||
.mf { color: $white-mf; }
|
||||
.mh { color: $white-mh; }
|
||||
.mi { color: $white-mi; }
|
||||
|
||||
.mi,
|
||||
.hljs-number { color: $white-mi; }
|
||||
.mo { color: $white-mo; }
|
||||
.sb { color: $white-sb; }
|
||||
.sc { color: $white-sc; }
|
||||
|
@ -351,7 +374,9 @@ span.highlight_word {
|
|||
.si { color: $white-si; }
|
||||
.sx { color: $white-sx; }
|
||||
.sr { color: $white-sr; }
|
||||
.s1 { color: $white-s1; }
|
||||
|
||||
.s1,
|
||||
.hljs-string { color: $white-s1; }
|
||||
.ss { color: $white-ss; }
|
||||
.bp { color: $white-bp; }
|
||||
.vc { color: $white-vc; }
|
||||
|
|
|
@ -1,15 +0,0 @@
|
|||
.hierarchy-rounded-arrow-tail {
|
||||
position: absolute;
|
||||
top: 4px;
|
||||
left: 5px;
|
||||
height: calc(100% - 20px);
|
||||
}
|
||||
|
||||
.hierarchy-icon-wrapper {
|
||||
height: $default-icon-size;
|
||||
width: $default-icon-size;
|
||||
}
|
||||
|
||||
.hierarchy-rounded-arrow {
|
||||
transform: scale(1, -1) rotate(90deg);
|
||||
}
|
|
@ -1,15 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module WorkItemsHierarchy
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
# rubocop:disable Gitlab/ModuleWithInstanceVariables
|
||||
def planning_hierarchy
|
||||
return render_404 unless Feature.enabled?(:work_items_hierarchy, @project, default_enabled: :yaml)
|
||||
|
||||
render 'shared/planning_hierarchy'
|
||||
end
|
||||
# rubocop:enable Gitlab/ModuleWithInstanceVariables
|
||||
end
|
||||
|
||||
WorkItemsHierarchy.prepend_mod_with('WorkItemsHierarchy')
|
|
@ -6,6 +6,7 @@ class Projects::TreeController < Projects::ApplicationController
|
|||
include CreatesCommit
|
||||
include ActionView::Helpers::SanitizeHelper
|
||||
include RedirectsForMissingPathOnTree
|
||||
include SourcegraphDecorator
|
||||
|
||||
around_action :allow_gitaly_ref_name_caching, only: [:show]
|
||||
|
||||
|
|
|
@ -9,7 +9,7 @@ class ProjectsController < Projects::ApplicationController
|
|||
include RecordUserLastActivity
|
||||
include ImportUrlParams
|
||||
include FiltersEvents
|
||||
include WorkItemsHierarchy
|
||||
include SourcegraphDecorator
|
||||
|
||||
prepend_before_action(only: [:show]) { authenticate_sessionless_user!(:rss) }
|
||||
|
||||
|
@ -53,7 +53,6 @@ class ProjectsController < Projects::ApplicationController
|
|||
feature_category :team_planning, [:preview_markdown, :new_issuable_address]
|
||||
feature_category :importers, [:export, :remove_export, :generate_new_export, :download_export]
|
||||
feature_category :code_review, [:unfoldered_environment_names]
|
||||
feature_category :portfolio_management, [:planning_hierarchy]
|
||||
|
||||
urgency :low, [:refs]
|
||||
urgency :high, [:unfoldered_environment_names]
|
||||
|
|
|
@ -69,6 +69,7 @@ module Ci
|
|||
has_many :builds, foreign_key: :commit_id, inverse_of: :pipeline
|
||||
has_many :generic_commit_statuses, foreign_key: :commit_id, inverse_of: :pipeline, class_name: 'GenericCommitStatus'
|
||||
has_many :job_artifacts, through: :builds
|
||||
has_many :build_trace_chunks, class_name: 'Ci::BuildTraceChunk', through: :builds, source: :trace_chunks
|
||||
has_many :trigger_requests, dependent: :destroy, foreign_key: :commit_id # rubocop:disable Cop/ActiveRecordDependent
|
||||
has_many :variables, class_name: 'Ci::PipelineVariable'
|
||||
has_many :deployments, through: :builds
|
||||
|
@ -130,6 +131,7 @@ module Ci
|
|||
after_create :keep_around_commits, unless: :importing?
|
||||
|
||||
use_fast_destroy :job_artifacts
|
||||
use_fast_destroy :build_trace_chunks
|
||||
|
||||
# We use `Enums::Ci::Pipeline.sources` here so that EE can more easily extend
|
||||
# this `Hash` with new values.
|
||||
|
|
|
@ -240,7 +240,6 @@ class ProjectPolicy < BasePolicy
|
|||
enable :read_wiki
|
||||
enable :read_issue
|
||||
enable :read_label
|
||||
enable :read_work_items_hierarchy
|
||||
enable :read_milestone
|
||||
enable :read_snippet
|
||||
enable :read_project_member
|
||||
|
@ -573,7 +572,6 @@ class ProjectPolicy < BasePolicy
|
|||
enable :read_issue_board_list
|
||||
enable :read_wiki
|
||||
enable :read_label
|
||||
enable :read_work_items_hierarchy
|
||||
enable :read_milestone
|
||||
enable :read_snippet
|
||||
enable :read_project_member
|
||||
|
|
|
@ -3,39 +3,50 @@
|
|||
module Ci
|
||||
class AfterRequeueJobService < ::BaseService
|
||||
def execute(processable)
|
||||
process_subsequent_jobs(processable)
|
||||
reset_source_bridge(processable)
|
||||
@processable = processable
|
||||
|
||||
process_subsequent_jobs
|
||||
reset_source_bridge
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def process_subsequent_jobs(processable)
|
||||
(stage_dependent_jobs(processable) | needs_dependent_jobs(processable))
|
||||
.each do |processable|
|
||||
process(processable)
|
||||
def process_subsequent_jobs
|
||||
dependent_jobs.each do |job|
|
||||
process(job)
|
||||
end
|
||||
end
|
||||
|
||||
def reset_source_bridge(processable)
|
||||
processable.pipeline.reset_source_bridge!(current_user)
|
||||
def reset_source_bridge
|
||||
@processable.pipeline.reset_source_bridge!(current_user)
|
||||
end
|
||||
|
||||
def process(processable)
|
||||
Gitlab::OptimisticLocking.retry_lock(processable, name: 'ci_requeue_job') do |processable|
|
||||
processable.process(current_user)
|
||||
def dependent_jobs
|
||||
if ::Feature.enabled?(:ci_order_subsequent_jobs_by_stage, @processable.pipeline.project, default_enabled: :yaml)
|
||||
stage_dependent_jobs
|
||||
.or(needs_dependent_jobs.except(:preload))
|
||||
.ordered_by_stage
|
||||
else
|
||||
stage_dependent_jobs | needs_dependent_jobs
|
||||
end
|
||||
end
|
||||
|
||||
def skipped_jobs(processable)
|
||||
processable.pipeline.processables.skipped
|
||||
def process(job)
|
||||
Gitlab::OptimisticLocking.retry_lock(job, name: 'ci_requeue_job') do |job|
|
||||
job.process(current_user)
|
||||
end
|
||||
end
|
||||
|
||||
def stage_dependent_jobs(processable)
|
||||
skipped_jobs(processable).after_stage(processable.stage_idx)
|
||||
def stage_dependent_jobs
|
||||
skipped_jobs.after_stage(@processable.stage_idx)
|
||||
end
|
||||
|
||||
def needs_dependent_jobs(processable)
|
||||
skipped_jobs(processable).scheduling_type_dag.with_needs([processable.name])
|
||||
def needs_dependent_jobs
|
||||
skipped_jobs.scheduling_type_dag.with_needs([@processable.name])
|
||||
end
|
||||
|
||||
def skipped_jobs
|
||||
@skipped_jobs ||= @processable.pipeline.processables.skipped
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -9,9 +9,11 @@ module Ci
|
|||
|
||||
pipeline.cancel_running if pipeline.cancelable?
|
||||
|
||||
# Ci::Pipeline#destroy triggers `use_fast_destroy :job_artifacts` and
|
||||
# ci_builds has ON DELETE CASCADE to ci_pipelines. The pipeline, the builds,
|
||||
# job and pipeline artifacts all get destroyed here.
|
||||
# The pipeline, the builds, job and pipeline artifacts all get destroyed here.
|
||||
# Ci::Pipeline#destroy triggers fast destroy on job_artifacts and
|
||||
# build_trace_chunks to remove the records and data stored in object storage.
|
||||
# ci_builds records are deleted using ON DELETE CASCADE from ci_pipelines
|
||||
#
|
||||
pipeline.reset.destroy!
|
||||
|
||||
ServiceResponse.success(message: 'Pipeline not found')
|
||||
|
|
|
@ -1,5 +0,0 @@
|
|||
- page_title _("Planning hierarchy")
|
||||
- has_sub_epics = Gitlab.ee? && @project&.feature_available?(:subepics)
|
||||
- has_epics = Gitlab.ee? && @project&.feature_available?(:epics)
|
||||
|
||||
#js-work-items-hierarchy{ data: { has_sub_epics: has_sub_epics.to_s, has_epics: has_epics.to_s, illustration_path: image_path('illustrations/rocket-launch-md.svg') } }
|
|
@ -1,8 +1,8 @@
|
|||
---
|
||||
name: work_items_hierarchy
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/76720
|
||||
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/350451
|
||||
name: ci_order_subsequent_jobs_by_stage
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/77528
|
||||
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/349977
|
||||
milestone: '14.7'
|
||||
type: development
|
||||
group: group::product planning
|
||||
group: group::pipeline authoring
|
||||
default_enabled: false
|
|
@ -641,7 +641,6 @@ constraints(::Constraints::ProjectUrlConstrainer.new) do
|
|||
post :generate_new_export
|
||||
get :download_export
|
||||
get :activity
|
||||
get :planning_hierarchy
|
||||
get :refs
|
||||
put :new_issuable_address
|
||||
get :unfoldered_environment_names
|
||||
|
|
|
@ -19,6 +19,7 @@ The [`StandardContext`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/lib/g
|
|||
| `project_id` | **{dotted-circle}** | integer | |
|
||||
| `namespace_id` | **{dotted-circle}** | integer | |
|
||||
| `user_id` | **{dotted-circle}** | integer | User database record ID attribute. This file undergoes a pseudonymization process at the collector level. |
|
||||
| `context_generated_at` | **{dotted-circle}** | string (date time format) | Timestamp indicating when context was generated. |
|
||||
| `environment` | **{check-circle}** | string (max 32 chars) | Name of the source environment, such as `production` or `staging` |
|
||||
| `source` | **{check-circle}** | string (max 32 chars) | Name of the source application, such as `gitlab-rails` or `gitlab-javascript` |
|
||||
| `plan` | **{dotted-circle}** | string (max 32 chars) | Name of the plan for the namespace, such as `free`, `premium`, or `ultimate`. Automatically picked from the `namespace`. |
|
||||
|
|
|
@ -55,14 +55,21 @@ you can increase the values to complete backfill in a shorter time. If it's
|
|||
under heavy load and backfill reduces its availability for normal requests,
|
||||
you can decrease them.
|
||||
|
||||
## Using a different URL for synchronization
|
||||
## Set up the internal URLs
|
||||
|
||||
> Setting up internal URLs in secondary sites was [introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/77179) in GitLab 14.7.
|
||||
|
||||
You can set up a different URL for synchronization between the primary and secondary site.
|
||||
|
||||
The **primary** site's Internal URL is used by **secondary** sites to contact it
|
||||
(to sync repositories, for example). The name Internal URL distinguishes it from
|
||||
[External URL](https://docs.gitlab.com/omnibus/settings/configuration.html#configuring-the-external-url-for-gitlab),
|
||||
which is used by users. Internal URL does not need to be a private address.
|
||||
|
||||
Internal URL defaults to external URL, but you can also customize it:
|
||||
When [Geo secondary proxying](../../administration/geo/secondary_proxy/index.md) is enabled,
|
||||
the primary uses the secondary's internal URL to contact it directly.
|
||||
|
||||
The internal URL defaults to external URL. To change it:
|
||||
|
||||
1. On the top bar, select **Menu > Admin**.
|
||||
1. On the left sidebar, select **Geo > Nodes**.
|
||||
|
@ -70,6 +77,9 @@ Internal URL defaults to external URL, but you can also customize it:
|
|||
1. Edit the internal URL.
|
||||
1. Select **Save changes**.
|
||||
|
||||
When enabled, the Admin Area for Geo shows replication details for each site directly
|
||||
from the primary site's UI, and through the Geo secondary proxy, if enabled.
|
||||
|
||||
WARNING:
|
||||
We recommend using an HTTPS connection while configuring the Geo sites. To avoid
|
||||
breaking communication between **primary** and **secondary** sites when using
|
||||
|
|
|
@ -313,10 +313,10 @@ Use this schema to define `clusters` objects in the [`schedule` rule type](#sche
|
|||
|
||||
| Field | Type | Possible values | Description |
|
||||
|--------------|---------------------|--------------------------|-------------|
|
||||
| `containers` | `array` of `string` | | The container name to be scanned (only the first value is currently supported). |
|
||||
| `resources` | `array` of `string` | | The resource name to be scanned (only the first value is currently supported). |
|
||||
| `namespaces` | `array` of `string` | | The namespace to be scanned (only the first value is currently supported). |
|
||||
| `kinds` | `array` of `string` | `deployment`/`daemonset` | The resource kind to be scanned (only the first value is currently supported). |
|
||||
| `containers` | `array` of `string` | | The container name that is scanned (only the first value is currently supported). |
|
||||
| `resources` | `array` of `string` | | The resource name that is scanned (only the first value is currently supported). |
|
||||
| `namespaces` | `array` of `string` | | The namespace that is scanned (only the first value is currently supported). |
|
||||
| `kinds` | `array` of `string` | `deployment`/`daemonset` | The resource kind that should be scanned (only the first value is currently supported). |
|
||||
|
||||
### `scan` action type
|
||||
|
||||
|
|
|
@ -100,6 +100,14 @@ You can give a user access to all projects in a group.
|
|||
1. Fill in the fields.
|
||||
- The role applies to all projects in the group. [Learn more about permissions](../permissions.md).
|
||||
- On the **Access expiration date**, the user can no longer access projects in the group.
|
||||
1. Select **Invite**.
|
||||
|
||||
Members that are not automatically added are displayed on the **Invited** tab.
|
||||
Users can be on this tab because they:
|
||||
|
||||
- Have not yet accepted the invitation.
|
||||
- Are waiting for [approval from an administrator](../admin_area/moderate_users.md).
|
||||
- [Exceed the group user cap](#user-cap-for-groups).
|
||||
|
||||
## Request access to a group
|
||||
|
||||
|
@ -508,6 +516,74 @@ To prevent a project from being shared with other groups:
|
|||
This setting applies to all subgroups unless overridden by a group owner. Groups already
|
||||
added to a project lose access when the setting is enabled.
|
||||
|
||||
## User cap for groups
|
||||
|
||||
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/330027) in GitLab 14.7.
|
||||
|
||||
FLAG:
|
||||
On self-managed GitLab, this feature is not available. On GitLab.com, this feature is available for some groups.
|
||||
This feature is not ready for production use.
|
||||
|
||||
When the number of billable members reaches the user cap, new users can't be added to the group
|
||||
without being approved by the group owner.
|
||||
|
||||
Groups with the user cap feature enabled have [group sharing](#share-a-group-with-another-group)
|
||||
disabled for the group and its subgroups.
|
||||
|
||||
### Specify a user cap for a group
|
||||
|
||||
Prerequisite:
|
||||
|
||||
- You must be assigned the [Owner role](../permissions.md#group-members-permissions) for the group.
|
||||
|
||||
To specify a user cap:
|
||||
|
||||
1. On the top bar, select **Menu > Groups** and find your group.
|
||||
You can set a cap on the top-level group only.
|
||||
1. On the left sidebar, select **Settings > General**.
|
||||
1. Expand **Permissions and group features**.
|
||||
1. In the **User cap** box, enter the desired number of users.
|
||||
1. Select **Save changes**.
|
||||
|
||||
If you already have more users in the group than the user cap value, users
|
||||
are not removed. However, you can't add more without approval.
|
||||
|
||||
Increasing the user cap does not approve pending members.
|
||||
|
||||
### Remove the user cap for a group
|
||||
|
||||
You can remove the user cap, so there is no limit on the number of members you can add to a group.
|
||||
|
||||
Prerequisite:
|
||||
|
||||
- You must be assigned the [Owner role](../permissions.md#group-members-permissions) for the group.
|
||||
|
||||
To remove the user cap:
|
||||
|
||||
1. On the top bar, select **Menu > Groups** and find your group.
|
||||
1. On the left sidebar, select **Settings > General**.
|
||||
1. Expand **Permissions and group features**.
|
||||
1. In the **User cap** box, delete the value.
|
||||
1. Select **Save changes**.
|
||||
|
||||
Decreasing the user cap does not approve pending members.
|
||||
|
||||
### Approve pending members for a group
|
||||
|
||||
When the number of billable users reaches the user cap, any new member is put in a pending state
|
||||
and must be approved.
|
||||
|
||||
Prerequisite:
|
||||
|
||||
- You must be assigned the [Owner role](../permissions.md#group-members-permissions) for the group.
|
||||
|
||||
To approve members that are pending because they've exceeded the user cap:
|
||||
|
||||
1. On the top bar, select **Menu > Groups** and find your group.
|
||||
1. On the left sidebar, select **Settings > Usage Quotas**.
|
||||
1. On the **Seats** tab, under the alert, select **View pending approvals**.
|
||||
1. For each member you want to approve, select **Approve**.
|
||||
|
||||
## Prevent members from being added to projects in a group **(PREMIUM)**
|
||||
|
||||
As a group owner, you can prevent any new project membership for all
|
||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 38 KiB |
|
@ -20,20 +20,6 @@ To learn about hierarchies in general, common frameworks, and using GitLab for
|
|||
portfolio management, see
|
||||
[How to use GitLab for Agile portfolio planning and project management](https://about.gitlab.com/blog/2020/11/11/gitlab-for-agile-portfolio-planning-project-management/).
|
||||
|
||||
## View planning hierarchies
|
||||
|
||||
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/340844/) in GitLab 14.7 and is behind the feature flag `work_items_hierarchy`.
|
||||
|
||||
To view the planning hierarchy in a project:
|
||||
|
||||
1. On the top bar, select **Menu > Projects** and find your project.
|
||||
1. On the left sidebar, select **Project information > Planning hierarchy**.
|
||||
|
||||
Under **Current structure**, you can see a hierarchy diagram that matches your current planning hierarchy.
|
||||
The work items outside your subscription plan show up below **Unavailable structure**.
|
||||
|
||||
![Screenshot showing hierarchy page](img/view-project-work-item-hierarchy_v14_7.png)
|
||||
|
||||
## Hierarchies with epics
|
||||
|
||||
With epics, you can achieve the following hierarchy:
|
||||
|
|
|
@ -11,10 +11,6 @@ module Gitlab
|
|||
|
||||
delegate :close, :tell, :seek, :size, :url, :truncate, to: :stream, allow_nil: true
|
||||
|
||||
delegate :valid?, to: :stream, allow_nil: true
|
||||
|
||||
alias_method :present?, :valid?
|
||||
|
||||
def initialize(metrics = Trace::Metrics.new)
|
||||
@stream = yield
|
||||
@stream&.binmode
|
||||
|
@ -24,6 +20,7 @@ module Gitlab
|
|||
def valid?
|
||||
self.stream.present?
|
||||
end
|
||||
alias_method :present?, :valid?
|
||||
|
||||
def file?
|
||||
self.path.present?
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
module Gitlab
|
||||
module Tracking
|
||||
class StandardContext
|
||||
GITLAB_STANDARD_SCHEMA_URL = 'iglu:com.gitlab/gitlab_standard/jsonschema/1-0-7'
|
||||
GITLAB_STANDARD_SCHEMA_URL = 'iglu:com.gitlab/gitlab_standard/jsonschema/1-0-8'
|
||||
GITLAB_RAILS_SOURCE = 'gitlab-rails'
|
||||
|
||||
def initialize(namespace: nil, project: nil, user: nil, **extra)
|
||||
|
@ -46,7 +46,8 @@ module Gitlab
|
|||
extra: extra,
|
||||
user_id: user&.id,
|
||||
namespace_id: namespace&.id,
|
||||
project_id: project_id
|
||||
project_id: project_id,
|
||||
context_generated_at: Time.current
|
||||
}
|
||||
end
|
||||
|
||||
|
|
|
@ -1,27 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
# This module has the necessary methods to render
|
||||
# work items hierarchy menu
|
||||
module Sidebars
|
||||
module Concerns
|
||||
module WorkItemHierarchy
|
||||
def hierarchy_menu_item(container, url, path)
|
||||
unless show_hierarachy_menu_item?(container)
|
||||
return ::Sidebars::NilMenuItem.new(item_id: :hierarchy)
|
||||
end
|
||||
|
||||
::Sidebars::MenuItem.new(
|
||||
title: _('Planning hierarchy'),
|
||||
link: url,
|
||||
active_routes: { path: path },
|
||||
item_id: :hierarchy
|
||||
)
|
||||
end
|
||||
|
||||
def show_hierarachy_menu_item?(container)
|
||||
Feature.enabled?(:work_items_hierarchy, container, default_enabled: :yaml) &&
|
||||
can?(context.current_user, :read_work_items_hierarchy, container)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -4,13 +4,10 @@ module Sidebars
|
|||
module Projects
|
||||
module Menus
|
||||
class ProjectInformationMenu < ::Sidebars::Menu
|
||||
include ::Sidebars::Concerns::WorkItemHierarchy
|
||||
|
||||
override :configure_menu_items
|
||||
def configure_menu_items
|
||||
add_item(activity_menu_item)
|
||||
add_item(labels_menu_item)
|
||||
add_item(hierarchy_menu_item(context.project, planning_hierarchy_project_path(context.project), 'projects#planning_hierarchy'))
|
||||
add_item(members_menu_item)
|
||||
|
||||
true
|
||||
|
|
|
@ -722,6 +722,9 @@ msgstr ""
|
|||
msgid "%{level_name} is not allowed since the fork source project has lower visibility."
|
||||
msgstr ""
|
||||
|
||||
msgid "%{linkStart}Learn more.%{linkEnd}"
|
||||
msgstr ""
|
||||
|
||||
msgid "%{link_start}Learn more%{link_end} about roles."
|
||||
msgstr ""
|
||||
|
||||
|
@ -2480,6 +2483,12 @@ msgstr ""
|
|||
msgid "AdminDashboard|Error loading the statistics. Please try again"
|
||||
msgstr ""
|
||||
|
||||
msgid "AdminGeo|The URL of the primary site that is used internally by the secondary sites."
|
||||
msgstr ""
|
||||
|
||||
msgid "AdminGeo|The URL of the secondary site that is used internally by the primary site."
|
||||
msgstr ""
|
||||
|
||||
msgid "AdminLabels|Define your default set of project labels"
|
||||
msgstr ""
|
||||
|
||||
|
@ -7071,9 +7080,6 @@ msgstr ""
|
|||
msgid "Child"
|
||||
msgstr ""
|
||||
|
||||
msgid "Child epic"
|
||||
msgstr ""
|
||||
|
||||
msgid "Child epic does not exist."
|
||||
msgstr ""
|
||||
|
||||
|
@ -17619,33 +17625,6 @@ msgstr[1] ""
|
|||
msgid "Hide values"
|
||||
msgstr ""
|
||||
|
||||
msgid "Hierarchy|Current structure"
|
||||
msgstr ""
|
||||
|
||||
msgid "Hierarchy|Deliver value more efficiently by breaking down necessary work into a hierarchical structure. This structure helps teams understand scope, priorities, and how work cascades up toward larger goals."
|
||||
msgstr ""
|
||||
|
||||
msgid "Hierarchy|Help us improve work items in GitLab!"
|
||||
msgstr ""
|
||||
|
||||
msgid "Hierarchy|Is there a framework or type of work item you wish you had access to in GitLab? Give us your feedback and help us build the experiences valuable to you."
|
||||
msgstr ""
|
||||
|
||||
msgid "Hierarchy|Planning hierarchy"
|
||||
msgstr ""
|
||||
|
||||
msgid "Hierarchy|Take the work items survey"
|
||||
msgstr ""
|
||||
|
||||
msgid "Hierarchy|These items are unavailable in the current structure."
|
||||
msgstr ""
|
||||
|
||||
msgid "Hierarchy|Unavailable structure"
|
||||
msgstr ""
|
||||
|
||||
msgid "Hierarchy|You can start using these items now."
|
||||
msgstr ""
|
||||
|
||||
msgid "High or unknown vulnerabilities present"
|
||||
msgstr ""
|
||||
|
||||
|
@ -26530,9 +26509,6 @@ msgstr ""
|
|||
msgid "Plan:"
|
||||
msgstr ""
|
||||
|
||||
msgid "Planning hierarchy"
|
||||
msgstr ""
|
||||
|
||||
msgid "PlantUML"
|
||||
msgstr ""
|
||||
|
||||
|
@ -30256,9 +30232,6 @@ msgstr ""
|
|||
msgid "Required only if you are not using role instance credentials."
|
||||
msgstr ""
|
||||
|
||||
msgid "Requirement"
|
||||
msgstr ""
|
||||
|
||||
msgid "Requirement %{reference} has been added"
|
||||
msgstr ""
|
||||
|
||||
|
@ -34942,9 +34915,6 @@ msgstr ""
|
|||
msgid "Target-Branch"
|
||||
msgstr ""
|
||||
|
||||
msgid "Task"
|
||||
msgstr ""
|
||||
|
||||
msgid "Task ID: %{elastic_task}"
|
||||
msgstr ""
|
||||
|
||||
|
@ -35190,9 +35160,6 @@ msgstr ""
|
|||
msgid "Test Cases"
|
||||
msgstr ""
|
||||
|
||||
msgid "Test case"
|
||||
msgstr ""
|
||||
|
||||
msgid "Test coverage parsing"
|
||||
msgstr ""
|
||||
|
||||
|
@ -35406,12 +35373,6 @@ msgstr ""
|
|||
msgid "The Snowplow cookie domain."
|
||||
msgstr ""
|
||||
|
||||
msgid "The URL defined on the primary node that secondary nodes should use to contact it."
|
||||
msgstr ""
|
||||
|
||||
msgid "The URL defined on the primary node that secondary nodes should use to contact it. %{linkStart}Learn more%{linkEnd}"
|
||||
msgstr ""
|
||||
|
||||
msgid "The URL of the Jenkins server."
|
||||
msgstr ""
|
||||
|
||||
|
|
|
@ -63,6 +63,7 @@ WORKDIR /home/gitlab/qa
|
|||
|
||||
# Install qa dependencies or fetch from cache if unchanged
|
||||
COPY ./qa/Gemfile* /home/gitlab/qa/
|
||||
RUN gem install bundler --no-document --conservative --version 2.3.5
|
||||
RUN bundle install --jobs=$(nproc) --retry=3 --without=development --quiet
|
||||
|
||||
##
|
||||
|
|
|
@ -355,4 +355,4 @@ DEPENDENCIES
|
|||
zeitwerk (~> 2.4)
|
||||
|
||||
BUNDLED WITH
|
||||
2.2.33
|
||||
2.3.5
|
||||
|
|
|
@ -36,6 +36,7 @@ function bundle_install_script() {
|
|||
exit 1;
|
||||
fi;
|
||||
|
||||
gem install bundler --no-document --conservative --version 2.3.5
|
||||
bundle --version
|
||||
bundle config set path "$(pwd)/vendor"
|
||||
bundle config set clean 'true'
|
||||
|
|
|
@ -47,7 +47,7 @@ RSpec.describe 'bin/metrics-server', :aggregate_failures do
|
|||
if @pid
|
||||
pgrp = Process.getpgid(@pid)
|
||||
|
||||
Timeout.timeout(5) do
|
||||
Timeout.timeout(10) do
|
||||
Process.kill('TERM', -pgrp)
|
||||
Process.waitpid(@pid)
|
||||
end
|
||||
|
@ -63,7 +63,7 @@ RSpec.describe 'bin/metrics-server', :aggregate_failures do
|
|||
|
||||
it 'serves /metrics endpoint' do
|
||||
expect do
|
||||
Timeout.timeout(5) do
|
||||
Timeout.timeout(10) do
|
||||
http_ok = false
|
||||
until http_ok
|
||||
sleep 1
|
||||
|
|
|
@ -1,39 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe WorkItemsHierarchy do
|
||||
controller(ApplicationController) do
|
||||
include WorkItemsHierarchy
|
||||
end
|
||||
|
||||
let_it_be(:user) { create(:user) }
|
||||
let_it_be(:group) { create(:group) }
|
||||
let_it_be(:project) { create(:project, group: group) }
|
||||
|
||||
render_views
|
||||
|
||||
before do
|
||||
sign_in user
|
||||
routes.draw { get :planning_hierarchy, to: "anonymous#planning_hierarchy" }
|
||||
controller.instance_variable_set(:@project, project)
|
||||
end
|
||||
|
||||
it 'renders hierarchy' do
|
||||
stub_feature_flags(work_items_hierarchy: true)
|
||||
|
||||
get :planning_hierarchy
|
||||
|
||||
expect(response).to have_gitlab_http_status(:ok)
|
||||
expect(response.body).to match(/id="js-work-items-hierarchy"/)
|
||||
end
|
||||
|
||||
it 'renders 404' do
|
||||
stub_feature_flags(work_items_hierarchy: false)
|
||||
|
||||
get :planning_hierarchy
|
||||
|
||||
expect(response).to have_gitlab_http_status(:not_found)
|
||||
expect(response.body).not_to match(/id="js-work-items-hierarchy"/)
|
||||
end
|
||||
end
|
|
@ -5,6 +5,7 @@ FactoryBot.define do
|
|||
name { 'test' }
|
||||
add_attribute(:protected) { false }
|
||||
created_at { 'Di 29. Okt 09:50:00 CET 2013' }
|
||||
scheduling_type { 'stage' }
|
||||
pending
|
||||
|
||||
options do
|
||||
|
@ -33,6 +34,8 @@ FactoryBot.define do
|
|||
end
|
||||
|
||||
trait :dependent do
|
||||
scheduling_type { 'dag' }
|
||||
|
||||
transient do
|
||||
sequence(:needed_name) { |n| "dependency #{n}" }
|
||||
needed { association(:ci_build, name: needed_name, pipeline: pipeline) }
|
||||
|
|
|
@ -1,7 +0,0 @@
|
|||
/* useful for timing promises when jest fakeTimers are not reliable enough */
|
||||
export default (timeout) =>
|
||||
new Promise((resolve) => {
|
||||
jest.useRealTimers();
|
||||
setTimeout(resolve, timeout);
|
||||
jest.useFakeTimers();
|
||||
});
|
|
@ -17,7 +17,7 @@ exports[`Blob Header Default Actions rendering matches the snapshot 1`] = `
|
|||
</div>
|
||||
|
||||
<div
|
||||
class="gl-display-none gl-sm-display-flex"
|
||||
class="gl-display-none gl-sm-display-flex file-actions"
|
||||
>
|
||||
<viewer-switcher-stub
|
||||
value="simple"
|
||||
|
|
|
@ -5,7 +5,6 @@ import Vue from 'vue';
|
|||
import Vuex from 'vuex';
|
||||
import '~/behaviors/markdown/render_gfm';
|
||||
import waitForPromises from 'helpers/wait_for_promises';
|
||||
import waitUsingRealTimer from 'helpers/wait_using_real_timer';
|
||||
import { exampleConfigs, exampleFiles } from 'jest/ide/lib/editorconfig/mock_data';
|
||||
import { EDITOR_CODE_INSTANCE_FN, EDITOR_DIFF_INSTANCE_FN } from '~/editor/constants';
|
||||
import { EditorMarkdownExtension } from '~/editor/extensions/source_editor_markdown_ext';
|
||||
|
@ -540,7 +539,6 @@ describe('RepoEditor', () => {
|
|||
},
|
||||
});
|
||||
await vm.$nextTick();
|
||||
await vm.$nextTick();
|
||||
|
||||
expect(vm.initEditor).toHaveBeenCalled();
|
||||
});
|
||||
|
@ -567,8 +565,8 @@ describe('RepoEditor', () => {
|
|||
// switching from edit to diff mode usually triggers editor initialization
|
||||
vm.$store.state.viewer = viewerTypes.diff;
|
||||
|
||||
// we delay returning the file to make sure editor doesn't initialize before we fetch file content
|
||||
await waitUsingRealTimer(30);
|
||||
jest.runOnlyPendingTimers();
|
||||
|
||||
return 'rawFileData123\n';
|
||||
});
|
||||
|
||||
|
@ -598,8 +596,9 @@ describe('RepoEditor', () => {
|
|||
return aContent;
|
||||
})
|
||||
.mockImplementationOnce(async () => {
|
||||
// we delay returning fileB content to make sure the editor doesn't initialize prematurely
|
||||
await waitUsingRealTimer(30);
|
||||
// we delay returning fileB content
|
||||
// to make sure the editor doesn't initialize prematurely
|
||||
jest.advanceTimersByTime(30);
|
||||
return bContent;
|
||||
});
|
||||
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -1,78 +0,0 @@
|
|||
import { nextTick } from 'vue';
|
||||
import { createLocalVue, mount } from '@vue/test-utils';
|
||||
import VueApollo from 'vue-apollo';
|
||||
import { GlBanner } from '@gitlab/ui';
|
||||
import App from '~/work_items_hierarchy/components/app.vue';
|
||||
import { extendedWrapper } from 'helpers/vue_test_utils_helper';
|
||||
|
||||
const localVue = createLocalVue();
|
||||
localVue.use(VueApollo);
|
||||
|
||||
describe('WorkItemsHierarchy App', () => {
|
||||
let wrapper;
|
||||
const createComponent = (props = {}, data = {}) => {
|
||||
wrapper = extendedWrapper(
|
||||
mount(App, {
|
||||
localVue,
|
||||
provide: {
|
||||
illustrationPath: '/foo.svg',
|
||||
licensePlan: 'free',
|
||||
...props,
|
||||
},
|
||||
data() {
|
||||
return data;
|
||||
},
|
||||
}),
|
||||
);
|
||||
};
|
||||
|
||||
afterEach(() => {
|
||||
wrapper.destroy();
|
||||
});
|
||||
|
||||
describe.each`
|
||||
licensePlan
|
||||
${'free'}
|
||||
${'premium'}
|
||||
${'ultimate'}
|
||||
`('when licensePlan is $licensePlan', ({ licensePlan }) => {
|
||||
beforeEach(() => {
|
||||
createComponent({ licensePlan });
|
||||
});
|
||||
|
||||
it('matches the snapshot', () => {
|
||||
expect(wrapper.element).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
|
||||
describe('survey banner', () => {
|
||||
it('shows when the banner is visible', () => {
|
||||
createComponent({}, { bannerVisible: true });
|
||||
|
||||
expect(wrapper.find(GlBanner).exists()).toBe(true);
|
||||
});
|
||||
|
||||
it('hide when close is called', async () => {
|
||||
createComponent({}, { bannerVisible: true });
|
||||
|
||||
wrapper.findByTestId('close-icon').trigger('click');
|
||||
|
||||
await nextTick();
|
||||
|
||||
expect(wrapper.find(GlBanner).exists()).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Unavailable structure', () => {
|
||||
it.each`
|
||||
licensePlan | visible
|
||||
${'free'} | ${true}
|
||||
${'premium'} | ${true}
|
||||
${'ultimate'} | ${false}
|
||||
`('visibility is $visible when plan is $licensePlan', ({ licensePlan, visible }) => {
|
||||
createComponent({ licensePlan });
|
||||
|
||||
expect(wrapper.findByTestId('unavailable-structure').exists()).toBe(visible);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,118 +0,0 @@
|
|||
import { createLocalVue, mount } from '@vue/test-utils';
|
||||
import VueApollo from 'vue-apollo';
|
||||
import { GlBadge } from '@gitlab/ui';
|
||||
import Hierarchy from '~/work_items_hierarchy/components/hierarchy.vue';
|
||||
import { extendedWrapper } from 'helpers/vue_test_utils_helper';
|
||||
import RESPONSE from '~/work_items_hierarchy/static_response';
|
||||
import { workItemTypes } from '~/work_items/constants';
|
||||
|
||||
const localVue = createLocalVue();
|
||||
localVue.use(VueApollo);
|
||||
|
||||
describe('WorkItemsHierarchy Hierarchy', () => {
|
||||
let wrapper;
|
||||
|
||||
const workItemsFromResponse = (response) => {
|
||||
return response.reduce(
|
||||
(itemTypes, item) => {
|
||||
const key = item.available ? 'available' : 'unavailable';
|
||||
itemTypes[key].push({
|
||||
...item,
|
||||
...workItemTypes[item.type],
|
||||
nestedTypes: item.nestedTypes
|
||||
? item.nestedTypes.map((type) => workItemTypes[type])
|
||||
: null,
|
||||
});
|
||||
return itemTypes;
|
||||
},
|
||||
{ available: [], unavailable: [] },
|
||||
);
|
||||
};
|
||||
|
||||
const createComponent = (props = {}) => {
|
||||
wrapper = extendedWrapper(
|
||||
mount(Hierarchy, {
|
||||
localVue,
|
||||
propsData: {
|
||||
workItemTypes: props.workItemTypes,
|
||||
...props,
|
||||
},
|
||||
}),
|
||||
);
|
||||
};
|
||||
|
||||
afterEach(() => {
|
||||
wrapper.destroy();
|
||||
});
|
||||
|
||||
describe('available structure', () => {
|
||||
let items = [];
|
||||
|
||||
beforeEach(() => {
|
||||
items = workItemsFromResponse(RESPONSE.ultimate).available;
|
||||
createComponent({ workItemTypes: items });
|
||||
});
|
||||
|
||||
it('renders all work items', () => {
|
||||
expect(wrapper.findAllByTestId('work-item-wrapper')).toHaveLength(items.length);
|
||||
});
|
||||
|
||||
it('does not render badges', () => {
|
||||
expect(wrapper.find(GlBadge).exists()).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('unavailable structure', () => {
|
||||
let items = [];
|
||||
|
||||
beforeEach(() => {
|
||||
items = workItemsFromResponse(RESPONSE.premium).unavailable;
|
||||
createComponent({ workItemTypes: items });
|
||||
});
|
||||
|
||||
it('renders all work items', () => {
|
||||
expect(wrapper.findAllByTestId('work-item-wrapper')).toHaveLength(items.length);
|
||||
});
|
||||
|
||||
it('renders license badges for all work items', () => {
|
||||
expect(wrapper.findAll(GlBadge)).toHaveLength(items.length);
|
||||
});
|
||||
|
||||
it('does not render svg icon for linking', () => {
|
||||
expect(wrapper.findByTestId('hierarchy-rounded-arrow-tail').exists()).toBe(false);
|
||||
expect(wrapper.findByTestId('level-up-icon').exists()).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('nested work items', () => {
|
||||
describe.each`
|
||||
licensePlan | arrowTailVisible | levelUpIconVisible | arrowDownIconVisible
|
||||
${'ultimate'} | ${true} | ${true} | ${true}
|
||||
${'premium'} | ${false} | ${false} | ${true}
|
||||
${'free'} | ${false} | ${false} | ${false}
|
||||
`(
|
||||
'when $licensePlan license',
|
||||
({ licensePlan, arrowTailVisible, levelUpIconVisible, arrowDownIconVisible }) => {
|
||||
let items = [];
|
||||
beforeEach(() => {
|
||||
items = workItemsFromResponse(RESPONSE[licensePlan]).available;
|
||||
createComponent({ workItemTypes: items });
|
||||
});
|
||||
|
||||
it(`${arrowTailVisible ? 'render' : 'does not render'} arrow tail svg`, () => {
|
||||
expect(wrapper.findByTestId('hierarchy-rounded-arrow-tail').exists()).toBe(
|
||||
arrowTailVisible,
|
||||
);
|
||||
});
|
||||
|
||||
it(`${levelUpIconVisible ? 'render' : 'does not render'} arrow tail svg`, () => {
|
||||
expect(wrapper.findByTestId('level-up-icon').exists()).toBe(levelUpIconVisible);
|
||||
});
|
||||
|
||||
it(`${arrowDownIconVisible ? 'render' : 'does not render'} arrow tail svg`, () => {
|
||||
expect(wrapper.findByTestId('arrow-down-icon').exists()).toBe(arrowDownIconVisible);
|
||||
});
|
||||
},
|
||||
);
|
||||
});
|
||||
});
|
|
@ -1,16 +0,0 @@
|
|||
import { inferLicensePlan } from '~/work_items_hierarchy/hierarchy_util';
|
||||
import { LICENSE_PLAN } from '~/work_items_hierarchy/constants';
|
||||
|
||||
describe('inferLicensePlan', () => {
|
||||
it.each`
|
||||
epics | subEpics | licensePlan
|
||||
${true} | ${true} | ${LICENSE_PLAN.ULTIMATE}
|
||||
${true} | ${false} | ${LICENSE_PLAN.PREMIUM}
|
||||
${false} | ${false} | ${LICENSE_PLAN.FREE}
|
||||
`(
|
||||
'returns $licensePlan when epic is $epics and sub-epic is $subEpics',
|
||||
({ epics, subEpics, licensePlan }) => {
|
||||
expect(inferLicensePlan({ hasEpics: epics, hasSubEpics: subEpics })).toBe(licensePlan);
|
||||
},
|
||||
);
|
||||
});
|
|
@ -92,7 +92,7 @@ RSpec.describe Gitlab::Gfm::ReferenceRewriter do
|
|||
let!(:group_label) { create(:group_label, id: 321, name: 'group label', group: old_group) }
|
||||
|
||||
before do
|
||||
old_project.update(namespace: old_group)
|
||||
old_project.update!(namespace: old_group)
|
||||
end
|
||||
|
||||
context 'label referenced by id' do
|
||||
|
|
|
@ -96,7 +96,7 @@ RSpec.describe Gitlab::GitAccess do
|
|||
|
||||
context 'when the DeployKey has access to the project' do
|
||||
before do
|
||||
deploy_key.deploy_keys_projects.create(project: project, can_push: true)
|
||||
deploy_key.deploy_keys_projects.create!(project: project, can_push: true)
|
||||
end
|
||||
|
||||
it 'allows push and pull access' do
|
||||
|
@ -820,7 +820,7 @@ RSpec.describe Gitlab::GitAccess do
|
|||
project.add_role(user, role)
|
||||
end
|
||||
|
||||
protected_branch.save
|
||||
protected_branch.save!
|
||||
|
||||
aggregate_failures do
|
||||
matrix.each do |action, allowed|
|
||||
|
@ -1090,7 +1090,7 @@ RSpec.describe Gitlab::GitAccess do
|
|||
context 'when deploy_key can push' do
|
||||
context 'when project is authorized' do
|
||||
before do
|
||||
key.deploy_keys_projects.create(project: project, can_push: true)
|
||||
key.deploy_keys_projects.create!(project: project, can_push: true)
|
||||
end
|
||||
|
||||
it { expect { push_access_check }.not_to raise_error }
|
||||
|
@ -1120,7 +1120,7 @@ RSpec.describe Gitlab::GitAccess do
|
|||
context 'when deploy_key cannot push' do
|
||||
context 'when project is authorized' do
|
||||
before do
|
||||
key.deploy_keys_projects.create(project: project, can_push: false)
|
||||
key.deploy_keys_projects.create!(project: project, can_push: false)
|
||||
end
|
||||
|
||||
it { expect { push_access_check }.to raise_forbidden(described_class::ERROR_MESSAGES[:deploy_key_upload]) }
|
||||
|
|
|
@ -281,6 +281,7 @@ ci_pipelines:
|
|||
- dast_site_profiles_pipeline
|
||||
- package_build_infos
|
||||
- package_file_build_infos
|
||||
- build_trace_chunks
|
||||
ci_refs:
|
||||
- project
|
||||
- ci_pipelines
|
||||
|
|
|
@ -20,7 +20,7 @@ RSpec.describe Gitlab::ImportExport::AvatarSaver do
|
|||
end
|
||||
|
||||
it 'saves a project avatar' do
|
||||
described_class.new(project: project_with_avatar, shared: shared).save
|
||||
described_class.new(project: project_with_avatar, shared: shared).save # rubocop:disable Rails/SaveBang
|
||||
|
||||
expect(File).to exist(Dir["#{shared.export_path}/avatar/**/dk.png"].first)
|
||||
end
|
||||
|
|
|
@ -11,7 +11,7 @@ RSpec.describe Gitlab::ImportExport::Base::RelationFactory do
|
|||
let(:excluded_keys) { [] }
|
||||
|
||||
subject do
|
||||
described_class.create(relation_sym: relation_sym,
|
||||
described_class.create(relation_sym: relation_sym, # rubocop:disable Rails/SaveBang
|
||||
relation_hash: relation_hash,
|
||||
relation_index: 1,
|
||||
object_builder: Gitlab::ImportExport::Project::ObjectBuilder,
|
||||
|
|
|
@ -24,7 +24,7 @@ RSpec.describe Gitlab::ImportExport::DesignRepoRestorer do
|
|||
allow(instance).to receive(:storage_path).and_return(export_path)
|
||||
end
|
||||
|
||||
bundler.save
|
||||
bundler.save # rubocop:disable Rails/SaveBang
|
||||
end
|
||||
|
||||
after do
|
||||
|
|
|
@ -58,6 +58,10 @@ RSpec.describe Gitlab::Tracking::StandardContext do
|
|||
expect(snowplow_context.to_json.dig(:data, :source)).to eq(described_class::GITLAB_RAILS_SOURCE)
|
||||
end
|
||||
|
||||
it 'contains context_generated_at timestamp', :freeze_time do
|
||||
expect(snowplow_context.to_json.dig(:data, :context_generated_at)).to eq(Time.current)
|
||||
end
|
||||
|
||||
context 'plan' do
|
||||
context 'when namespace is not available' do
|
||||
it 'is nil' do
|
||||
|
|
|
@ -1,36 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Sidebars::Concerns::WorkItemHierarchy do
|
||||
shared_examples 'hierarchy menu' do
|
||||
let(:item_id) { :hierarchy }
|
||||
|
||||
context 'when the feature is disabled does not render' do
|
||||
before do
|
||||
stub_feature_flags(work_items_hierarchy: false)
|
||||
end
|
||||
|
||||
specify { is_expected.to be_nil }
|
||||
end
|
||||
|
||||
context 'when the feature is enabled does render' do
|
||||
before do
|
||||
stub_feature_flags(work_items_hierarchy: true)
|
||||
end
|
||||
|
||||
specify { is_expected.not_to be_nil }
|
||||
end
|
||||
end
|
||||
|
||||
describe 'Project hierarchy menu item' do
|
||||
let_it_be_with_reload(:project) { create(:project, :repository) }
|
||||
|
||||
let(:user) { project.owner }
|
||||
let(:context) { Sidebars::Projects::Context.new(current_user: user, container: project) }
|
||||
|
||||
subject { Sidebars::Projects::Menus::ProjectInformationMenu.new(context).renderable_items.index { |e| e.item_id == item_id } }
|
||||
|
||||
it_behaves_like 'hierarchy menu'
|
||||
end
|
||||
end
|
|
@ -59,25 +59,5 @@ RSpec.describe Sidebars::Projects::Menus::ProjectInformationMenu do
|
|||
specify { is_expected.to be_nil }
|
||||
end
|
||||
end
|
||||
|
||||
describe 'Hierarchy' do
|
||||
let(:item_id) { :hierarchy }
|
||||
|
||||
context 'when the feature is disabled' do
|
||||
before do
|
||||
stub_feature_flags(work_items_hierarchy: false)
|
||||
end
|
||||
|
||||
specify { is_expected.to be_nil }
|
||||
end
|
||||
|
||||
context 'when the feature is enabled' do
|
||||
before do
|
||||
stub_feature_flags(work_items_hierarchy: true)
|
||||
end
|
||||
|
||||
specify { is_expected.not_to be_nil }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -49,9 +49,8 @@ RSpec.describe Ci::BuildTraceChunk, :clean_gitlab_redis_shared_state, :clean_git
|
|||
end
|
||||
|
||||
context 'FastDestroyAll' do
|
||||
let(:project) { create(:project) }
|
||||
let(:pipeline) { create(:ci_pipeline, project: project) }
|
||||
let!(:build) { create(:ci_build, :running, :trace_live, pipeline: pipeline, project: project) }
|
||||
let(:pipeline) { create(:ci_pipeline) }
|
||||
let!(:build) { create(:ci_build, :running, :trace_live, pipeline: pipeline) }
|
||||
let(:subjects) { build.trace_chunks }
|
||||
|
||||
describe 'Forbid #destroy and #destroy_all' do
|
||||
|
@ -84,13 +83,7 @@ RSpec.describe Ci::BuildTraceChunk, :clean_gitlab_redis_shared_state, :clean_git
|
|||
expect(external_data_counter).to be > 0
|
||||
expect(subjects.count).to be > 0
|
||||
|
||||
::Gitlab::Database::QueryAnalyzers::PreventCrossDatabaseModification.allow_cross_database_modification_within_transaction(url: 'https://gitlab.com/gitlab-org/gitlab/-/issues/350185') do
|
||||
# This should use to prevent cross-DB modification
|
||||
# but due to https://gitlab.com/gitlab-org/gitlab/-/issues/350185
|
||||
# the build trace chunks are not destroyed by Projects::DestroyService
|
||||
# Change to: expect { Projects::DestroyService.new(project, project.owner).execute }.to eq(true)
|
||||
expect { project.destroy! }.not_to raise_error
|
||||
end
|
||||
expect { pipeline.destroy! }.not_to raise_error
|
||||
|
||||
expect(subjects.count).to eq(0)
|
||||
expect(external_data_counter).to eq(0)
|
||||
|
@ -858,13 +851,7 @@ RSpec.describe Ci::BuildTraceChunk, :clean_gitlab_redis_shared_state, :clean_git
|
|||
|
||||
context 'when project is destroyed' do
|
||||
let(:subject) do
|
||||
::Gitlab::Database::QueryAnalyzers::PreventCrossDatabaseModification.allow_cross_database_modification_within_transaction(url: 'https://gitlab.com/gitlab-org/gitlab/-/issues/350185') do
|
||||
# This should use to prevent cross-DB modification
|
||||
# but due to https://gitlab.com/gitlab-org/gitlab/-/issues/350185
|
||||
# the build trace chunks are not destroyed by Projects::DestroyService
|
||||
# Change to: Projects::DestroyService.new(project, project.owner).execute
|
||||
project.destroy!
|
||||
end
|
||||
Projects::DestroyService.new(project, project.owner).execute
|
||||
end
|
||||
|
||||
it_behaves_like 'deletes all build_trace_chunk and data in redis'
|
||||
|
|
|
@ -31,6 +31,7 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep do
|
|||
it { is_expected.to have_many(:statuses_order_id_desc) }
|
||||
it { is_expected.to have_many(:bridges) }
|
||||
it { is_expected.to have_many(:job_artifacts).through(:builds) }
|
||||
it { is_expected.to have_many(:build_trace_chunks).through(:builds) }
|
||||
it { is_expected.to have_many(:auto_canceled_pipelines) }
|
||||
it { is_expected.to have_many(:auto_canceled_jobs) }
|
||||
it { is_expected.to have_many(:sourced_pipelines) }
|
||||
|
|
|
@ -8,46 +8,56 @@ RSpec.describe Ci::AfterRequeueJobService do
|
|||
|
||||
let(:pipeline) { create(:ci_pipeline, project: project) }
|
||||
|
||||
let!(:build) { create(:ci_build, pipeline: pipeline, stage_idx: 0, name: 'build') }
|
||||
let!(:test1) { create(:ci_build, :success, pipeline: pipeline, stage_idx: 1) }
|
||||
let!(:test2) { create(:ci_build, :skipped, pipeline: pipeline, stage_idx: 1) }
|
||||
let!(:test3) { create(:ci_build, :skipped, :dependent, pipeline: pipeline, stage_idx: 1, needed: build) }
|
||||
let!(:deploy) { create(:ci_build, :skipped, :dependent, pipeline: pipeline, stage_idx: 2, needed: test3) }
|
||||
let!(:build1) { create(:ci_build, name: 'build1', pipeline: pipeline, stage_idx: 0) }
|
||||
let!(:test1) { create(:ci_build, :success, name: 'test1', pipeline: pipeline, stage_idx: 1) }
|
||||
let!(:test2) { create(:ci_build, :skipped, name: 'test2', pipeline: pipeline, stage_idx: 1) }
|
||||
let!(:test3) { create(:ci_build, :skipped, :dependent, name: 'test3', pipeline: pipeline, stage_idx: 1, needed: build1) }
|
||||
let!(:deploy) { create(:ci_build, :skipped, :dependent, name: 'deploy', pipeline: pipeline, stage_idx: 2, needed: test3) }
|
||||
|
||||
subject(:execute_service) { described_class.new(project, user).execute(build) }
|
||||
subject(:execute_service) { described_class.new(project, user).execute(build1) }
|
||||
|
||||
it 'marks subsequent skipped jobs as processable' do
|
||||
expect(test1.reload).to be_success
|
||||
expect(test2.reload).to be_skipped
|
||||
expect(test3.reload).to be_skipped
|
||||
expect(deploy.reload).to be_skipped
|
||||
shared_examples 'processing subsequent skipped jobs' do
|
||||
it 'marks subsequent skipped jobs as processable' do
|
||||
expect(test1.reload).to be_success
|
||||
expect(test2.reload).to be_skipped
|
||||
expect(test3.reload).to be_skipped
|
||||
expect(deploy.reload).to be_skipped
|
||||
|
||||
execute_service
|
||||
execute_service
|
||||
|
||||
expect(test1.reload).to be_success
|
||||
expect(test2.reload).to be_created
|
||||
expect(test3.reload).to be_created
|
||||
expect(deploy.reload).to be_created
|
||||
expect(test1.reload).to be_success
|
||||
expect(test2.reload).to be_created
|
||||
expect(test3.reload).to be_created
|
||||
expect(deploy.reload).to be_created
|
||||
end
|
||||
end
|
||||
|
||||
it_behaves_like 'processing subsequent skipped jobs'
|
||||
|
||||
context 'when there is a job need from the same stage' do
|
||||
let!(:test4) do
|
||||
let!(:build2) do
|
||||
create(:ci_build,
|
||||
:skipped,
|
||||
:dependent,
|
||||
name: 'build2',
|
||||
pipeline: pipeline,
|
||||
stage_idx: 0,
|
||||
scheduling_type: :dag,
|
||||
needed: build)
|
||||
needed: build1)
|
||||
end
|
||||
|
||||
it 'marks subsequent skipped jobs as processable' do
|
||||
expect { execute_service }.to change { test4.reload.status }.from('skipped').to('created')
|
||||
shared_examples 'processing the same stage job' do
|
||||
it 'marks subsequent skipped jobs as processable' do
|
||||
expect { execute_service }.to change { build2.reload.status }.from('skipped').to('created')
|
||||
end
|
||||
end
|
||||
|
||||
it_behaves_like 'processing subsequent skipped jobs'
|
||||
it_behaves_like 'processing the same stage job'
|
||||
end
|
||||
|
||||
context 'when the pipeline is a downstream pipeline and the bridge is depended' do
|
||||
let!(:trigger_job) { create(:ci_bridge, :strategy_depend, status: 'success') }
|
||||
let!(:trigger_job) { create(:ci_bridge, :strategy_depend, name: 'trigger_job', status: 'success') }
|
||||
|
||||
before do
|
||||
create(:ci_sources_pipeline, pipeline: pipeline, source_job: trigger_job)
|
||||
|
|
|
@ -66,6 +66,28 @@ RSpec.describe ::Ci::DestroyPipelineService do
|
|||
expect { subject }.to change { Ci::DeletedObject.count }
|
||||
end
|
||||
end
|
||||
|
||||
context 'when job has trace chunks' do
|
||||
let(:connection_params) { Gitlab.config.artifacts.object_store.connection.symbolize_keys }
|
||||
let(:connection) { ::Fog::Storage.new(connection_params) }
|
||||
|
||||
before do
|
||||
stub_object_storage(connection_params: connection_params, remote_directory: 'artifacts')
|
||||
stub_artifacts_object_storage
|
||||
end
|
||||
|
||||
let!(:trace_chunk) { create(:ci_build_trace_chunk, :fog_with_data, build: build) }
|
||||
|
||||
it 'destroys associated trace chunks' do
|
||||
subject
|
||||
|
||||
expect { trace_chunk.reload }.to raise_error(ActiveRecord::RecordNotFound)
|
||||
end
|
||||
|
||||
it 'removes data from object store' do
|
||||
expect { subject }.to change { Ci::BuildTraceChunks::Fog.new.data(trace_chunk) }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when pipeline is in cancelable state' do
|
||||
|
|
|
@ -1004,6 +1004,63 @@ RSpec.describe Ci::PipelineProcessing::AtomicProcessingService do
|
|||
end
|
||||
end
|
||||
|
||||
context 'when the dependency is stage-independent', :sidekiq_inline do
|
||||
let(:config) do
|
||||
<<-EOY
|
||||
stages: [A, B]
|
||||
|
||||
A1:
|
||||
stage: A
|
||||
script: exit 0
|
||||
when: manual
|
||||
|
||||
A2:
|
||||
stage: A
|
||||
script: exit 0
|
||||
needs: [A1]
|
||||
|
||||
B:
|
||||
stage: B
|
||||
needs: [A2]
|
||||
script: exit 0
|
||||
EOY
|
||||
end
|
||||
|
||||
let(:pipeline) do
|
||||
Ci::CreatePipelineService.new(project, user, { ref: 'master' }).execute(:push).payload
|
||||
end
|
||||
|
||||
before do
|
||||
stub_ci_pipeline_yaml_file(config)
|
||||
end
|
||||
|
||||
it 'processes subsequent jobs in the correct order when playing first job' do
|
||||
expect(all_builds_names).to eq(%w[A1 A2 B])
|
||||
expect(all_builds_statuses).to eq(%w[manual skipped skipped])
|
||||
|
||||
play_manual_action('A1')
|
||||
|
||||
expect(all_builds_names).to eq(%w[A1 A2 B])
|
||||
expect(all_builds_statuses).to eq(%w[pending created created])
|
||||
end
|
||||
|
||||
context 'when the FF ci_order_subsequent_jobs_by_stage is disabled' do
|
||||
before do
|
||||
stub_feature_flags(ci_order_subsequent_jobs_by_stage: false)
|
||||
end
|
||||
|
||||
it 'processes subsequent jobs in an incorrect order when playing first job' do
|
||||
expect(all_builds_names).to eq(%w[A1 A2 B])
|
||||
expect(all_builds_statuses).to eq(%w[manual skipped skipped])
|
||||
|
||||
play_manual_action('A1')
|
||||
|
||||
expect(all_builds_names).to eq(%w[A1 A2 B])
|
||||
expect(all_builds_statuses).to eq(%w[pending created skipped])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def all_builds
|
||||
|
|
|
@ -212,9 +212,7 @@ module TestEnv
|
|||
spawn_script = Rails.root.join('scripts/gitaly-test-spawn').to_s
|
||||
Bundler.with_original_env do
|
||||
unless system(spawn_script)
|
||||
message = 'gitaly spawn failed'
|
||||
message += " (try `rm -rf #{gitaly_dir}` ?)" unless ci?
|
||||
raise message
|
||||
raise gitaly_failure_message
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -616,6 +614,39 @@ module TestEnv
|
|||
|
||||
expected_version == sha.chomp
|
||||
end
|
||||
|
||||
def gitaly_failure_message
|
||||
message = "gitaly spawn failed\n\n"
|
||||
|
||||
message += "- The `gitaly` binary does not exist: #{gitaly_binary}\n" unless File.exist?(gitaly_binary)
|
||||
message += "- The `praefect` binary does not exist: #{praefect_binary}\n" unless File.exist?(praefect_binary)
|
||||
message += "- The `git` binary does not exist: #{git_binary}\n" unless File.exist?(git_binary)
|
||||
|
||||
message += "\nCheck log/gitaly-test.log for errors.\n"
|
||||
|
||||
unless ci?
|
||||
message += "\nIf binaries are missing, try running `make -C tmp/tests/gitaly build git.`\n"
|
||||
message += "\nOtherwise, try running `rm -rf #{gitaly_dir}`."
|
||||
end
|
||||
|
||||
message
|
||||
end
|
||||
|
||||
def git_binary
|
||||
File.join(gitaly_dir, "_build", "deps", "git", "install", "bin", "git")
|
||||
end
|
||||
|
||||
def gitaly_binary
|
||||
File.join(gitaly_dir, "_build", "bin", "gitaly")
|
||||
end
|
||||
|
||||
def praefect_binary
|
||||
File.join(gitaly_dir, "_build", "bin", "praefect")
|
||||
end
|
||||
|
||||
def git_binary_exists?
|
||||
File.exist?(git_binary)
|
||||
end
|
||||
end
|
||||
|
||||
require_relative('../../../ee/spec/support/helpers/ee/test_env') if Gitlab.ee?
|
||||
|
|
|
@ -22,7 +22,6 @@ RSpec.shared_context 'project navbar structure' do
|
|||
nav_sub_items: [
|
||||
_('Activity'),
|
||||
_('Labels'),
|
||||
_('Planning hierarchy'),
|
||||
_('Members')
|
||||
]
|
||||
},
|
||||
|
|
|
@ -17,7 +17,7 @@ RSpec.shared_context 'ProjectPolicy context' do
|
|||
%i[
|
||||
award_emoji create_issue create_merge_request_in create_note
|
||||
create_project read_issue_board read_issue read_issue_iid read_issue_link
|
||||
read_label read_work_items_hierarchy read_issue_board_list read_milestone read_note read_project
|
||||
read_label read_issue_board_list read_milestone read_note read_project
|
||||
read_project_for_iids read_project_member read_release read_snippet
|
||||
read_wiki upload_file
|
||||
]
|
||||
|
|
Loading…
Reference in New Issue