Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
a2c7a6bbd5
commit
e48c28ed86
|
@ -168,7 +168,6 @@ setup-test-env:
|
|||
- .rails:rules:code-backstage-qa
|
||||
stage: prepare
|
||||
variables:
|
||||
GITLAB_TEST_EAGER_LOAD: "0"
|
||||
SETUP_DB: "false"
|
||||
script:
|
||||
- run_timed_command "scripts/setup-test-env"
|
||||
|
|
|
@ -381,8 +381,8 @@
|
|||
- "config/helpers/**/*.js"
|
||||
- "vendor/assets/javascripts/**/*"
|
||||
|
||||
.feature-flag-config-patterns: &feature-flag-config-patterns
|
||||
- "{,ee/}config/feature_flags/**/*.yml"
|
||||
.feature-flag-development-config-patterns: &feature-flag-development-config-patterns
|
||||
- "{,ee/}config/feature_flags/development/*.yml"
|
||||
|
||||
################
|
||||
# Shared rules #
|
||||
|
@ -686,7 +686,7 @@
|
|||
- <<: *if-not-ee
|
||||
when: never
|
||||
- <<: *if-dot-com-gitlab-org-and-security-merge-request
|
||||
changes: *feature-flag-config-patterns
|
||||
changes: *feature-flag-development-config-patterns
|
||||
when: never
|
||||
- <<: *if-dot-com-gitlab-org-and-security-merge-request
|
||||
changes: *ci-qa-patterns
|
||||
|
@ -706,7 +706,7 @@
|
|||
- <<: *if-not-ee
|
||||
when: never
|
||||
- <<: *if-dot-com-gitlab-org-and-security-merge-request
|
||||
changes: *feature-flag-config-patterns
|
||||
changes: *feature-flag-development-config-patterns
|
||||
when: manual
|
||||
allow_failure: true
|
||||
|
||||
|
|
|
@ -69,15 +69,13 @@ tasks:
|
|||
printf "Waiting for GitLab at $(gp url 3000) ..."
|
||||
# Check /-/readiness which returns JSON, but we're only interested in the exit code
|
||||
#
|
||||
# We use http://localhost:3000 instead of the public hostname because
|
||||
# We use http://localhost:3000 instead of the public hostname because
|
||||
# it's no longer possible to access as specific cookies are required
|
||||
until curl --silent --no-buffer --fail http://localhost:3000/-/readiness > /dev/null 2>&1; do printf '.'; sleep 5; done && echo ""
|
||||
# Give Gitpod a few more seconds to set up everything ...
|
||||
sleep 5
|
||||
printf "$(date) – GitLab is up (took ~%.1f minutes)\n" "$((10*$SECONDS/60))e-1" | tee -a /workspace/startup.log
|
||||
gp preview $(gp url 3000) || true
|
||||
# Speed up backend tests
|
||||
export GITLAB_TEST_EAGER_LOAD=false
|
||||
)
|
||||
|
||||
ports:
|
||||
|
|
|
@ -214,8 +214,9 @@ export default {
|
|||
:labels-create-title="createLabelTitle"
|
||||
:labels-filter-base-path="projectPathForActiveIssue"
|
||||
:attr-workspace-path="attrWorkspacePath"
|
||||
workspace-type="project"
|
||||
:issuable-type="issuableType"
|
||||
:label-type="labelType"
|
||||
:label-create-type="labelType"
|
||||
@onLabelRemove="handleLabelRemove"
|
||||
@updateSelectedLabels="handleUpdateSelectedLabels"
|
||||
>
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<script>
|
||||
import { GlButtonGroup, GlButton, GlTooltipDirective } from '@gitlab/ui';
|
||||
import { GlButtonGroup, GlButton, GlTooltipDirective, GlSafeHtmlDirective } from '@gitlab/ui';
|
||||
|
||||
import CommitPipelineStatus from '~/projects/tree/components/commit_pipeline_status_component.vue';
|
||||
import ModalCopyButton from '~/vue_shared/components/modal_copy_button.vue';
|
||||
|
@ -34,6 +34,7 @@ export default {
|
|||
},
|
||||
directives: {
|
||||
GlTooltip: GlTooltipDirective,
|
||||
SafeHtml: GlSafeHtmlDirective,
|
||||
},
|
||||
mixins: [glFeatureFlagsMixin()],
|
||||
props: {
|
||||
|
@ -88,6 +89,9 @@ export default {
|
|||
initUserPopovers(this.$el.querySelectorAll('.js-user-link'));
|
||||
});
|
||||
},
|
||||
safeHtmlConfig: {
|
||||
ADD_TAGS: ['gl-emoji'],
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
|
@ -101,7 +105,7 @@ export default {
|
|||
>
|
||||
<div
|
||||
v-if="commit.signature_html"
|
||||
v-html="commit.signature_html /* eslint-disable-line vue/no-v-html */"
|
||||
v-safe-html:[$options.safeHtmlConfig]="commit.signature_html"
|
||||
></div>
|
||||
<commit-pipeline-status
|
||||
v-if="commit.pipeline_status_path"
|
||||
|
@ -142,9 +146,9 @@ export default {
|
|||
<div class="commit-detail flex-list">
|
||||
<div class="commit-content" data-qa-selector="commit_content">
|
||||
<a
|
||||
v-safe-html:[$options.safeHtmlConfig]="commit.title_html"
|
||||
:href="commit.commit_url"
|
||||
class="commit-row-message item-title"
|
||||
v-html="commit.title_html /* eslint-disable-line vue/no-v-html */"
|
||||
></a>
|
||||
|
||||
<span class="commit-row-message d-block d-sm-none">· {{ commit.short_id }}</span>
|
||||
|
@ -174,9 +178,9 @@ export default {
|
|||
<div>
|
||||
<pre
|
||||
v-if="commit.description_html"
|
||||
v-safe-html:[$options.safeHtmlConfig]="commitDescription"
|
||||
:class="{ 'js-toggle-content': collapsible, 'd-block': !collapsible }"
|
||||
class="commit-row-description gl-mb-3 gl-text-body"
|
||||
v-html="commitDescription /* eslint-disable-line vue/no-v-html */"
|
||||
></pre>
|
||||
</div>
|
||||
</li>
|
||||
|
|
|
@ -39,3 +39,8 @@ export const IncidentType = 'incident';
|
|||
export const issueState = { issueType: undefined, isDirty: false };
|
||||
|
||||
export const POLLING_DELAY = 2000;
|
||||
|
||||
export const WorkspaceType = {
|
||||
project: 'project',
|
||||
group: 'group',
|
||||
};
|
||||
|
|
|
@ -1,5 +1,12 @@
|
|||
<script>
|
||||
import { GlTooltipDirective, GlLink, GlButton, GlButtonGroup, GlLoadingIcon } from '@gitlab/ui';
|
||||
import {
|
||||
GlTooltipDirective,
|
||||
GlLink,
|
||||
GlButton,
|
||||
GlButtonGroup,
|
||||
GlLoadingIcon,
|
||||
GlSafeHtmlDirective,
|
||||
} from '@gitlab/ui';
|
||||
import defaultAvatarUrl from 'images/no_avatar.png';
|
||||
import pathLastCommitQuery from 'shared_queries/repository/path_last_commit.query.graphql';
|
||||
import { sprintf, s__ } from '~/locale';
|
||||
|
@ -23,6 +30,7 @@ export default {
|
|||
},
|
||||
directives: {
|
||||
GlTooltip: GlTooltipDirective,
|
||||
SafeHtml: GlSafeHtmlDirective,
|
||||
},
|
||||
mixins: [getRefMixin],
|
||||
apollo: {
|
||||
|
@ -96,6 +104,9 @@ export default {
|
|||
},
|
||||
},
|
||||
defaultAvatarUrl,
|
||||
safeHtmlConfig: {
|
||||
ADD_TAGS: ['gl-emoji'],
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
|
@ -121,10 +132,10 @@ export default {
|
|||
<div class="commit-detail flex-list">
|
||||
<div class="commit-content qa-commit-content">
|
||||
<gl-link
|
||||
v-safe-html:[$options.safeHtmlConfig]="commit.titleHtml"
|
||||
:href="commit.webPath"
|
||||
:class="{ 'font-italic': !commit.message }"
|
||||
class="commit-row-message item-title"
|
||||
v-html="commit.titleHtml /* eslint-disable-line vue/no-v-html */"
|
||||
/>
|
||||
<gl-button
|
||||
v-if="commit.descriptionHtml"
|
||||
|
@ -150,15 +161,15 @@ export default {
|
|||
</div>
|
||||
<pre
|
||||
v-if="commitDescription"
|
||||
v-safe-html:[$options.safeHtmlConfig]="commitDescription"
|
||||
:class="{ 'd-block': showDescription }"
|
||||
class="commit-row-description gl-mb-3"
|
||||
v-html="commitDescription /* eslint-disable-line vue/no-v-html */"
|
||||
></pre>
|
||||
</div>
|
||||
<div class="commit-actions flex-row">
|
||||
<div
|
||||
v-if="commit.signatureHtml"
|
||||
v-html="commit.signatureHtml /* eslint-disable-line vue/no-v-html */"
|
||||
v-safe-html:[$options.safeHtmlConfig]="commit.signatureHtml"
|
||||
></div>
|
||||
<div v-if="commit.pipeline" class="ci-status-link">
|
||||
<gl-link
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
import $ from 'jquery';
|
||||
import Cookies from 'js-cookie';
|
||||
import { hide, fixTitle } from '~/tooltips';
|
||||
import { DEBOUNCE_DROPDOWN_DELAY } from '~/vue_shared/components/sidebar/labels_select_widget/constants';
|
||||
import createFlash from './flash';
|
||||
import axios from './lib/utils/axios_utils';
|
||||
import { sprintf, s__, __ } from './locale';
|
||||
|
@ -130,8 +131,10 @@ Sidebar.prototype.openDropdown = function (blockOrName) {
|
|||
// Wait for the sidebar to trigger('click') open
|
||||
// so it doesn't cause our dropdown to close preemptively
|
||||
setTimeout(() => {
|
||||
$block.find('.js-sidebar-dropdown-toggle').trigger('click');
|
||||
});
|
||||
if (!gon.features?.labelsWidget && !$block.hasClass('labels-select-wrapper')) {
|
||||
$block.find('.js-sidebar-dropdown-toggle').trigger('click');
|
||||
}
|
||||
}, DEBOUNCE_DROPDOWN_DELAY);
|
||||
};
|
||||
|
||||
Sidebar.prototype.setCollapseAfterUpdate = function ($block) {
|
||||
|
|
|
@ -158,8 +158,9 @@ export default {
|
|||
:labels-filter-base-path="projectIssuesPath"
|
||||
:variant="$options.variant"
|
||||
:issuable-type="issuableType"
|
||||
workspace-type="project"
|
||||
:attr-workspace-path="fullPath"
|
||||
:label-type="LabelType.project"
|
||||
:label-create-type="LabelType.project"
|
||||
data-qa-selector="labels_block"
|
||||
>
|
||||
{{ __('None') }}
|
||||
|
|
|
@ -134,7 +134,7 @@ export default {
|
|||
v-if="canUpdate && !initialLoading && canEdit"
|
||||
category="tertiary"
|
||||
size="small"
|
||||
class="gl-text-gray-900! gl-ml-auto hide-collapsed gl-mr-n2"
|
||||
class="gl-text-gray-900! gl-ml-auto hide-collapsed gl-mr-n2 shortcut-sidebar-dropdown-toggle"
|
||||
data-testid="edit-button"
|
||||
:data-track-action="tracking.event"
|
||||
:data-track-label="tracking.label"
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import updateIssueLabelsMutation from '~/boards/graphql/issue_set_labels.mutation.graphql';
|
||||
import { IssuableType } from '~/issue_show/constants';
|
||||
import { IssuableType, WorkspaceType } from '~/issue_show/constants';
|
||||
import { DEFAULT_DEBOUNCE_AND_THROTTLE_MS } from '~/lib/utils/constants';
|
||||
import epicConfidentialQuery from '~/sidebar/queries/epic_confidential.query.graphql';
|
||||
import epicDueDateQuery from '~/sidebar/queries/epic_due_date.query.graphql';
|
||||
|
@ -34,6 +34,7 @@ import updateMergeRequestLabelsMutation from '~/sidebar/queries/update_merge_req
|
|||
import updateMergeRequestSubscriptionMutation from '~/sidebar/queries/update_merge_request_subscription.mutation.graphql';
|
||||
import updateAlertAssigneesMutation from '~/vue_shared/alert_details/graphql/mutations/alert_set_assignees.mutation.graphql';
|
||||
import epicLabelsQuery from '~/vue_shared/components/sidebar/labels_select_widget/graphql/epic_labels.query.graphql';
|
||||
import updateEpicLabelsMutation from '~/vue_shared/components/sidebar/labels_select_widget/graphql/epic_update_labels.mutation.graphql';
|
||||
import groupLabelsQuery from '~/vue_shared/components/sidebar/labels_select_widget/graphql/group_labels.query.graphql';
|
||||
import issueLabelsQuery from '~/vue_shared/components/sidebar/labels_select_widget/graphql/issue_labels.query.graphql';
|
||||
import projectLabelsQuery from '~/vue_shared/components/sidebar/labels_select_widget/graphql/project_labels.query.graphql';
|
||||
|
@ -111,19 +112,18 @@ export const referenceQueries = {
|
|||
},
|
||||
};
|
||||
|
||||
export const labelsQueries = {
|
||||
[IssuableType.Issue]: {
|
||||
issuableQuery: issueLabelsQuery,
|
||||
workspaceQuery: projectLabelsQuery,
|
||||
export const workspaceLabelsQueries = {
|
||||
[WorkspaceType.project]: {
|
||||
query: projectLabelsQuery,
|
||||
},
|
||||
[IssuableType.Epic]: {
|
||||
issuableQuery: epicLabelsQuery,
|
||||
workspaceQuery: groupLabelsQuery,
|
||||
[WorkspaceType.group]: {
|
||||
query: groupLabelsQuery,
|
||||
},
|
||||
};
|
||||
|
||||
export const labelsMutations = {
|
||||
export const issuableLabelsQueries = {
|
||||
[IssuableType.Issue]: {
|
||||
issuableQuery: issueLabelsQuery,
|
||||
mutation: updateIssueLabelsMutation,
|
||||
mutationName: 'updateIssue',
|
||||
},
|
||||
|
@ -131,6 +131,11 @@ export const labelsMutations = {
|
|||
mutation: updateMergeRequestLabelsMutation,
|
||||
mutationName: 'mergeRequestSetLabels',
|
||||
},
|
||||
[IssuableType.Epic]: {
|
||||
issuableQuery: epicLabelsQuery,
|
||||
mutation: updateEpicLabelsMutation,
|
||||
mutationName: 'updateEpic',
|
||||
},
|
||||
};
|
||||
|
||||
export const dateTypes = {
|
||||
|
|
|
@ -36,6 +36,7 @@ export default {
|
|||
<template>
|
||||
<div
|
||||
class="labels-select-dropdown-contents gl-w-full gl-my-2 gl-py-3 gl-rounded-base gl-absolute"
|
||||
data-testid="labels-select-dropdown-contents"
|
||||
data-qa-selector="labels_dropdown_content"
|
||||
:style="directionStyle"
|
||||
>
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
export const SCOPED_LABEL_DELIMITER = '::';
|
||||
export const DEBOUNCE_DROPDOWN_DELAY = 200;
|
||||
|
||||
export const DropdownVariant = {
|
||||
Sidebar: 'sidebar',
|
||||
|
@ -7,6 +8,6 @@ export const DropdownVariant = {
|
|||
};
|
||||
|
||||
export const LabelType = {
|
||||
group: 'GroupLabel',
|
||||
project: 'ProjectLabel',
|
||||
group: 'group',
|
||||
project: 'project',
|
||||
};
|
||||
|
|
|
@ -66,11 +66,15 @@ export default {
|
|||
type: String,
|
||||
required: true,
|
||||
},
|
||||
workspaceType: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
attrWorkspacePath: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
labelType: {
|
||||
labelCreateType: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
|
@ -158,6 +162,7 @@ export default {
|
|||
this.$emit('setLabels', this.localSelectedLabels);
|
||||
},
|
||||
handleDropdownHide() {
|
||||
this.$emit('closeDropdown');
|
||||
if (!isDropdownVariantSidebar(this.variant)) {
|
||||
this.setLabels();
|
||||
}
|
||||
|
@ -168,6 +173,9 @@ export default {
|
|||
setFocus() {
|
||||
this.$refs.header.focusInput();
|
||||
},
|
||||
showDropdown() {
|
||||
this.$refs.dropdown.show();
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
@ -177,6 +185,7 @@ export default {
|
|||
ref="dropdown"
|
||||
:text="buttonText"
|
||||
class="gl-w-full gl-mt-2"
|
||||
data-testid="labels-select-dropdown-contents"
|
||||
data-qa-selector="labels_dropdown_content"
|
||||
@hide="handleDropdownHide"
|
||||
@shown="setFocus"
|
||||
|
@ -202,9 +211,10 @@ export default {
|
|||
:allow-multiselect="allowMultiselect"
|
||||
:issuable-type="issuableType"
|
||||
:full-path="fullPath"
|
||||
:workspace-type="workspaceType"
|
||||
:attr-workspace-path="attrWorkspacePath"
|
||||
:label-type="labelType"
|
||||
@hideCreateView="toggleDropdownContentsCreateView"
|
||||
:label-create-type="labelCreateType"
|
||||
@hideCreateView="toggleDropdownContent"
|
||||
/>
|
||||
</template>
|
||||
<template #footer>
|
||||
|
|
|
@ -3,7 +3,7 @@ import { GlTooltipDirective, GlButton, GlFormInput, GlLink, GlLoadingIcon } from
|
|||
import produce from 'immer';
|
||||
import createFlash from '~/flash';
|
||||
import { __ } from '~/locale';
|
||||
import { labelsQueries } from '~/sidebar/constants';
|
||||
import { workspaceLabelsQueries } from '~/sidebar/constants';
|
||||
import createLabelMutation from './graphql/create_label.mutation.graphql';
|
||||
import { LabelType } from './constants';
|
||||
|
||||
|
@ -20,10 +20,6 @@ export default {
|
|||
GlTooltip: GlTooltipDirective,
|
||||
},
|
||||
props: {
|
||||
issuableType: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
fullPath: {
|
||||
type: String,
|
||||
required: true,
|
||||
|
@ -32,7 +28,16 @@ export default {
|
|||
type: String,
|
||||
required: true,
|
||||
},
|
||||
labelType: {
|
||||
labelCreateType: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
issuableType: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: undefined,
|
||||
},
|
||||
workspaceType: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
|
@ -53,7 +58,7 @@ export default {
|
|||
return Object.keys(colorsMap).map((color) => ({ [color]: colorsMap[color] }));
|
||||
},
|
||||
mutationVariables() {
|
||||
const attributePath = this.labelType === LabelType.group ? 'groupPath' : 'projectPath';
|
||||
const attributePath = this.labelCreateType === LabelType.group ? 'groupPath' : 'projectPath';
|
||||
|
||||
return {
|
||||
title: this.labelTitle,
|
||||
|
@ -73,8 +78,10 @@ export default {
|
|||
this.selectedColor = this.getColorCode(color);
|
||||
},
|
||||
updateLabelsInCache(store, label) {
|
||||
const { query } = workspaceLabelsQueries[this.workspaceType];
|
||||
|
||||
const sourceData = store.readQuery({
|
||||
query: labelsQueries[this.issuableType].workspaceQuery,
|
||||
query,
|
||||
variables: { fullPath: this.fullPath, searchTerm: '' },
|
||||
});
|
||||
|
||||
|
@ -86,7 +93,7 @@ export default {
|
|||
});
|
||||
|
||||
store.writeQuery({
|
||||
query: labelsQueries[this.issuableType].workspaceQuery,
|
||||
query,
|
||||
variables: { fullPath: this.fullPath, searchTerm: '' },
|
||||
data,
|
||||
});
|
||||
|
@ -171,7 +178,7 @@ export default {
|
|||
<gl-button
|
||||
class="js-btn-cancel-create"
|
||||
data-testid="cancel-button"
|
||||
@click="$emit('hideCreateView')"
|
||||
@click.stop="$emit('hideCreateView')"
|
||||
>
|
||||
{{ __('Cancel') }}
|
||||
</gl-button>
|
||||
|
|
|
@ -4,7 +4,7 @@ import fuzzaldrinPlus from 'fuzzaldrin-plus';
|
|||
import createFlash from '~/flash';
|
||||
import { getIdFromGraphQLId } from '~/graphql_shared/utils';
|
||||
import { __ } from '~/locale';
|
||||
import { labelsQueries } from '~/sidebar/constants';
|
||||
import { workspaceLabelsQueries } from '~/sidebar/constants';
|
||||
import LabelItem from './label_item.vue';
|
||||
|
||||
export default {
|
||||
|
@ -39,6 +39,10 @@ export default {
|
|||
type: String,
|
||||
required: true,
|
||||
},
|
||||
workspaceType: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
@ -49,7 +53,7 @@ export default {
|
|||
apollo: {
|
||||
labels: {
|
||||
query() {
|
||||
return labelsQueries[this.issuableType].workspaceQuery;
|
||||
return workspaceLabelsQueries[this.workspaceType].query;
|
||||
},
|
||||
variables() {
|
||||
return {
|
||||
|
|
|
@ -1,12 +1,11 @@
|
|||
#import "~/graphql_shared/fragments/label.fragment.graphql"
|
||||
|
||||
mutation createLabel($title: String!, $color: String, $projectPath: ID, $groupPath: ID) {
|
||||
labelCreate(
|
||||
input: { title: $title, color: $color, projectPath: $projectPath, groupPath: $groupPath }
|
||||
) {
|
||||
label {
|
||||
id
|
||||
color
|
||||
description
|
||||
title
|
||||
...Label
|
||||
}
|
||||
errors
|
||||
}
|
||||
|
|
|
@ -1,13 +1,12 @@
|
|||
#import "~/graphql_shared/fragments/label.fragment.graphql"
|
||||
|
||||
query epicLabels($fullPath: ID!, $iid: ID) {
|
||||
workspace: group(fullPath: $fullPath) {
|
||||
issuable: epic(iid: $iid) {
|
||||
id
|
||||
labels {
|
||||
nodes {
|
||||
id
|
||||
title
|
||||
color
|
||||
description
|
||||
...Label
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,15 @@
|
|||
#import "~/graphql_shared/fragments/label.fragment.graphql"
|
||||
|
||||
mutation updateEpic($input: UpdateEpicInput!) {
|
||||
updateEpic(input: $input) {
|
||||
epic {
|
||||
id
|
||||
labels {
|
||||
nodes {
|
||||
...Label
|
||||
}
|
||||
}
|
||||
}
|
||||
errors
|
||||
}
|
||||
}
|
|
@ -1,11 +1,10 @@
|
|||
#import "~/graphql_shared/fragments/label.fragment.graphql"
|
||||
|
||||
query groupLabels($fullPath: ID!, $searchTerm: String) {
|
||||
workspace: group(fullPath: $fullPath) {
|
||||
labels(searchTerm: $searchTerm, onlyGroupLabels: true) {
|
||||
nodes {
|
||||
id
|
||||
title
|
||||
color
|
||||
description
|
||||
...Label
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,13 +1,12 @@
|
|||
#import "~/graphql_shared/fragments/label.fragment.graphql"
|
||||
|
||||
query issueLabels($fullPath: ID!, $iid: String) {
|
||||
workspace: project(fullPath: $fullPath) {
|
||||
issuable: issue(iid: $iid) {
|
||||
id
|
||||
labels {
|
||||
nodes {
|
||||
id
|
||||
title
|
||||
color
|
||||
description
|
||||
...Label
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,11 +1,10 @@
|
|||
#import "~/graphql_shared/fragments/label.fragment.graphql"
|
||||
|
||||
query projectLabels($fullPath: ID!, $searchTerm: String) {
|
||||
workspace: project(fullPath: $fullPath) {
|
||||
labels(searchTerm: $searchTerm, includeAncestorGroups: true) {
|
||||
nodes {
|
||||
id
|
||||
title
|
||||
color
|
||||
description
|
||||
...Label
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,11 +1,12 @@
|
|||
<script>
|
||||
import { MutationOperationMode } from '~/graphql_shared/utils';
|
||||
import { debounce } from 'lodash';
|
||||
import { MutationOperationMode, getIdFromGraphQLId } from '~/graphql_shared/utils';
|
||||
import createFlash from '~/flash';
|
||||
import { IssuableType } from '~/issue_show/constants';
|
||||
import { __ } from '~/locale';
|
||||
import SidebarEditableItem from '~/sidebar/components/sidebar_editable_item.vue';
|
||||
import { labelsQueries, labelsMutations } from '~/sidebar/constants';
|
||||
import { DropdownVariant } from './constants';
|
||||
import { issuableLabelsQueries } from '~/sidebar/constants';
|
||||
import { DEBOUNCE_DROPDOWN_DELAY, DropdownVariant } from './constants';
|
||||
import DropdownContents from './dropdown_contents.vue';
|
||||
import DropdownValue from './dropdown_value.vue';
|
||||
import DropdownValueCollapsed from './dropdown_value_collapsed.vue';
|
||||
|
@ -91,11 +92,15 @@ export default {
|
|||
type: String,
|
||||
required: true,
|
||||
},
|
||||
workspaceType: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
attrWorkspacePath: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
labelType: {
|
||||
labelCreateType: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
|
@ -106,17 +111,21 @@ export default {
|
|||
issuableLabels: [],
|
||||
labelsSelectInProgress: false,
|
||||
oldIid: null,
|
||||
sidebarExpandedOnClick: false,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
isLoading() {
|
||||
return this.labelsSelectInProgress || this.$apollo.queries.issuableLabels.loading;
|
||||
},
|
||||
issuableLabelIds() {
|
||||
return this.issuableLabels.map((label) => label.id);
|
||||
},
|
||||
},
|
||||
apollo: {
|
||||
issuableLabels: {
|
||||
query() {
|
||||
return labelsQueries[this.issuableType].issuableQuery;
|
||||
return issuableLabelsQueries[this.issuableType].issuableQuery;
|
||||
},
|
||||
skip() {
|
||||
return !isDropdownVariantSidebar(this.variant);
|
||||
|
@ -140,6 +149,15 @@ export default {
|
|||
this.oldIid = oldVal;
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
document.addEventListener('toggleSidebarRevealLabelsDropdown', this.handleCollapsedValueClick);
|
||||
},
|
||||
beforeDestroy() {
|
||||
document.removeEventListener(
|
||||
'toggleSidebarRevealLabelsDropdown',
|
||||
this.handleCollapsedValueClick,
|
||||
);
|
||||
},
|
||||
methods: {
|
||||
handleDropdownClose(labels) {
|
||||
if (this.iid !== '') {
|
||||
|
@ -152,9 +170,18 @@ export default {
|
|||
},
|
||||
collapseEditableItem() {
|
||||
this.$refs.editable?.collapse();
|
||||
if (this.sidebarExpandedOnClick) {
|
||||
this.sidebarExpandedOnClick = false;
|
||||
this.$emit('toggleCollapse');
|
||||
}
|
||||
},
|
||||
handleCollapsedValueClick() {
|
||||
this.sidebarExpandedOnClick = true;
|
||||
this.$emit('toggleCollapse');
|
||||
debounce(() => {
|
||||
this.$refs.editable.toggle();
|
||||
this.$refs.dropdownContents.showDropdown();
|
||||
}, DEBOUNCE_DROPDOWN_DELAY)();
|
||||
},
|
||||
getUpdateVariables(labels) {
|
||||
let labelIds = [];
|
||||
|
@ -172,8 +199,19 @@ export default {
|
|||
case IssuableType.Issue:
|
||||
return updateVariables;
|
||||
case IssuableType.MergeRequest:
|
||||
updateVariables.operationMode = MutationOperationMode.Replace;
|
||||
return updateVariables;
|
||||
return {
|
||||
...updateVariables,
|
||||
operationMode: MutationOperationMode.Replace,
|
||||
};
|
||||
case IssuableType.Epic:
|
||||
return {
|
||||
iid: currentIid,
|
||||
groupPath: this.fullPath,
|
||||
addLabelIds: labelIds,
|
||||
removeLabelIds: this.issuableLabelIds
|
||||
.filter((id) => !labelIds.includes(id))
|
||||
.map((id) => getIdFromGraphQLId(id)),
|
||||
};
|
||||
default:
|
||||
return {};
|
||||
}
|
||||
|
@ -183,11 +221,11 @@ export default {
|
|||
|
||||
this.$apollo
|
||||
.mutate({
|
||||
mutation: labelsMutations[this.issuableType].mutation,
|
||||
mutation: issuableLabelsQueries[this.issuableType].mutation,
|
||||
variables: { input: inputVariables },
|
||||
})
|
||||
.then(({ data }) => {
|
||||
const { mutationName } = labelsMutations[this.issuableType];
|
||||
const { mutationName } = issuableLabelsQueries[this.issuableType];
|
||||
|
||||
if (data[mutationName]?.errors?.length) {
|
||||
throw new Error();
|
||||
|
@ -227,6 +265,12 @@ export default {
|
|||
labelIds: [labelId],
|
||||
operationMode: MutationOperationMode.Remove,
|
||||
};
|
||||
case IssuableType.Epic:
|
||||
return {
|
||||
iid: this.iid,
|
||||
removeLabelIds: [labelId],
|
||||
groupPath: this.fullPath,
|
||||
};
|
||||
default:
|
||||
return {};
|
||||
}
|
||||
|
@ -288,6 +332,7 @@ export default {
|
|||
<slot></slot>
|
||||
</dropdown-value>
|
||||
<dropdown-contents
|
||||
ref="dropdownContents"
|
||||
:dropdown-button-text="dropdownButtonText"
|
||||
:allow-multiselect="allowMultiselect"
|
||||
:labels-list-title="labelsListTitle"
|
||||
|
@ -299,8 +344,9 @@ export default {
|
|||
:issuable-type="issuableType"
|
||||
:is-visible="edit"
|
||||
:full-path="fullPath"
|
||||
:workspace-type="workspaceType"
|
||||
:attr-workspace-path="attrWorkspacePath"
|
||||
:label-type="labelType"
|
||||
:label-create-type="labelCreateType"
|
||||
@setLabels="handleDropdownClose"
|
||||
@closeDropdown="collapseEditableItem"
|
||||
/>
|
||||
|
@ -320,8 +366,9 @@ export default {
|
|||
:variant="variant"
|
||||
:issuable-type="issuableType"
|
||||
:full-path="fullPath"
|
||||
:workspace-type="workspaceType"
|
||||
:attr-workspace-path="attrWorkspacePath"
|
||||
:label-type="labelType"
|
||||
:label-create-type="labelCreateType"
|
||||
@setLabels="handleDropdownClose"
|
||||
/>
|
||||
</div>
|
||||
|
|
|
@ -43,9 +43,13 @@ module Emails
|
|||
|
||||
user = User.find(user_id)
|
||||
|
||||
@source_hidden = !member_source.readable_by?(user)
|
||||
|
||||
human_name = @source_hidden ? 'Hidden' : member_source.human_name
|
||||
|
||||
member_email_with_layout(
|
||||
to: user.notification_email_for(notification_group),
|
||||
subject: subject("Access to the #{member_source.human_name} #{member_source.model_name.singular} was denied"))
|
||||
subject: subject("Access to the #{human_name} #{member_source.model_name.singular} was denied"))
|
||||
end
|
||||
|
||||
def member_invited_email(member_source_type, member_id, token)
|
||||
|
|
|
@ -39,6 +39,7 @@ module Ci
|
|||
|
||||
ignore_column :build_id_convert_to_bigint, remove_with: '14.5', remove_after: '2021-10-22'
|
||||
ignore_columns :id_convert_to_bigint, remove_with: '14.5', remove_after: '2021-10-22'
|
||||
ignore_columns :runner_features, remove_with: '14.7', remove_after: '2021-11-22'
|
||||
|
||||
def update_timeout_state
|
||||
timeout = timeout_with_highest_precedence
|
||||
|
|
|
@ -20,7 +20,6 @@ module Ci
|
|||
delegate :interruptible, to: :metadata, prefix: false, allow_nil: true
|
||||
delegate :has_exposed_artifacts?, to: :metadata, prefix: false, allow_nil: true
|
||||
delegate :environment_auto_stop_in, to: :metadata, prefix: false, allow_nil: true
|
||||
delegate :runner_features, to: :metadata, prefix: false, allow_nil: false
|
||||
before_create :ensure_metadata
|
||||
end
|
||||
|
||||
|
|
|
@ -2,6 +2,11 @@
|
|||
%td.text-content
|
||||
%p
|
||||
Your request to join the
|
||||
#{link_to member_source.human_name, member_source.web_url, class: :highlight} #{member_source.model_name.singular}
|
||||
has been #{content_tag :span, 'denied', class: :highlight}.
|
||||
|
||||
- if @source_hidden
|
||||
#{content_tag :span, 'Hidden', class: :highlight}
|
||||
- else
|
||||
#{link_to member_source.human_name, member_source.web_url, class: :highlight}
|
||||
|
||||
#{member_source.model_name.singular} has been #{content_tag :span, 'denied', class: :highlight}.
|
||||
|
||||
|
|
|
@ -21,7 +21,7 @@ module Ci
|
|||
# Therefore, we can deduplicate the sidekiq jobs until the on-going
|
||||
# assignment process has been finished.
|
||||
idempotent!
|
||||
deduplicate :until_executed
|
||||
deduplicate :until_executed, if_deduplicated: :reschedule_once
|
||||
|
||||
def perform(resource_group_id)
|
||||
::Ci::ResourceGroup.find_by_id(resource_group_id).try do |resource_group|
|
||||
|
|
|
@ -49,7 +49,7 @@ Rails.application.configure do
|
|||
# Print deprecation notices to the stderr
|
||||
config.active_support.deprecation = :stderr
|
||||
|
||||
config.eager_load = Gitlab::Utils.to_boolean(ENV['GITLAB_TEST_EAGER_LOAD'], default: true)
|
||||
config.eager_load = Gitlab::Utils.to_boolean(ENV['GITLAB_TEST_EAGER_LOAD'], default: ENV['CI'].present?)
|
||||
|
||||
config.cache_store = :null_store
|
||||
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class AddRuntimeRunnerFeaturesToCiBuildsMetadata < Gitlab::Database::Migration[1.0]
|
||||
enable_lock_retries!
|
||||
|
||||
def change
|
||||
add_column :ci_builds_metadata, :runtime_runner_features, :jsonb, default: {}, null: false
|
||||
end
|
||||
end
|
|
@ -0,0 +1,26 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class CreateIndexSecurityCiBuildsOnNameAndIdParserFeatures < Gitlab::Database::Migration[1.0]
|
||||
TABLE = "ci_builds"
|
||||
COLUMNS = %i[name id]
|
||||
INDEX_NAME = "index_security_ci_builds_on_name_and_id_parser_features"
|
||||
CONSTRAINTS = "(name::text = ANY (ARRAY['container_scanning'::character varying::text,
|
||||
'dast'::character varying::text,
|
||||
'dependency_scanning'::character varying::text,
|
||||
'license_management'::character varying::text,
|
||||
'sast'::character varying::text,
|
||||
'secret_detection'::character varying::text,
|
||||
'coverage_fuzzing'::character varying::text,
|
||||
'license_scanning'::character varying::text])
|
||||
) AND type::text = 'Ci::Build'::text"
|
||||
|
||||
disable_ddl_transaction!
|
||||
|
||||
def up
|
||||
add_concurrent_index(TABLE, COLUMNS, name: INDEX_NAME, where: CONSTRAINTS)
|
||||
end
|
||||
|
||||
def down
|
||||
remove_concurrent_index(TABLE, COLUMNS, name: INDEX_NAME, where: CONSTRAINTS)
|
||||
end
|
||||
end
|
|
@ -0,0 +1 @@
|
|||
933c37a1a44869588b6586d34dec6bc8d731c81b6e5889ce588d535c011b9340
|
|
@ -0,0 +1 @@
|
|||
97e8b2ce324594581ec0af65840a0dde8271b1b2712e22059f5c26b30d7d5cac
|
|
@ -11428,7 +11428,8 @@ CREATE TABLE ci_builds_metadata (
|
|||
secrets jsonb DEFAULT '{}'::jsonb NOT NULL,
|
||||
build_id bigint NOT NULL,
|
||||
id bigint NOT NULL,
|
||||
runner_features jsonb DEFAULT '{}'::jsonb NOT NULL
|
||||
runner_features jsonb DEFAULT '{}'::jsonb NOT NULL,
|
||||
runtime_runner_features jsonb DEFAULT '{}'::jsonb NOT NULL
|
||||
);
|
||||
|
||||
CREATE SEQUENCE ci_builds_metadata_id_seq
|
||||
|
@ -26561,6 +26562,8 @@ CREATE UNIQUE INDEX index_scim_oauth_access_tokens_on_group_id_and_token_encrypt
|
|||
|
||||
CREATE INDEX index_secure_ci_builds_on_user_id_name_created_at ON ci_builds USING btree (user_id, name, created_at) WHERE (((type)::text = 'Ci::Build'::text) AND ((name)::text = ANY (ARRAY[('container_scanning'::character varying)::text, ('dast'::character varying)::text, ('dependency_scanning'::character varying)::text, ('license_management'::character varying)::text, ('license_scanning'::character varying)::text, ('sast'::character varying)::text, ('coverage_fuzzing'::character varying)::text, ('apifuzzer_fuzz'::character varying)::text, ('apifuzzer_fuzz_dnd'::character varying)::text, ('secret_detection'::character varying)::text])));
|
||||
|
||||
CREATE INDEX index_security_ci_builds_on_name_and_id_parser_features ON ci_builds USING btree (name, id) WHERE (((name)::text = ANY (ARRAY[('container_scanning'::character varying)::text, ('dast'::character varying)::text, ('dependency_scanning'::character varying)::text, ('license_management'::character varying)::text, ('sast'::character varying)::text, ('secret_detection'::character varying)::text, ('coverage_fuzzing'::character varying)::text, ('license_scanning'::character varying)::text])) AND ((type)::text = 'Ci::Build'::text));
|
||||
|
||||
CREATE INDEX index_security_ci_builds_on_name_and_id_parser_features_broken ON ci_builds USING btree (name, id) WHERE (((name)::text = ANY (ARRAY[('container_scanning'::character varying)::text, ('dast'::character varying)::text, ('dependency_scanning'::character varying)::text, ('license_management'::character varying)::text, ('sast'::character varying)::text, ('secret_detection'::character varying)::text, ('coverage_fuzzing'::character varying)::text, ('license_scanning'::character varying)::text])) AND ((type)::text = 'Ci::Build'::text));
|
||||
|
||||
CREATE INDEX index_security_findings_on_confidence ON security_findings USING btree (confidence);
|
||||
|
|
|
@ -259,7 +259,7 @@ test:
|
|||
| `name` | yes, when used with any other option | 9.4 | Full name of the image to use. If the full image name includes a registry hostname, use the `alias` option to define a shorter service access name. For more information, see [Accessing the services](#accessing-the-services). |
|
||||
| `entrypoint` | no | 9.4 |Command or script to execute as the container's entrypoint. It's translated to Docker's `--entrypoint` option while creating the container. The syntax is similar to [`Dockerfile`'s `ENTRYPOINT`](https://docs.docker.com/engine/reference/builder/#entrypoint) directive, where each shell token is a separate string in the array. |
|
||||
| `command` | no | 9.4 |Command or script that should be used as the container's command. It's translated to arguments passed to Docker after the image's name. The syntax is similar to [`Dockerfile`'s `CMD`](https://docs.docker.com/engine/reference/builder/#cmd) directive, where each shell token is a separate string in the array. |
|
||||
| `alias` (1) | no | 9.4 |Additional alias that can be used to access the service from the job's container. Read [Accessing the services](#accessing-the-services) for more information. |
|
||||
| `alias` (1) | no | 9.4 | Additional alias that can be used to access the service from the job's container. Read [Accessing the services](#accessing-the-services) for more information. |
|
||||
| `variables` | no | 14.5 | Additional environment variables that are passed exclusively to the service. The syntax is the same as [Job Variables](../variables/index.md). |
|
||||
|
||||
(1) Alias support for the Kubernetes executor was [introduced](https://gitlab.com/gitlab-org/gitlab-runner/-/issues/2229) in GitLab Runner 12.8, and is only available for Kubernetes version 1.7 or later.
|
||||
|
|
|
@ -734,37 +734,50 @@ image:
|
|||
|
||||
#### `services`
|
||||
|
||||
Use `services` to specify a [service Docker image](../services/index.md), linked to a base image specified in [`image`](#image).
|
||||
Use `services` to specify an additional Docker image to run scripts in. The [`services` image](../services/index.md) is linked
|
||||
to the image specified in the [`image`](#image) keyword.
|
||||
|
||||
For:
|
||||
**Keyword type**: Job keyword. You can use it only as part of a job or in the
|
||||
[`default:` section](#custom-default-keyword-values).
|
||||
|
||||
- Usage examples, see [Define `services` in the `.gitlab-ci.yml` file](../services/index.md#define-services-in-the-gitlab-ciyml-file).
|
||||
- Detailed usage information, refer to [Docker integration](../docker/index.md) documentation.
|
||||
- Example services, see [GitLab CI/CD Services](../services/index.md).
|
||||
**Possible inputs**: The name of the services image, including the registry path if needed, in one of these formats:
|
||||
|
||||
##### `services:name`
|
||||
- `<image-name>` (Same as using `<image-name>` with the `latest` tag)
|
||||
- `<image-name>:<tag>`
|
||||
- `<image-name>@<digest>`
|
||||
|
||||
An [extended Docker configuration option](../docker/using_docker_images.md#extended-docker-configuration-options).
|
||||
**Example of `services`**:
|
||||
|
||||
For more information, see [Available settings for `services`](../services/index.md#available-settings-for-services).
|
||||
```yaml
|
||||
default:
|
||||
image:
|
||||
name: ruby:2.6
|
||||
entrypoint: ["/bin/bash"]
|
||||
|
||||
##### `services:alias`
|
||||
services:
|
||||
- name: my-postgres:11.7
|
||||
alias: db-postgres
|
||||
entrypoint: ["/usr/local/bin/db-postgres"]
|
||||
command: ["start"]
|
||||
|
||||
An [extended Docker configuration option](../docker/using_docker_images.md#extended-docker-configuration-options).
|
||||
before_script:
|
||||
- bundle install
|
||||
|
||||
For more information, see [Available settings for `services`](../services/index.md#available-settings-for-services).
|
||||
test:
|
||||
script:
|
||||
- bundle exec rake spec
|
||||
```
|
||||
|
||||
##### `services:entrypoint`
|
||||
In this example, the job launches a Ruby container. Then, from that container, the job launches
|
||||
another container that's running PostgreSQL. Then the job then runs scripts
|
||||
in that container.
|
||||
|
||||
An [extended Docker configuration option](../docker/using_docker_images.md#extended-docker-configuration-options).
|
||||
**Related topics**:
|
||||
|
||||
For more information, see [Available settings for `services`](../services/index.md#available-settings-for-services).
|
||||
|
||||
##### `services:command`
|
||||
|
||||
An [extended Docker configuration option](../docker/using_docker_images.md#extended-docker-configuration-options).
|
||||
|
||||
For more information, see [Available settings for `services`](../services/index.md#available-settings-for-services).
|
||||
- [Available settings for `services`](../services/index.md#available-settings-for-services).
|
||||
- [Define `services` in the `.gitlab-ci.yml` file](../services/index.md#define-services-in-the-gitlab-ciyml-file).
|
||||
- [Run your CI/CD jobs in Docker containers](../docker/using_docker_images.md).
|
||||
- [Use Docker to build Docker images](../docker/using_docker_build.md).
|
||||
|
||||
### `script`
|
||||
|
||||
|
|
|
@ -592,3 +592,90 @@ In order to prevent this from happening, it is recommended to use the method `us
|
|||
forbidden!(api_access_denied_message(user))
|
||||
end
|
||||
```
|
||||
|
||||
## Guidelines when defining missing methods with metaprogramming
|
||||
|
||||
Metaprogramming is a way to define methods **at runtime**, instead of at the time of writing and deploying the code. It is a powerful tool, but can be dangerous if we allow untrusted actors (like users) to define their own arbitrary methods. For example, imagine we accidentally let an attacker overwrite an access control method to always return true! It can lead to many classes of vulnerabilities such as access control bypass, information disclosure, arbitrary file reads, and remote code execution.
|
||||
|
||||
Key methods to watch out for are `method_missing`, `define_method`, `delegate`, and similar methods.
|
||||
|
||||
### Insecure metaprogramming example
|
||||
|
||||
This example is adapted from an example submitted by [@jobert](https://hackerone.com/jobert?type=user) through our HackerOne bug bounty program.
|
||||
Thank you for your contribution!
|
||||
|
||||
Before Ruby 2.5.1, you could implement delegators using the `delegate` or `method_missing` methods. For example:
|
||||
|
||||
```ruby
|
||||
class User
|
||||
def initialize(attributes)
|
||||
@options = OpenStruct.new(attributes)
|
||||
end
|
||||
|
||||
def is_admin?
|
||||
name.eql?("Sid") # Note - never do this!
|
||||
end
|
||||
|
||||
def method_missing(method, *args)
|
||||
@options.send(method, *args)
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
When a method was called on a `User` instance that didn't exist, it passed it along to the `@options` instance variable.
|
||||
|
||||
```ruby
|
||||
User.new({name: "Jeeves"}).is_admin?
|
||||
# => false
|
||||
|
||||
User.new(name: "Sid").is_admin?
|
||||
# => true
|
||||
|
||||
User.new(name: "Jeeves", "is_admin?" => true).is_admin?
|
||||
# => false
|
||||
```
|
||||
|
||||
Because the `is_admin?` method is already defined on the class, its behavior is not overridden when passing `is_admin?` to the initializer.
|
||||
|
||||
This class can be refactored to use the `Forwardable` method and `def_delegators`:
|
||||
|
||||
```ruby
|
||||
class User
|
||||
extend Forwardable
|
||||
|
||||
def initialize(attributes)
|
||||
@options = OpenStruct.new(attributes)
|
||||
|
||||
self.class.instance_eval do
|
||||
def_delegators :@options, *attributes.keys
|
||||
end
|
||||
end
|
||||
|
||||
def is_admin?
|
||||
name.eql?("Sid") # Note - never do this!
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
It might seem like this example has the same behavior as the first code example. However, there's one crucial difference: **because the delegators are meta-programmed after the class is loaded, it can overwrite existing methods**:
|
||||
|
||||
```ruby
|
||||
User.new({name: "Jeeves"}).is_admin?
|
||||
# => false
|
||||
|
||||
User.new(name: "Sid").is_admin?
|
||||
# => true
|
||||
|
||||
User.new(name: "Jeeves", "is_admin?" => true).is_admin?
|
||||
# => true
|
||||
# ^------------------ The method is overwritten! Sneaky Jeeves!
|
||||
```
|
||||
|
||||
In the example above, the `is_admin?` method is overwritten when passing it to the initializer.
|
||||
|
||||
### Best practices
|
||||
|
||||
- Never pass user-provided details into method-defining metaprogramming methods.
|
||||
- If you must, be **very** confident that you've sanitized the values correctly.
|
||||
Consider creating an allowlist of values, and validating the user input against that.
|
||||
- When extending classes that use metaprogramming, make sure you don't inadvertently override any method definition safety checks.
|
||||
|
|
|
@ -50,6 +50,20 @@ bundle exec guard
|
|||
|
||||
When using spring and guard together, use `SPRING=1 bundle exec guard` instead to make use of spring.
|
||||
|
||||
### Eager loading the application code
|
||||
|
||||
By default, the application code:
|
||||
|
||||
- Isn't eagerly loaded in the `test` environment.
|
||||
- Is eagerly loaded in CI/CD (when `ENV['CI'].present?`) to surface any potential loading issues.
|
||||
|
||||
If you need to enable eager loading when executing tests,
|
||||
use the `GITLAB_TEST_EAGER_LOAD` environment variable:
|
||||
|
||||
```shell
|
||||
GITLAB_TEST_EAGER_LOAD=1 bin/rspec spec/models/project_spec.rb
|
||||
```
|
||||
|
||||
### Ruby warnings
|
||||
|
||||
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/47767) in GitLab 13.7.
|
||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 23 KiB |
|
@ -187,17 +187,22 @@ By default, the vulnerability report does not show vulnerabilities of `dismissed
|
|||
|
||||
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/9928) in GitLab 12.2.
|
||||
|
||||
You can implement merge request approvals to require approval by selected users or a group when a
|
||||
merge request would introduce one of the following security issues:
|
||||
You can enforce an additional approval for merge requests that would introduce one of the following
|
||||
security issues:
|
||||
|
||||
- A security vulnerability
|
||||
- A software license compliance violation
|
||||
- A security vulnerability. For more details, read
|
||||
[Vulnerability-Check rule](#vulnerability-check-rule).
|
||||
- A software license compliance violation. For more details, read
|
||||
[Enabling license approvals within a project](../compliance/license_compliance/index.md#enabling-license-approvals-within-a-project).
|
||||
|
||||
When the Vulnerability-Check merge request rule is enabled, additional merge request approval
|
||||
is required when the latest security report in a merge request:
|
||||
### Vulnerability-Check rule
|
||||
|
||||
- Contains vulnerabilities that are not present in the
|
||||
target branch. Note that approval is still required for dismissed vulnerabilities.
|
||||
To prevent a merge request introducing a security vulnerability in a project, enable the
|
||||
Vulnerability-Check rule. While this rule is enabled, an additional merge request approval is
|
||||
required when the latest security report in a merge request:
|
||||
|
||||
- Contains vulnerabilities that are not present in the target branch. Note that approval is still
|
||||
required for dismissed vulnerabilities.
|
||||
- Contains vulnerabilities with severity levels (for example, `high`, `critical`, or `unknown`)
|
||||
matching the rule's severity levels.
|
||||
- Contains a vulnerability count higher than the rule allows.
|
||||
|
@ -210,36 +215,22 @@ An approval is optional when the security report:
|
|||
the rule's severity levels.
|
||||
- Contains a vulnerability count equal to or less than what the rule allows.
|
||||
|
||||
When the License-Check merge request rule is enabled, additional approval is required if a merge
|
||||
request contains a denied license. For more details, see [Enabling license approvals within a project](../compliance/license_compliance/index.md#enabling-license-approvals-within-a-project).
|
||||
|
||||
### Enable the Vulnerability-Check rule
|
||||
#### Enable the Vulnerability-Check rule
|
||||
|
||||
Prerequisites:
|
||||
|
||||
- Maintainer or Owner [role](../permissions.md#project-members-permissions).
|
||||
|
||||
For this approval group, you must set the number of approvals required to greater than zero.
|
||||
|
||||
Follow these steps to enable `Vulnerability-Check`:
|
||||
To enable the `Vulnerability-Check` rule:
|
||||
|
||||
1. On the top bar, select **Menu > Projects** and find your project.
|
||||
1. On the left sidebar, select **Settings > General**.
|
||||
1. Expand **Merge request approvals**.
|
||||
1. Select **Enable** or **Edit**.
|
||||
1. Set the **Security scanners** that the rule applies to.
|
||||
1. Select the **Target branch**.
|
||||
1. Set the **Vulnerabilities allowed** to the number of vulnerabilities allowed before the rule is
|
||||
triggered.
|
||||
1. Set the **Severity levels** to the severity levels that the rule applies to.
|
||||
1. Set the **Approvals required** to the number of approvals that the rule requires.
|
||||
1. Select the users or groups to provide approval.
|
||||
1. Complete the fields. **Approvals required** must be at least 1.
|
||||
1. Select **Add approval rule**.
|
||||
|
||||
Once this group is added to your project, the approval rule is enabled for all merge requests.
|
||||
Any code changes cause the approvals required to reset.
|
||||
|
||||
![Vulnerability Check Approver Rule](img/vulnerability-check_v14_2.png)
|
||||
The approval rule is enabled for all merge requests. Any code changes reset the approvals required.
|
||||
|
||||
## Using private Maven repositories
|
||||
|
||||
|
@ -279,48 +270,43 @@ If you don't want scans running in your normal DevOps process you can use on-dem
|
|||
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/321918) in GitLab 13.11.
|
||||
> - Schema validation message [added](https://gitlab.com/gitlab-org/gitlab/-/issues/321730) in GitLab 14.0.
|
||||
|
||||
You can optionally enable validation of the security report artifacts based on the
|
||||
[report schemas](https://gitlab.com/gitlab-org/security-products/security-report-schemas/-/tree/master/dist).
|
||||
If you enable validation, GitLab validates the report artifacts before ingesting the vulnerabilities.
|
||||
This prevents ingestion of broken vulnerability data into the database.
|
||||
You can enforce validation of the security report artifacts before ingesting the vulnerabilities.
|
||||
This prevents ingestion of broken vulnerability data into the database. GitLab validates the
|
||||
artifacts based on the [report schemas](https://gitlab.com/gitlab-org/security-products/security-report-schemas/-/tree/master/dist).
|
||||
|
||||
In GitLab 14.0 and later, the pipeline's **Security** tab lists any report artifacts
|
||||
that failed validation. Security report validation must first be enabled.
|
||||
In GitLab 14.0 and later, when artifact validation is enabled, the pipeline's **Security** tab lists
|
||||
any report artifacts that failed validation.
|
||||
|
||||
### Enable security report validation
|
||||
|
||||
To enable report artifacts validation, set the `VALIDATE_SCHEMA` environment variable to `"true"` for the jobs in the `.gitlab-ci.yml` file.
|
||||
To enable report artifacts validation, set the `VALIDATE_SCHEMA` environment variable to `"true"`
|
||||
for the desired jobs in the `.gitlab-ci.yml` file.
|
||||
|
||||
For example, the configuration below enables validation for only the `sast` job:
|
||||
For example, to enable validation for only the `sast` job:
|
||||
|
||||
```yaml
|
||||
include:
|
||||
- template: Security/Dependency-Scanning.gitlab-ci.yml
|
||||
- template: Security/License-Scanning.gitlab-ci.yml
|
||||
- template: Security/SAST.gitlab-ci.yml
|
||||
- template: Security/Secret-Detection.gitlab-ci.yml
|
||||
```yaml
|
||||
include:
|
||||
- template: Security/Dependency-Scanning.gitlab-ci.yml
|
||||
- template: Security/License-Scanning.gitlab-ci.yml
|
||||
- template: Security/SAST.gitlab-ci.yml
|
||||
- template: Security/Secret-Detection.gitlab-ci.yml
|
||||
stages:
|
||||
- security-scan
|
||||
dependency_scanning:
|
||||
stage: security-scan
|
||||
license_scanning:
|
||||
stage: security-scan
|
||||
sast:
|
||||
stage: security-scan
|
||||
variables:
|
||||
VALIDATE_SCHEMA: "true"
|
||||
.secret-analyzer:
|
||||
stage: security-scan
|
||||
```
|
||||
|
||||
stages:
|
||||
- security-scan
|
||||
## Interact with findings and vulnerabilities
|
||||
|
||||
dependency_scanning:
|
||||
stage: security-scan
|
||||
|
||||
license_scanning:
|
||||
stage: security-scan
|
||||
|
||||
sast:
|
||||
stage: security-scan
|
||||
variables:
|
||||
VALIDATE_SCHEMA: "true"
|
||||
|
||||
.secret-analyzer:
|
||||
stage: security-scan
|
||||
```
|
||||
|
||||
## Interacting with findings and vulnerabilities
|
||||
|
||||
There are a variety of locations and ways to interact with the results of the security scanning tools:
|
||||
You can interact with the results of the security scanning tools in several locations:
|
||||
|
||||
- [Scan information in merge requests](#view-security-scan-information-in-merge-requests)
|
||||
- [Project Security Dashboard](security_dashboard/#project-security-dashboard)
|
||||
|
@ -331,7 +317,11 @@ There are a variety of locations and ways to interact with the results of the se
|
|||
- [Vulnerability Pages](vulnerabilities/index.md)
|
||||
- [Dependency List](dependency_list/index.md)
|
||||
|
||||
For more details about which findings or vulnerabilities you can view in each of those locations, select the respective link. Each page details the ways in which you can interact with the findings and vulnerabilities. As an example, in most cases findings start out as _detected_ status. You have the option to:
|
||||
For more details about which findings or vulnerabilities you can view in each of those locations,
|
||||
select the respective link. Each page details the ways in which you can interact with the findings
|
||||
and vulnerabilities. As an example, in most cases findings start out as _detected_ status.
|
||||
|
||||
You have the option to:
|
||||
|
||||
- Change the status.
|
||||
- Create an issue.
|
||||
|
@ -368,8 +358,8 @@ variables:
|
|||
|
||||
### Outdated security reports
|
||||
|
||||
When a security report generated for a merge request becomes outdated, the merge request shows a warning
|
||||
message in the security widget and prompts you to take an appropriate action.
|
||||
When a security report generated for a merge request becomes outdated, the merge request shows a
|
||||
warning message in the security widget and prompts you to take an appropriate action.
|
||||
|
||||
This can happen in two scenarios:
|
||||
|
||||
|
@ -378,18 +368,18 @@ This can happen in two scenarios:
|
|||
|
||||
#### Source branch is behind the target branch
|
||||
|
||||
This means the most recent common ancestor commit between the target branch and the source branch is
|
||||
not the most recent commit on the target branch. This is by far the most common situation.
|
||||
A security report can be out of date when the most recent common ancestor commit between the
|
||||
target branch and the source branch is not the most recent commit on the target branch.
|
||||
|
||||
In this case you must rebase or merge to incorporate the changes from the target branch.
|
||||
To fix this issue, rebase or merge to incorporate the changes from the target branch.
|
||||
|
||||
![Incorporate target branch changes](img/outdated_report_branch_v12_9.png)
|
||||
|
||||
#### Target branch security report is out of date
|
||||
|
||||
This can happen for many reasons, including failed jobs or new advisories. When the merge request shows that a
|
||||
security report is out of date, you must run a new pipeline on the target branch.
|
||||
You can do it quickly by following the hyperlink given to run a new pipeline.
|
||||
This can happen for many reasons, including failed jobs or new advisories. When the merge request
|
||||
shows that a security report is out of date, you must run a new pipeline on the target branch.
|
||||
Select **new pipeline** to run a new pipeline.
|
||||
|
||||
![Run a new pipeline](img/outdated_report_pipeline_v12_9.png)
|
||||
|
||||
|
@ -406,6 +396,7 @@ Found errors in your .gitlab-ci.yml:
|
|||
```
|
||||
|
||||
This error appears when the included job's stage (named `test`) isn't declared in `.gitlab-ci.yml`.
|
||||
|
||||
To fix this issue, you can either:
|
||||
|
||||
- Add a `test` stage in your `.gitlab-ci.yml`.
|
||||
|
@ -439,12 +430,11 @@ All the security scanning tools define their stage, so this error can occur with
|
|||
|
||||
### Getting warning messages `… report.json: no matching files`
|
||||
|
||||
This is often followed by the [error `No files to upload`](../../ci/pipelines/job_artifacts.md#error-message-no-files-to-upload),
|
||||
and preceded by other errors or warnings that indicate why the JSON report wasn't generated. Please
|
||||
check the entire job log for such messages. If you don't find these messages, retry the failed job
|
||||
after setting `SECURE_LOG_LEVEL: "debug"` as a
|
||||
[custom CI/CD variable](../../ci/variables/index.md#custom-cicd-variables).
|
||||
This provides useful information to investigate further.
|
||||
This message is often followed by the [error `No files to upload`](../../ci/pipelines/job_artifacts.md#error-message-no-files-to-upload),
|
||||
and preceded by other errors or warnings that indicate why the JSON report wasn't generated. Check
|
||||
the entire job log for such messages. If you don't find these messages, retry the failed job after
|
||||
setting `SECURE_LOG_LEVEL: "debug"` as a [custom CI/CD variable](../../ci/variables/index.md#custom-cicd-variables).
|
||||
This provides extra information to investigate further.
|
||||
|
||||
### Getting error message `sast job: config key may not be used with 'rules': only/except`
|
||||
|
||||
|
@ -542,23 +532,24 @@ involve pinning to the previous template versions, for example:
|
|||
```
|
||||
|
||||
Additionally, we provide a dedicated project containing the versioned legacy templates.
|
||||
This can be useful for offline setups or anyone wishing to use [Auto DevOps](../../topics/autodevops/index.md).
|
||||
This can be used for offline setups or anyone wishing to use [Auto DevOps](../../topics/autodevops/index.md).
|
||||
|
||||
Instructions are available in the [legacy template project](https://gitlab.com/gitlab-org/auto-devops-v12-10).
|
||||
|
||||
#### Vulnerabilities are found, but the job succeeds. How can I have a pipeline fail instead?
|
||||
|
||||
This is the current default behavior, because the job's status indicates success or failure of the analyzer itself.
|
||||
Analyzer results are displayed in the [job logs](../../ci/jobs/index.md#expand-and-collapse-job-log-sections),
|
||||
[Merge Request widget](#view-security-scan-information-in-merge-requests)
|
||||
or [Security Dashboard](security_dashboard/index.md).
|
||||
In these circumstances, that the job succeeds is the default behavior. The job's status indicates
|
||||
success or failure of the analyzer itself. Analyzer results are displayed in the
|
||||
[job logs](../../ci/jobs/index.md#expand-and-collapse-job-log-sections),
|
||||
[Merge Request widget](#view-security-scan-information-in-merge-requests) or
|
||||
[Security Dashboard](security_dashboard/index.md).
|
||||
|
||||
### Error: job `is used for configuration only, and its script should not be executed`
|
||||
|
||||
[Changes made in GitLab 13.4](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/41260)
|
||||
to the `Security/Dependency-Scanning.gitlab-ci.yml` and `Security/SAST.gitlab-ci.yml`
|
||||
templates mean that if you enable the `sast` or `dependency_scanning` jobs by setting the `rules` attribute,
|
||||
they will fail with the error `(job) is used for configuration only, and its script should not be executed`.
|
||||
they fail with the error `(job) is used for configuration only, and its script should not be executed`.
|
||||
|
||||
The `sast` or `dependency_scanning` stanzas can be used to make changes to all SAST or Dependency Scanning,
|
||||
such as changing `variables` or the `stage`, but they cannot be used to define shared `rules`.
|
||||
|
|
|
@ -309,7 +309,7 @@ in addition to the steps in the
|
|||
[Docker-in-Docker](../../../ci/docker/using_docker_build.md#use-the-docker-executor-with-the-docker-image-docker-in-docker) section:
|
||||
|
||||
1. Update the `image` and `service` to point to your registry.
|
||||
1. Add a service [alias](../../../ci/yaml/index.md#servicesalias).
|
||||
1. Add a service [alias](../../../ci/services/index.md#available-settings-for-services).
|
||||
|
||||
Below is an example of what your `.gitlab-ci.yml` should look like:
|
||||
|
||||
|
@ -339,7 +339,7 @@ in addition to the steps in the
|
|||
[Docker-in-Docker](../../../ci/docker/using_docker_build.md#use-the-docker-executor-with-the-docker-image-docker-in-docker) section:
|
||||
|
||||
1. Update the `image` and `service` to point to your registry.
|
||||
1. Add a service [alias](../../../ci/yaml/index.md#servicesalias).
|
||||
1. Add a service [alias](../../../ci/services/index.md#available-settings-for-services).
|
||||
|
||||
Below is an example of what your `.gitlab-ci.yml` should look like:
|
||||
|
||||
|
|
|
@ -1,54 +1,76 @@
|
|||
# To contribute improvements to CI/CD templates, please follow the Development guide at:
|
||||
# https://docs.gitlab.com/ee/development/cicd/templates.html
|
||||
# This specific template is located at:
|
||||
# https://gitlab.com/gitlab-org/gitlab/-/blob/master/lib/gitlab/ci/templates/Django.gitlab-ci.yml
|
||||
|
||||
# Official framework image. Look for the different tagged releases at:
|
||||
# https://hub.docker.com/r/library/python
|
||||
image: python:latest
|
||||
|
||||
# Pick zero or more services to be used on all builds.
|
||||
# Only needed when using a docker container to run your tests in.
|
||||
# Check out: http://docs.gitlab.com/ee/ci/docker/using_docker_images.html#what-is-a-service
|
||||
services:
|
||||
- mysql:latest
|
||||
- postgres:latest
|
||||
# This example is for testing Django with MySQL.
|
||||
#
|
||||
# The test CI/CD variables MYSQL_DB, MYSQL_USER and MYSQL_PASS can be set in the project settings at:
|
||||
# Settings --> CI/CD --> Variables
|
||||
#
|
||||
# The Django settings in settings.py, used in tests, might look similar to:
|
||||
#
|
||||
# DATABASES = {
|
||||
# 'default': {
|
||||
# 'ENGINE': 'django.db.backends.mysql',
|
||||
# 'NAME': os.environ.get('MYSQL_DATABASE'),
|
||||
# 'USER': os.environ.get('MYSQL_USER'),
|
||||
# 'PASSWORD': os.environ.get('MYSQL_PASSWORD'),
|
||||
# 'HOST': 'mysql',
|
||||
# 'PORT': '3306',
|
||||
# 'CONN_MAX_AGE':60,
|
||||
# },
|
||||
# }
|
||||
#
|
||||
# It is possible to use '--settings' to specify a custom settings file on the command line below or use an environment
|
||||
# variable to trigger an include on the bottom of your settings.py:
|
||||
# if os.environ.get('DJANGO_CONFIG')=='test':
|
||||
# from .settings_test import *
|
||||
#
|
||||
# It is also possible to hardcode the database name and credentials in the settings.py file and in the .gitlab-ci.yml file.
|
||||
#
|
||||
# The mysql service needs some variables too. See https://hub.docker.com/_/mysql for possible mysql env variables
|
||||
# Note that when using a service in GitLab CI/CD that needs environment variables to run, only variables defined in
|
||||
# .gitlab-ci.yml are passed to the service and variables defined in the GitLab UI are not.
|
||||
# https://gitlab.com/gitlab-org/gitlab/-/issues/30178
|
||||
|
||||
variables:
|
||||
POSTGRES_DB: database_name
|
||||
# DJANGO_CONFIG: "test"
|
||||
MYSQL_DATABASE: $MYSQL_DB
|
||||
MYSQL_ROOT_PASSWORD: $MYSQL_PASS
|
||||
MYSQL_USER: $MYSQL_USER
|
||||
MYSQL_PASSWORD: $MYSQL_PASS
|
||||
|
||||
# This folder is cached between builds
|
||||
# https://docs.gitlab.com/ee/ci/yaml/index.html#cache
|
||||
cache:
|
||||
paths:
|
||||
- ~/.cache/pip/
|
||||
default:
|
||||
image: ubuntu:20.04
|
||||
#
|
||||
# Pick zero or more services to be used on all builds.
|
||||
# Only needed when using a docker container to run your tests in.
|
||||
# Check out: http://docs.gitlab.com/ee/ci/docker/using_docker_images.html#what-is-a-service
|
||||
services:
|
||||
- mysql:8.0
|
||||
#
|
||||
# This folder is cached between builds
|
||||
# http://docs.gitlab.com/ee/ci/yaml/README.html#cache
|
||||
cache:
|
||||
paths:
|
||||
- ~/.cache/pip/
|
||||
before_script:
|
||||
- apt -y update
|
||||
- apt -y install apt-utils
|
||||
- apt -y install net-tools python3.8 python3-pip mysql-client libmysqlclient-dev
|
||||
- apt -y upgrade
|
||||
- pip3 install -r requirements.txt
|
||||
|
||||
# This is a basic example for a gem or script which doesn't use
|
||||
# services such as redis or postgres
|
||||
before_script:
|
||||
- python -V # Print out python version for debugging
|
||||
# Uncomment next line if your Django app needs a JS runtime:
|
||||
# - apt-get update -q && apt-get install nodejs -yqq
|
||||
- pip install -r requirements.txt
|
||||
|
||||
# To get Django tests to work you may need to create a settings file using
|
||||
# the following DATABASES:
|
||||
#
|
||||
# DATABASES = {
|
||||
# 'default': {
|
||||
# 'ENGINE': 'django.db.backends.postgresql_psycopg2',
|
||||
# 'NAME': 'ci',
|
||||
# 'USER': 'postgres',
|
||||
# 'PASSWORD': 'postgres',
|
||||
# 'HOST': 'postgres',
|
||||
# 'PORT': '5432',
|
||||
# },
|
||||
# }
|
||||
#
|
||||
# and then adding `--settings app.settings.ci` (or similar) to the test command
|
||||
|
||||
test:
|
||||
variables:
|
||||
DATABASE_URL: "postgresql://postgres:postgres@postgres:5432/$POSTGRES_DB"
|
||||
migrations:
|
||||
stage: build
|
||||
script:
|
||||
- python manage.py test
|
||||
- python3 manage.py makemigrations
|
||||
# - python3 manage.py makemigrations myapp
|
||||
- python3 manage.py migrate
|
||||
- python3 manage.py check
|
||||
|
||||
|
||||
django-tests:
|
||||
stage: test
|
||||
script:
|
||||
# The MYSQL user only gets permissions for MYSQL_DB, so Django can't create a test database.
|
||||
- echo "GRANT ALL on *.* to '${MYSQL_USER}';"| mysql -u root --password="${MYSQL_ROOT_PASSWORD}" -h mysql
|
||||
# use python3 explicitly. see https://wiki.ubuntu.com/Python/3
|
||||
- python3 manage.py test
|
||||
|
|
|
@ -5,8 +5,7 @@ import VueApollo from 'vue-apollo';
|
|||
import createMockApollo from 'helpers/mock_apollo_helper';
|
||||
import waitForPromises from 'helpers/wait_for_promises';
|
||||
import createFlash from '~/flash';
|
||||
import { IssuableType } from '~/issue_show/constants';
|
||||
import { labelsQueries } from '~/sidebar/constants';
|
||||
import { workspaceLabelsQueries } from '~/sidebar/constants';
|
||||
import DropdownContentsCreateView from '~/vue_shared/components/sidebar/labels_select_widget/dropdown_contents_create_view.vue';
|
||||
import createLabelMutation from '~/vue_shared/components/sidebar/labels_select_widget/graphql/create_label.mutation.graphql';
|
||||
import {
|
||||
|
@ -50,12 +49,12 @@ describe('DropdownContentsCreateView', () => {
|
|||
|
||||
const createComponent = ({
|
||||
mutationHandler = createLabelSuccessHandler,
|
||||
issuableType = IssuableType.Issue,
|
||||
labelType = 'ProjectLabel',
|
||||
labelCreateType = 'project',
|
||||
workspaceType = 'project',
|
||||
} = {}) => {
|
||||
const mockApollo = createMockApollo([[createLabelMutation, mutationHandler]]);
|
||||
mockApollo.clients.defaultClient.cache.writeQuery({
|
||||
query: labelsQueries[issuableType].workspaceQuery,
|
||||
query: workspaceLabelsQueries[workspaceType].query,
|
||||
data: workspaceLabelsQueryResponse.data,
|
||||
variables: {
|
||||
fullPath: '',
|
||||
|
@ -67,10 +66,10 @@ describe('DropdownContentsCreateView', () => {
|
|||
localVue,
|
||||
apolloProvider: mockApollo,
|
||||
propsData: {
|
||||
issuableType,
|
||||
fullPath: '',
|
||||
attrWorkspacePath: '',
|
||||
labelType,
|
||||
labelCreateType,
|
||||
workspaceType,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
@ -131,9 +130,11 @@ describe('DropdownContentsCreateView', () => {
|
|||
|
||||
it('emits a `hideCreateView` event on Cancel button click', () => {
|
||||
createComponent();
|
||||
findCancelButton().vm.$emit('click');
|
||||
const event = { stopPropagation: jest.fn() };
|
||||
findCancelButton().vm.$emit('click', event);
|
||||
|
||||
expect(wrapper.emitted('hideCreateView')).toHaveLength(1);
|
||||
expect(event.stopPropagation).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
describe('when label title and selected color are set', () => {
|
||||
|
@ -177,7 +178,7 @@ describe('DropdownContentsCreateView', () => {
|
|||
});
|
||||
|
||||
it('calls a mutation with `groupPath` variable on the epic', () => {
|
||||
createComponent({ issuableType: IssuableType.Epic, labelType: 'GroupLabel' });
|
||||
createComponent({ labelCreateType: 'group', workspaceType: 'group' });
|
||||
fillLabelAttributes();
|
||||
findCreateButton().vm.$emit('click');
|
||||
|
||||
|
|
|
@ -59,6 +59,8 @@ describe('DropdownContentsLabelsView', () => {
|
|||
localSelectedLabels,
|
||||
issuableType: IssuableType.Issue,
|
||||
searchKey,
|
||||
labelCreateType: 'project',
|
||||
workspaceType: 'project',
|
||||
},
|
||||
stubs: {
|
||||
GlSearchBoxByType,
|
||||
|
|
|
@ -41,7 +41,8 @@ describe('DropdownContent', () => {
|
|||
variant: 'sidebar',
|
||||
issuableType: 'issue',
|
||||
fullPath: 'test',
|
||||
labelType: 'ProjectLabel',
|
||||
workspaceType: 'project',
|
||||
labelCreateType: 'project',
|
||||
attrWorkspacePath: 'path',
|
||||
...props,
|
||||
},
|
||||
|
|
|
@ -41,7 +41,8 @@ describe('LabelsSelectRoot', () => {
|
|||
propsData: {
|
||||
...config,
|
||||
issuableType: IssuableType.Issue,
|
||||
labelType: 'ProjectLabel',
|
||||
labelCreateType: 'project',
|
||||
workspaceType: 'project',
|
||||
},
|
||||
stubs: {
|
||||
SidebarEditableItem,
|
||||
|
|
|
@ -80,6 +80,7 @@ export const createLabelSuccessfulResponse = {
|
|||
color: '#dc143c',
|
||||
description: null,
|
||||
title: 'ewrwrwer',
|
||||
textColor: '#000000',
|
||||
__typename: 'Label',
|
||||
},
|
||||
errors: [],
|
||||
|
@ -98,12 +99,14 @@ export const workspaceLabelsQueryResponse = {
|
|||
description: null,
|
||||
id: 'gid://gitlab/ProjectLabel/1',
|
||||
title: 'Label1',
|
||||
textColor: '#000000',
|
||||
},
|
||||
{
|
||||
color: '#2f7b2e',
|
||||
description: null,
|
||||
id: 'gid://gitlab/ProjectLabel/2',
|
||||
title: 'Label2',
|
||||
textColor: '#000000',
|
||||
},
|
||||
],
|
||||
},
|
||||
|
@ -123,6 +126,7 @@ export const issuableLabelsQueryResponse = {
|
|||
description: null,
|
||||
id: 'gid://gitlab/ProjectLabel/1',
|
||||
title: 'Label1',
|
||||
textColor: '#000000',
|
||||
},
|
||||
],
|
||||
},
|
||||
|
|
|
@ -721,11 +721,8 @@ RSpec.describe Notify do
|
|||
end
|
||||
|
||||
describe 'project access denied' do
|
||||
let(:project) { create(:project, :public) }
|
||||
let(:project_member) do
|
||||
project.request_access(user)
|
||||
project.requesters.find_by(user_id: user.id)
|
||||
end
|
||||
let_it_be(:project) { create(:project, :public) }
|
||||
let_it_be(:project_member) { create(:project_member, :developer, :access_request, user: user, source: project) }
|
||||
|
||||
subject { described_class.member_access_denied_email('project', project.id, user.id) }
|
||||
|
||||
|
@ -740,6 +737,17 @@ RSpec.describe Notify do
|
|||
is_expected.to have_body_text project.full_name
|
||||
is_expected.to have_body_text project.web_url
|
||||
end
|
||||
|
||||
context 'when user can not read project' do
|
||||
let_it_be(:project) { create(:project, :private) }
|
||||
|
||||
it 'hides project name from subject and body' do
|
||||
is_expected.to have_subject "Access to the Hidden project was denied"
|
||||
is_expected.to have_body_text "Hidden project"
|
||||
is_expected.not_to have_body_text project.full_name
|
||||
is_expected.not_to have_body_text project.web_url
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'project access changed' do
|
||||
|
@ -1375,10 +1383,8 @@ RSpec.describe Notify do
|
|||
end
|
||||
|
||||
describe 'group access denied' do
|
||||
let(:group_member) do
|
||||
group.request_access(user)
|
||||
group.requesters.find_by(user_id: user.id)
|
||||
end
|
||||
let_it_be(:group) { create(:group, :public) }
|
||||
let_it_be(:group_member) { create(:group_member, :developer, :access_request, user: user, source: group) }
|
||||
|
||||
let(:recipient) { user }
|
||||
|
||||
|
@ -1396,6 +1402,17 @@ RSpec.describe Notify do
|
|||
is_expected.to have_body_text group.name
|
||||
is_expected.to have_body_text group.web_url
|
||||
end
|
||||
|
||||
context 'when user can not read group' do
|
||||
let_it_be(:group) { create(:group, :private) }
|
||||
|
||||
it 'hides group name from subject and body' do
|
||||
is_expected.to have_subject "Access to the Hidden group was denied"
|
||||
is_expected.to have_body_text "Hidden group"
|
||||
is_expected.not_to have_body_text group.name
|
||||
is_expected.not_to have_body_text group.web_url
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'group access changed' do
|
||||
|
|
|
@ -17,8 +17,6 @@ RSpec.describe Ci::Bridge do
|
|||
{ trigger: { project: 'my/project', branch: 'master' } }
|
||||
end
|
||||
|
||||
it { is_expected.to respond_to(:runner_features) }
|
||||
|
||||
it 'has many sourced pipelines' do
|
||||
expect(bridge).to have_many(:sourced_pipelines)
|
||||
end
|
||||
|
|
|
@ -35,7 +35,6 @@ RSpec.describe Ci::Build do
|
|||
|
||||
it { is_expected.to respond_to(:has_trace?) }
|
||||
it { is_expected.to respond_to(:trace) }
|
||||
it { is_expected.to respond_to(:runner_features) }
|
||||
|
||||
it { is_expected.to delegate_method(:merge_request?).to(:pipeline) }
|
||||
it { is_expected.to delegate_method(:merge_request_ref?).to(:pipeline) }
|
||||
|
|
|
@ -9,6 +9,10 @@ RSpec.describe Ci::ResourceGroups::AssignResourceFromResourceGroupWorker do
|
|||
expect(described_class.get_deduplicate_strategy).to eq(:until_executed)
|
||||
end
|
||||
|
||||
it 'has an option to reschedule once if deduplicated' do
|
||||
expect(described_class.get_deduplication_options).to include({ if_deduplicated: :reschedule_once })
|
||||
end
|
||||
|
||||
describe '#perform' do
|
||||
subject { worker.perform(resource_group_id) }
|
||||
|
||||
|
|
Loading…
Reference in New Issue