Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
51858218a3
commit
fb994e98ec
|
@ -1 +1 @@
|
||||||
c886f7f37533e8ed19e245a83f363086daf590e9
|
29dec5fdae0846da19f803058441581b43fda91d
|
||||||
|
|
|
@ -31,8 +31,11 @@ export default {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
...mapState(['filterParams']),
|
...mapState(['filterParams', 'highlightedLists']),
|
||||||
...mapGetters(['getIssuesByList']),
|
...mapGetters(['getIssuesByList']),
|
||||||
|
highlighted() {
|
||||||
|
return this.highlightedLists.includes(this.list.id);
|
||||||
|
},
|
||||||
listIssues() {
|
listIssues() {
|
||||||
return this.getIssuesByList(this.list.id);
|
return this.getIssuesByList(this.list.id);
|
||||||
},
|
},
|
||||||
|
@ -48,6 +51,16 @@ export default {
|
||||||
deep: true,
|
deep: true,
|
||||||
immediate: true,
|
immediate: true,
|
||||||
},
|
},
|
||||||
|
highlighted: {
|
||||||
|
handler(highlighted) {
|
||||||
|
if (highlighted) {
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.$el.scrollIntoView({ behavior: 'smooth', block: 'start' });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
immediate: true,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
...mapActions(['fetchIssuesForList']),
|
...mapActions(['fetchIssuesForList']),
|
||||||
|
@ -68,6 +81,7 @@ export default {
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="board-inner gl-display-flex gl-flex-direction-column gl-relative gl-h-full gl-rounded-base"
|
class="board-inner gl-display-flex gl-flex-direction-column gl-relative gl-h-full gl-rounded-base"
|
||||||
|
:class="{ 'board-column-highlighted': highlighted }"
|
||||||
>
|
>
|
||||||
<board-list-header :can-admin-list="canAdminList" :list="list" :disabled="disabled" />
|
<board-list-header :can-admin-list="canAdminList" :list="list" :disabled="disabled" />
|
||||||
<board-list
|
<board-list
|
||||||
|
|
|
@ -54,6 +54,16 @@ export default {
|
||||||
},
|
},
|
||||||
deep: true,
|
deep: true,
|
||||||
},
|
},
|
||||||
|
'list.highlighted': {
|
||||||
|
handler(highlighted) {
|
||||||
|
if (highlighted) {
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.$el.scrollIntoView({ behavior: 'smooth', block: 'start' });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
immediate: true,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
const instance = this;
|
const instance = this;
|
||||||
|
@ -98,6 +108,7 @@ export default {
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="board-inner gl-display-flex gl-flex-direction-column gl-relative gl-h-full gl-rounded-base"
|
class="board-inner gl-display-flex gl-flex-direction-column gl-relative gl-h-full gl-rounded-base"
|
||||||
|
:class="{ 'board-column-highlighted': list.highlighted }"
|
||||||
>
|
>
|
||||||
<board-list-header :can-admin-list="canAdminList" :list="list" :disabled="disabled" />
|
<board-list-header :can-admin-list="canAdminList" :list="list" :disabled="disabled" />
|
||||||
<board-list ref="board-list" :disabled="disabled" :issues="listIssues" :list="list" />
|
<board-list ref="board-list" :disabled="disabled" :issues="listIssues" :list="list" />
|
||||||
|
|
|
@ -34,6 +34,8 @@ export const LIST = 'list';
|
||||||
|
|
||||||
export const NOT_FILTER = 'not[';
|
export const NOT_FILTER = 'not[';
|
||||||
|
|
||||||
|
export const flashAnimationDuration = 2000;
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
BoardType,
|
BoardType,
|
||||||
ListType,
|
ListType,
|
||||||
|
|
|
@ -44,6 +44,7 @@ class List {
|
||||||
this.isExpandable = Boolean(typeInfo.isExpandable);
|
this.isExpandable = Boolean(typeInfo.isExpandable);
|
||||||
this.isExpanded = !obj.collapsed;
|
this.isExpanded = !obj.collapsed;
|
||||||
this.page = 1;
|
this.page = 1;
|
||||||
|
this.highlighted = obj.highlighted;
|
||||||
this.loading = true;
|
this.loading = true;
|
||||||
this.loadingMore = false;
|
this.loadingMore = false;
|
||||||
this.issues = obj.issues || [];
|
this.issues = obj.issues || [];
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { pick } from 'lodash';
|
import { pick } from 'lodash';
|
||||||
|
|
||||||
import boardListsQuery from 'ee_else_ce/boards/graphql/board_lists.query.graphql';
|
import boardListsQuery from 'ee_else_ce/boards/graphql/board_lists.query.graphql';
|
||||||
import { BoardType, ListType, inactiveId } from '~/boards/constants';
|
import { BoardType, ListType, inactiveId, flashAnimationDuration } from '~/boards/constants';
|
||||||
import createFlash from '~/flash';
|
import createFlash from '~/flash';
|
||||||
import { getIdFromGraphQLId } from '~/graphql_shared/utils';
|
import { getIdFromGraphQLId } from '~/graphql_shared/utils';
|
||||||
import createGqClient, { fetchPolicies } from '~/lib/graphql';
|
import createGqClient, { fetchPolicies } from '~/lib/graphql';
|
||||||
|
@ -110,9 +110,31 @@ export default {
|
||||||
.catch(() => commit(types.RECEIVE_BOARD_LISTS_FAILURE));
|
.catch(() => commit(types.RECEIVE_BOARD_LISTS_FAILURE));
|
||||||
},
|
},
|
||||||
|
|
||||||
createList: ({ state, commit, dispatch }, { backlog, labelId, milestoneId, assigneeId }) => {
|
highlightList: ({ commit, state }, listId) => {
|
||||||
|
if ([ListType.backlog, ListType.closed].includes(state.boardLists[listId].listType)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
commit(types.ADD_LIST_TO_HIGHLIGHTED_LISTS, listId);
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
commit(types.REMOVE_LIST_FROM_HIGHLIGHTED_LISTS, listId);
|
||||||
|
}, flashAnimationDuration);
|
||||||
|
},
|
||||||
|
|
||||||
|
createList: (
|
||||||
|
{ state, commit, dispatch, getters },
|
||||||
|
{ backlog, labelId, milestoneId, assigneeId },
|
||||||
|
) => {
|
||||||
const { boardId } = state;
|
const { boardId } = state;
|
||||||
|
|
||||||
|
const existingList = getters.getListByLabelId(labelId);
|
||||||
|
|
||||||
|
if (existingList) {
|
||||||
|
dispatch('highlightList', existingList.id);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
gqlClient
|
gqlClient
|
||||||
.mutate({
|
.mutate({
|
||||||
mutation: createBoardListMutation,
|
mutation: createBoardListMutation,
|
||||||
|
@ -130,6 +152,7 @@ export default {
|
||||||
} else {
|
} else {
|
||||||
const list = data.boardListCreate?.list;
|
const list = data.boardListCreate?.list;
|
||||||
dispatch('addList', list);
|
dispatch('addList', list);
|
||||||
|
dispatch('highlightList', list.id);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch(() => commit(types.CREATE_LIST_FAILURE));
|
.catch(() => commit(types.CREATE_LIST_FAILURE));
|
||||||
|
|
|
@ -14,7 +14,7 @@ import {
|
||||||
convertObjectPropsToCamelCase,
|
convertObjectPropsToCamelCase,
|
||||||
} from '~/lib/utils/common_utils';
|
} from '~/lib/utils/common_utils';
|
||||||
import { mergeUrlParams } from '~/lib/utils/url_utility';
|
import { mergeUrlParams } from '~/lib/utils/url_utility';
|
||||||
import { ListType } from '../constants';
|
import { ListType, flashAnimationDuration } from '../constants';
|
||||||
import eventHub from '../eventhub';
|
import eventHub from '../eventhub';
|
||||||
import ListAssignee from '../models/assignee';
|
import ListAssignee from '../models/assignee';
|
||||||
import ListLabel from '../models/label';
|
import ListLabel from '../models/label';
|
||||||
|
@ -106,6 +106,11 @@ const boardsStore = {
|
||||||
list
|
list
|
||||||
.save()
|
.save()
|
||||||
.then(() => {
|
.then(() => {
|
||||||
|
list.highlighted = true;
|
||||||
|
setTimeout(() => {
|
||||||
|
list.highlighted = false;
|
||||||
|
}, flashAnimationDuration);
|
||||||
|
|
||||||
// Remove any new issues from the backlog
|
// Remove any new issues from the backlog
|
||||||
// as they will be visible in the new list
|
// as they will be visible in the new list
|
||||||
list.issues.forEach(backlogList.removeIssue.bind(backlogList));
|
list.issues.forEach(backlogList.removeIssue.bind(backlogList));
|
||||||
|
|
|
@ -28,6 +28,9 @@ export default {
|
||||||
},
|
},
|
||||||
|
|
||||||
getListByLabelId: (state) => (labelId) => {
|
getListByLabelId: (state) => (labelId) => {
|
||||||
|
if (!labelId) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
return find(state.boardLists, (l) => l.label?.id === labelId);
|
return find(state.boardLists, (l) => l.label?.id === labelId);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
|
@ -43,3 +43,5 @@ export const SET_SELECTED_PROJECT = 'SET_SELECTED_PROJECT';
|
||||||
export const ADD_BOARD_ITEM_TO_SELECTION = 'ADD_BOARD_ITEM_TO_SELECTION';
|
export const ADD_BOARD_ITEM_TO_SELECTION = 'ADD_BOARD_ITEM_TO_SELECTION';
|
||||||
export const REMOVE_BOARD_ITEM_FROM_SELECTION = 'REMOVE_BOARD_ITEM_FROM_SELECTION';
|
export const REMOVE_BOARD_ITEM_FROM_SELECTION = 'REMOVE_BOARD_ITEM_FROM_SELECTION';
|
||||||
export const SET_ADD_COLUMN_FORM_VISIBLE = 'SET_ADD_COLUMN_FORM_VISIBLE';
|
export const SET_ADD_COLUMN_FORM_VISIBLE = 'SET_ADD_COLUMN_FORM_VISIBLE';
|
||||||
|
export const ADD_LIST_TO_HIGHLIGHTED_LISTS = 'ADD_LIST_TO_HIGHLIGHTED_LISTS';
|
||||||
|
export const REMOVE_LIST_FROM_HIGHLIGHTED_LISTS = 'REMOVE_LIST_FROM_HIGHLIGHTED_LISTS';
|
||||||
|
|
|
@ -274,4 +274,12 @@ export default {
|
||||||
[mutationTypes.SET_ADD_COLUMN_FORM_VISIBLE]: (state, visible) => {
|
[mutationTypes.SET_ADD_COLUMN_FORM_VISIBLE]: (state, visible) => {
|
||||||
state.addColumnFormVisible = visible;
|
state.addColumnFormVisible = visible;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
[mutationTypes.ADD_LIST_TO_HIGHLIGHTED_LISTS]: (state, listId) => {
|
||||||
|
state.highlightedLists.push(listId);
|
||||||
|
},
|
||||||
|
|
||||||
|
[mutationTypes.REMOVE_LIST_FROM_HIGHLIGHTED_LISTS]: (state, listId) => {
|
||||||
|
state.highlightedLists = state.highlightedLists.filter((id) => id !== listId);
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
|
@ -15,6 +15,7 @@ export default () => ({
|
||||||
filterParams: {},
|
filterParams: {},
|
||||||
boardConfig: {},
|
boardConfig: {},
|
||||||
labels: [],
|
labels: [],
|
||||||
|
highlightedLists: [],
|
||||||
selectedBoardItems: [],
|
selectedBoardItems: [],
|
||||||
groupProjects: [],
|
groupProjects: [],
|
||||||
groupProjectsFlags: {
|
groupProjectsFlags: {
|
||||||
|
|
|
@ -42,6 +42,7 @@ export default class Profile {
|
||||||
$('#user_notification_email').on('select2-selecting', (event) => {
|
$('#user_notification_email').on('select2-selecting', (event) => {
|
||||||
setTimeout(this.submitForm.bind(event.currentTarget));
|
setTimeout(this.submitForm.bind(event.currentTarget));
|
||||||
});
|
});
|
||||||
|
$('#user_email_opted_in').on('change', this.submitForm);
|
||||||
$('#user_notified_of_own_activity').on('change', this.submitForm);
|
$('#user_notified_of_own_activity').on('change', this.submitForm);
|
||||||
this.form.on('submit', this.onSubmitForm);
|
this.form.on('submit', this.onSubmitForm);
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,6 +14,7 @@ query getState($projectPath: ID!, $iid: String!) {
|
||||||
pipelines(first: 1) {
|
pipelines(first: 1) {
|
||||||
nodes {
|
nodes {
|
||||||
status
|
status
|
||||||
|
warnings
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
shouldBeRebased
|
shouldBeRebased
|
||||||
|
|
|
@ -172,6 +172,11 @@ export default class MergeRequestStore {
|
||||||
this.canBeMerged = mergeRequest.mergeStatus === 'can_be_merged';
|
this.canBeMerged = mergeRequest.mergeStatus === 'can_be_merged';
|
||||||
this.canMerge = mergeRequest.userPermissions.canMerge;
|
this.canMerge = mergeRequest.userPermissions.canMerge;
|
||||||
this.ciStatus = pipeline?.status.toLowerCase();
|
this.ciStatus = pipeline?.status.toLowerCase();
|
||||||
|
|
||||||
|
if (pipeline?.warnings && this.ciStatus === 'success') {
|
||||||
|
this.ciStatus = `${this.ciStatus}-with-warnings`;
|
||||||
|
}
|
||||||
|
|
||||||
this.commitsCount = mergeRequest.commitCount || 10;
|
this.commitsCount = mergeRequest.commitCount || 10;
|
||||||
this.branchMissing = !mergeRequest.sourceBranchExists || !mergeRequest.targetBranchExists;
|
this.branchMissing = !mergeRequest.sourceBranchExists || !mergeRequest.targetBranchExists;
|
||||||
this.hasConflicts = mergeRequest.conflicts;
|
this.hasConflicts = mergeRequest.conflicts;
|
||||||
|
|
|
@ -138,6 +138,47 @@
|
||||||
border: 1px solid var(--gray-100, $gray-100);
|
border: 1px solid var(--gray-100, $gray-100);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// to highlight columns we have animated pulse of box-shadow
|
||||||
|
// we don't want to actually animate the box-shadow property
|
||||||
|
// because that causes costly repaints. Instead we can add a
|
||||||
|
// pseudo-element that is the same size as our element, then
|
||||||
|
// animate opacity/transform to give a soothing single pulse
|
||||||
|
.board-column-highlighted::after {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
pointer-events: none;
|
||||||
|
opacity: 0;
|
||||||
|
z-index: -1;
|
||||||
|
box-shadow: 0 0 6px 3px $blue-200;
|
||||||
|
animation-name: board-column-flash-border;
|
||||||
|
animation-duration: 1.2s;
|
||||||
|
animation-fill-mode: forwards;
|
||||||
|
animation-timing-function: ease-in-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes board-column-flash-border {
|
||||||
|
0%,
|
||||||
|
100% {
|
||||||
|
opacity: 0;
|
||||||
|
transform: scale(0.98);
|
||||||
|
}
|
||||||
|
|
||||||
|
25%,
|
||||||
|
75% {
|
||||||
|
opacity: 1;
|
||||||
|
transform: scale(0.99);
|
||||||
|
}
|
||||||
|
|
||||||
|
50% {
|
||||||
|
opacity: 1;
|
||||||
|
transform: scale(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.board-header {
|
.board-header {
|
||||||
&.has-border::before {
|
&.has-border::before {
|
||||||
border-top: 3px solid;
|
border-top: 3px solid;
|
||||||
|
|
|
@ -108,6 +108,30 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.text-expander {
|
||||||
|
display: inline-flex;
|
||||||
|
background: $white;
|
||||||
|
color: $gl-text-color-secondary;
|
||||||
|
padding: 1px $gl-padding-4;
|
||||||
|
cursor: pointer;
|
||||||
|
border: 1px solid $border-white-normal;
|
||||||
|
border-radius: $border-radius-default;
|
||||||
|
margin-left: 5px;
|
||||||
|
font-size: 12px;
|
||||||
|
line-height: $gl-font-size;
|
||||||
|
outline: none;
|
||||||
|
|
||||||
|
&.open {
|
||||||
|
background-color: darken($gray-light, 10%);
|
||||||
|
box-shadow: inset 0 0 2px rgba($black, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: darken($gray-light, 10%);
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.commit.flex-list {
|
.commit.flex-list {
|
||||||
display: flex;
|
display: flex;
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,7 +29,7 @@ class Profiles::NotificationsController < Profiles::ApplicationController
|
||||||
end
|
end
|
||||||
|
|
||||||
def user_params
|
def user_params
|
||||||
params.require(:user).permit(:notification_email, :notified_of_own_activity)
|
params.require(:user).permit(:notification_email, :email_opted_in, :notified_of_own_activity)
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
|
@ -27,6 +27,9 @@ module Types
|
||||||
field :status, PipelineStatusEnum, null: false,
|
field :status, PipelineStatusEnum, null: false,
|
||||||
description: "Status of the pipeline (#{::Ci::Pipeline.all_state_names.compact.join(', ').upcase})"
|
description: "Status of the pipeline (#{::Ci::Pipeline.all_state_names.compact.join(', ').upcase})"
|
||||||
|
|
||||||
|
field :warnings, GraphQL::BOOLEAN_TYPE, null: false, method: :has_warnings?,
|
||||||
|
description: "Indicates if a pipeline has warnings."
|
||||||
|
|
||||||
field :detailed_status, Types::Ci::DetailedStatusType, null: false,
|
field :detailed_status, Types::Ci::DetailedStatusType, null: false,
|
||||||
description: 'Detailed status of the pipeline.'
|
description: 'Detailed status of the pipeline.'
|
||||||
|
|
||||||
|
|
|
@ -14,7 +14,6 @@ module Analytics
|
||||||
end
|
end
|
||||||
|
|
||||||
def track_visit(target_id)
|
def track_visit(target_id)
|
||||||
return unless Feature.enabled?(:track_unique_visits, default_enabled: true)
|
|
||||||
return unless visitor_id
|
return unless visitor_id
|
||||||
|
|
||||||
Gitlab::Analytics::UniqueVisits.new.track_visit(visitor_id, target_id)
|
Gitlab::Analytics::UniqueVisits.new.track_visit(visitor_id, target_id)
|
||||||
|
|
|
@ -4,3 +4,7 @@
|
||||||
= form.select :notification_email, @user.public_verified_emails, { include_blank: false }, class: "select2", disabled: local_assigns.fetch(:email_change_disabled, nil)
|
= form.select :notification_email, @user.public_verified_emails, { include_blank: false }, class: "select2", disabled: local_assigns.fetch(:email_change_disabled, nil)
|
||||||
.help-block
|
.help-block
|
||||||
= local_assigns.fetch(:help_text, nil)
|
= local_assigns.fetch(:help_text, nil)
|
||||||
|
.form-group
|
||||||
|
%label{ for: 'user_email_opted_in' }
|
||||||
|
= form.check_box :email_opted_in
|
||||||
|
%span= _('Receive product marketing emails')
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
---
|
---
|
||||||
title: Improve highlighting for merge diffs
|
title: Improve highlighting for merge diffs
|
||||||
merge_request: 52499
|
merge_request: 53980
|
||||||
author:
|
author:
|
||||||
type: added
|
type: added
|
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
title: Add user setting for opting into marketing emails
|
||||||
|
merge_request: 53921
|
||||||
|
author:
|
||||||
|
type: added
|
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
title: Accept deeply nested arrays for CI script keyword
|
||||||
|
merge_request: 53737
|
||||||
|
author:
|
||||||
|
type: changed
|
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
title: Added warnings field to the pipelines GraphQL type
|
||||||
|
merge_request: 54089
|
||||||
|
author:
|
||||||
|
type: added
|
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
title: Highlight board lists when they are added
|
||||||
|
merge_request: 53779
|
||||||
|
author:
|
||||||
|
type: changed
|
|
@ -11,7 +11,13 @@ Rails.application.configure do
|
||||||
# Show full error reports and disable caching
|
# Show full error reports and disable caching
|
||||||
config.active_record.verbose_query_logs = true
|
config.active_record.verbose_query_logs = true
|
||||||
config.consider_all_requests_local = true
|
config.consider_all_requests_local = true
|
||||||
config.action_controller.perform_caching = false
|
|
||||||
|
if Rails.root.join('tmp', 'caching-dev.txt').exist?
|
||||||
|
config.action_controller.perform_caching = true
|
||||||
|
config.action_controller.enable_fragment_cache_logging = true
|
||||||
|
else
|
||||||
|
config.action_controller.perform_caching = false
|
||||||
|
end
|
||||||
|
|
||||||
# Show a warning when a large data set is loaded into memory
|
# Show a warning when a large data set is loaded into memory
|
||||||
config.active_record.warn_on_records_fetched_greater_than = 1000
|
config.active_record.warn_on_records_fetched_greater_than = 1000
|
||||||
|
|
|
@ -5,4 +5,4 @@ rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/299884
|
||||||
milestone: '13.9'
|
milestone: '13.9'
|
||||||
type: development
|
type: development
|
||||||
group: group::source code
|
group: group::source code
|
||||||
default_enabled: false
|
default_enabled: true
|
||||||
|
|
|
@ -18566,6 +18566,11 @@ type Pipeline {
|
||||||
Permissions for the current user on the resource
|
Permissions for the current user on the resource
|
||||||
"""
|
"""
|
||||||
userPermissions: PipelinePermissions!
|
userPermissions: PipelinePermissions!
|
||||||
|
|
||||||
|
"""
|
||||||
|
Indicates if a pipeline has warnings.
|
||||||
|
"""
|
||||||
|
warnings: Boolean!
|
||||||
}
|
}
|
||||||
|
|
||||||
type PipelineAnalytics {
|
type PipelineAnalytics {
|
||||||
|
@ -20284,6 +20289,11 @@ type Project {
|
||||||
"""
|
"""
|
||||||
iids: [ID!]
|
iids: [ID!]
|
||||||
|
|
||||||
|
"""
|
||||||
|
The state of latest requirement test report.
|
||||||
|
"""
|
||||||
|
lastTestReportState: TestReportState
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Search query for requirement title.
|
Search query for requirement title.
|
||||||
"""
|
"""
|
||||||
|
@ -20344,6 +20354,11 @@ type Project {
|
||||||
"""
|
"""
|
||||||
last: Int
|
last: Int
|
||||||
|
|
||||||
|
"""
|
||||||
|
The state of latest requirement test report.
|
||||||
|
"""
|
||||||
|
lastTestReportState: TestReportState
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Search query for requirement title.
|
Search query for requirement title.
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -54399,6 +54399,24 @@
|
||||||
},
|
},
|
||||||
"isDeprecated": false,
|
"isDeprecated": false,
|
||||||
"deprecationReason": null
|
"deprecationReason": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "warnings",
|
||||||
|
"description": "Indicates if a pipeline has warnings.",
|
||||||
|
"args": [
|
||||||
|
|
||||||
|
],
|
||||||
|
"type": {
|
||||||
|
"kind": "NON_NULL",
|
||||||
|
"name": null,
|
||||||
|
"ofType": {
|
||||||
|
"kind": "SCALAR",
|
||||||
|
"name": "Boolean",
|
||||||
|
"ofType": null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"isDeprecated": false,
|
||||||
|
"deprecationReason": null
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"inputFields": null,
|
"inputFields": null,
|
||||||
|
@ -58805,6 +58823,16 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"defaultValue": null
|
"defaultValue": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "lastTestReportState",
|
||||||
|
"description": "The state of latest requirement test report.",
|
||||||
|
"type": {
|
||||||
|
"kind": "ENUM",
|
||||||
|
"name": "TestReportState",
|
||||||
|
"ofType": null
|
||||||
|
},
|
||||||
|
"defaultValue": null
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"type": {
|
"type": {
|
||||||
|
@ -58909,6 +58937,16 @@
|
||||||
},
|
},
|
||||||
"defaultValue": null
|
"defaultValue": null
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "lastTestReportState",
|
||||||
|
"description": "The state of latest requirement test report.",
|
||||||
|
"type": {
|
||||||
|
"kind": "ENUM",
|
||||||
|
"name": "TestReportState",
|
||||||
|
"ofType": null
|
||||||
|
},
|
||||||
|
"defaultValue": null
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "after",
|
"name": "after",
|
||||||
"description": "Returns the elements in the list that come after the specified cursor.",
|
"description": "Returns the elements in the list that come after the specified cursor.",
|
||||||
|
|
|
@ -2807,6 +2807,7 @@ Information about pagination in a connection..
|
||||||
| `upstream` | Pipeline | Pipeline that triggered the pipeline. |
|
| `upstream` | Pipeline | Pipeline that triggered the pipeline. |
|
||||||
| `user` | User | Pipeline user. |
|
| `user` | User | Pipeline user. |
|
||||||
| `userPermissions` | PipelinePermissions! | Permissions for the current user on the resource |
|
| `userPermissions` | PipelinePermissions! | Permissions for the current user on the resource |
|
||||||
|
| `warnings` | Boolean! | Indicates if a pipeline has warnings. |
|
||||||
|
|
||||||
### PipelineAnalytics
|
### PipelineAnalytics
|
||||||
|
|
||||||
|
|
|
@ -84,9 +84,9 @@ This example configures the pipeline with a single job, `publish`, which runs `s
|
||||||
|
|
||||||
The default `before_script` generates a temporary `.npmrc` that is used to authenticate to the Package Registry during the `publish` job.
|
The default `before_script` generates a temporary `.npmrc` that is used to authenticate to the Package Registry during the `publish` job.
|
||||||
|
|
||||||
## Set up environment variables
|
## Set up CI/CD variables
|
||||||
|
|
||||||
As part of publishing a package, semantic-release increases the version number in `package.json`. For semantic-release to commit this change and push it back to GitLab, the pipeline requires a custom environment variable named `GITLAB_TOKEN`. To create this variable:
|
As part of publishing a package, semantic-release increases the version number in `package.json`. For semantic-release to commit this change and push it back to GitLab, the pipeline requires a custom CI/CD variable named `GITLAB_TOKEN`. To create this variable:
|
||||||
|
|
||||||
1. Navigate to **Project > Settings > Access Tokens**.
|
1. Navigate to **Project > Settings > Access Tokens**.
|
||||||
1. Give the token a name, and select the `api` scope.
|
1. Give the token a name, and select the `api` scope.
|
||||||
|
|
|
@ -273,5 +273,5 @@ Output indicates that the package has been successfully installed.
|
||||||
WARNING:
|
WARNING:
|
||||||
Never commit the `auth.json` file to your repository. To install packages from a CI/CD job,
|
Never commit the `auth.json` file to your repository. To install packages from a CI/CD job,
|
||||||
consider using the [`composer config`](https://getcomposer.org/doc/articles/handling-private-packages.md#satis) tool with your personal access token
|
consider using the [`composer config`](https://getcomposer.org/doc/articles/handling-private-packages.md#satis) tool with your personal access token
|
||||||
stored in a [GitLab CI/CD environment variable](../../../ci/variables/README.md) or in
|
stored in a [GitLab CI/CD variable](../../../ci/variables/README.md) or in
|
||||||
[HashiCorp Vault](../../../ci/secrets/index.md).
|
[HashiCorp Vault](../../../ci/secrets/index.md).
|
||||||
|
|
|
@ -144,7 +144,7 @@ Before you can build and push images by using GitLab CI/CD, you must authenticat
|
||||||
|
|
||||||
To use CI/CD to authenticate, you can use:
|
To use CI/CD to authenticate, you can use:
|
||||||
|
|
||||||
- The `CI_REGISTRY_USER` variable.
|
- The `CI_REGISTRY_USER` CI/CD variable.
|
||||||
|
|
||||||
This variable has read-write access to the Container Registry and is valid for
|
This variable has read-write access to the Container Registry and is valid for
|
||||||
one job only. Its password is also automatically created and assigned to `CI_REGISTRY_PASSWORD`.
|
one job only. Its password is also automatically created and assigned to `CI_REGISTRY_PASSWORD`.
|
||||||
|
@ -209,7 +209,7 @@ build:
|
||||||
- docker push $CI_REGISTRY/group/project/image:latest
|
- docker push $CI_REGISTRY/group/project/image:latest
|
||||||
```
|
```
|
||||||
|
|
||||||
You can also make use of [other variables](../../../ci/variables/README.md) to avoid hard-coding:
|
You can also make use of [other CI/CD variables](../../../ci/variables/README.md) to avoid hard-coding:
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
build:
|
build:
|
||||||
|
@ -382,7 +382,7 @@ The following example defines two stages: `build`, and `clean`. The
|
||||||
`build_image` job builds the Docker image for the branch, and the
|
`build_image` job builds the Docker image for the branch, and the
|
||||||
`delete_image` job deletes it. The `reg` executable is downloaded and used to
|
`delete_image` job deletes it. The `reg` executable is downloaded and used to
|
||||||
remove the image matching the `$CI_PROJECT_PATH:$CI_COMMIT_REF_SLUG`
|
remove the image matching the `$CI_PROJECT_PATH:$CI_COMMIT_REF_SLUG`
|
||||||
[environment variable](../../../ci/variables/predefined_variables.md).
|
[predefined CI/CD variable](../../../ci/variables/predefined_variables.md).
|
||||||
|
|
||||||
To use this example, change the `IMAGE_TAG` variable to match your needs:
|
To use this example, change the `IMAGE_TAG` variable to match your needs:
|
||||||
|
|
||||||
|
|
|
@ -96,17 +96,17 @@ You can authenticate using:
|
||||||
|
|
||||||
Runners log in to the Dependency Proxy automatically. To pull through
|
Runners log in to the Dependency Proxy automatically. To pull through
|
||||||
the Dependency Proxy, use the `CI_DEPENDENCY_PROXY_GROUP_IMAGE_PREFIX`
|
the Dependency Proxy, use the `CI_DEPENDENCY_PROXY_GROUP_IMAGE_PREFIX`
|
||||||
environment variable:
|
[predefined CI/CD variable](../../../ci/variables/predefined_variables.md):
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
# .gitlab-ci.yml
|
# .gitlab-ci.yml
|
||||||
image: ${CI_DEPENDENCY_PROXY_GROUP_IMAGE_PREFIX}/node:latest
|
image: ${CI_DEPENDENCY_PROXY_GROUP_IMAGE_PREFIX}/node:latest
|
||||||
```
|
```
|
||||||
|
|
||||||
There are other additional predefined environment variables you can also use:
|
There are other additional predefined CI/CD variables you can also use:
|
||||||
|
|
||||||
- `CI_DEPENDENCY_PROXY_USER`: A CI user for logging in to the Dependency Proxy.
|
- `CI_DEPENDENCY_PROXY_USER`: A CI/CD user for logging in to the Dependency Proxy.
|
||||||
- `CI_DEPENDENCY_PROXY_PASSWORD`: A CI password for logging in to the Dependency Proxy.
|
- `CI_DEPENDENCY_PROXY_PASSWORD`: A CI/CD password for logging in to the Dependency Proxy.
|
||||||
- `CI_DEPENDENCY_PROXY_SERVER`: The server for logging in to the Dependency Proxy.
|
- `CI_DEPENDENCY_PROXY_SERVER`: The server for logging in to the Dependency Proxy.
|
||||||
- `CI_DEPENDENCY_PROXY_GROUP_IMAGE_PREFIX`: The image prefix for pulling images through the Dependency Proxy.
|
- `CI_DEPENDENCY_PROXY_GROUP_IMAGE_PREFIX`: The image prefix for pulling images through the Dependency Proxy.
|
||||||
|
|
||||||
|
@ -119,7 +119,7 @@ Proxy manually without including the port:
|
||||||
docker pull gitlab.example.com:443/my-group/dependency_proxy/containers/alpine:latest
|
docker pull gitlab.example.com:443/my-group/dependency_proxy/containers/alpine:latest
|
||||||
```
|
```
|
||||||
|
|
||||||
You can also use [custom environment variables](../../../ci/variables/README.md#custom-cicd-variables) to store and access your personal access token or other valid credentials.
|
You can also use [custom CI/CD variables](../../../ci/variables/README.md#custom-cicd-variables) to store and access your personal access token or other valid credentials.
|
||||||
|
|
||||||
### Store a Docker image in Dependency Proxy cache
|
### Store a Docker image in Dependency Proxy cache
|
||||||
|
|
||||||
|
|
|
@ -732,7 +732,7 @@ You can create a new package each time the `master` branch is updated.
|
||||||
```
|
```
|
||||||
|
|
||||||
1. Make sure your `pom.xml` file includes the following.
|
1. Make sure your `pom.xml` file includes the following.
|
||||||
You can either let Maven use the CI environment variables, as shown in this example,
|
You can either let Maven use the [predefined CI/CD variables](../../../ci/variables/predefined_variables.md), as shown in this example,
|
||||||
or you can hard code your server's hostname and project's ID.
|
or you can hard code your server's hostname and project's ID.
|
||||||
|
|
||||||
```xml
|
```xml
|
||||||
|
@ -771,7 +771,7 @@ The next time the `deploy` job runs, it copies `ci_settings.xml` to the
|
||||||
user's home location. In this example:
|
user's home location. In this example:
|
||||||
|
|
||||||
- The user is `root`, because the job runs in a Docker container.
|
- The user is `root`, because the job runs in a Docker container.
|
||||||
- Maven uses the configured CI [environment variables](../../../ci/variables/README.md#predefined-cicd-variables).
|
- Maven uses the configured CI/CD variables.
|
||||||
|
|
||||||
### Create Maven packages with GitLab CI/CD by using Gradle
|
### Create Maven packages with GitLab CI/CD by using Gradle
|
||||||
|
|
||||||
|
|
|
@ -199,7 +199,7 @@ Then, you can run `npm publish` either locally or by using GitLab CI/CD.
|
||||||
NPM_TOKEN=<your_token> npm publish
|
NPM_TOKEN=<your_token> npm publish
|
||||||
```
|
```
|
||||||
|
|
||||||
- **GitLab CI/CD:** Set an `NPM_TOKEN` [variable](../../../ci/variables/README.md)
|
- **GitLab CI/CD:** Set an `NPM_TOKEN` [CI/CD variable](../../../ci/variables/README.md)
|
||||||
under your project's **Settings > CI/CD > Variables**.
|
under your project's **Settings > CI/CD > Variables**.
|
||||||
|
|
||||||
## Package naming convention
|
## Package naming convention
|
||||||
|
@ -450,7 +450,7 @@ And the `.npmrc` file should look like:
|
||||||
|
|
||||||
### `npm install` returns `Error: Failed to replace env in config: ${npm_TOKEN}`
|
### `npm install` returns `Error: Failed to replace env in config: ${npm_TOKEN}`
|
||||||
|
|
||||||
You do not need a token to run `npm install` unless your project is private. The token is only required to publish. If the `.npmrc` file was checked in with a reference to `$npm_TOKEN`, you can remove it. If you prefer to leave the reference in, you must set a value prior to running `npm install` or set the value by using [GitLab environment variables](../../../ci/variables/README.md):
|
You do not need a token to run `npm install` unless your project is private. The token is only required to publish. If the `.npmrc` file was checked in with a reference to `$npm_TOKEN`, you can remove it. If you prefer to leave the reference in, you must set a value prior to running `npm install` or set the value by using [GitLab CI/CD variables](../../../ci/variables/README.md):
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
NPM_TOKEN=<your_token> npm install
|
NPM_TOKEN=<your_token> npm install
|
||||||
|
|
|
@ -56,6 +56,8 @@ Your **Global notification settings** are the default settings unless you select
|
||||||
- This is the email address your notifications are sent to.
|
- This is the email address your notifications are sent to.
|
||||||
- Global notification level
|
- Global notification level
|
||||||
- This is the default [notification level](#notification-levels) which applies to all your notifications.
|
- This is the default [notification level](#notification-levels) which applies to all your notifications.
|
||||||
|
- Receive product marketing emails
|
||||||
|
- Check this checkbox if you want to receive periodic emails related to GitLab features.
|
||||||
- Receive notifications about your own activity.
|
- Receive notifications about your own activity.
|
||||||
- Check this checkbox if you want to receive notification about your own activity. Default: Not checked.
|
- Check this checkbox if you want to receive notification about your own activity. Default: Not checked.
|
||||||
|
|
||||||
|
|
|
@ -10,12 +10,14 @@ module Gitlab
|
||||||
class Commands < ::Gitlab::Config::Entry::Node
|
class Commands < ::Gitlab::Config::Entry::Node
|
||||||
include ::Gitlab::Config::Entry::Validatable
|
include ::Gitlab::Config::Entry::Validatable
|
||||||
|
|
||||||
|
MAX_NESTING_LEVEL = 10
|
||||||
|
|
||||||
validations do
|
validations do
|
||||||
validates :config, string_or_nested_array_of_strings: true
|
validates :config, string_or_nested_array_of_strings: { max_level: MAX_NESTING_LEVEL }
|
||||||
end
|
end
|
||||||
|
|
||||||
def value
|
def value
|
||||||
Array(@config).flatten(1)
|
Array(@config).flatten(MAX_NESTING_LEVEL)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -268,18 +268,17 @@ module Gitlab
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
class StringOrNestedArrayOfStringsValidator < NestedArrayOfStringsValidator
|
class StringOrNestedArrayOfStringsValidator < ActiveModel::EachValidator
|
||||||
|
include LegacyValidationHelpers
|
||||||
|
include NestedArrayHelpers
|
||||||
|
|
||||||
def validate_each(record, attribute, value)
|
def validate_each(record, attribute, value)
|
||||||
unless validate_string_or_nested_array_of_strings(value)
|
max_level = options.fetch(:max_level, 1)
|
||||||
record.errors.add(attribute, 'should be a string or an array containing strings and arrays of strings')
|
|
||||||
|
unless validate_string(value) || validate_nested_array(value, max_level, &method(:validate_string))
|
||||||
|
record.errors.add(attribute, "should be a string or a nested array of strings up to #{max_level} levels deep")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
def validate_string_or_nested_array_of_strings(values)
|
|
||||||
validate_string(values) || validate_nested_array_of_strings(values)
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
class TypeValidator < ActiveModel::EachValidator
|
class TypeValidator < ActiveModel::EachValidator
|
||||||
|
|
|
@ -0,0 +1,46 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module Gitlab
|
||||||
|
module Config
|
||||||
|
module Entry
|
||||||
|
module Validators
|
||||||
|
# Include this module to validate deeply nested array of values
|
||||||
|
#
|
||||||
|
# class MyNestedValidator < ActiveModel::EachValidator
|
||||||
|
# include NestedArrayHelpers
|
||||||
|
#
|
||||||
|
# def validate_each(record, attribute, value)
|
||||||
|
# max_depth = options.fetch(:max_depth, 1)
|
||||||
|
#
|
||||||
|
# unless validate_nested_array(value, max_depth) { |v| v.is_a?(Integer) }
|
||||||
|
# record.errors.add(attribute, "is invalid")
|
||||||
|
# end
|
||||||
|
# end
|
||||||
|
# end
|
||||||
|
#
|
||||||
|
module NestedArrayHelpers
|
||||||
|
def validate_nested_array(value, max_depth = 1, &validator_proc)
|
||||||
|
return false unless value.is_a?(Array)
|
||||||
|
|
||||||
|
validate_nested_array_recursively(value, max_depth, &validator_proc)
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
# rubocop: disable Performance/RedundantBlockCall
|
||||||
|
# Disables Rubocop rule for easier readability reasons.
|
||||||
|
def validate_nested_array_recursively(value, nesting_level, &validator_proc)
|
||||||
|
return true if validator_proc.call(value)
|
||||||
|
return false if nesting_level <= 0
|
||||||
|
return false unless value.is_a?(Array)
|
||||||
|
|
||||||
|
value.all? do |element|
|
||||||
|
validate_nested_array_recursively(element, nesting_level - 1, &validator_proc)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
# rubocop: enable Performance/RedundantBlockCall
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -77,7 +77,7 @@ module Gitlab
|
||||||
private
|
private
|
||||||
|
|
||||||
def version
|
def version
|
||||||
if Feature.enabled?(:improved_merge_diff_highlighting, diffable.project)
|
if Feature.enabled?(:improved_merge_diff_highlighting, diffable.project, default_enabled: :yaml)
|
||||||
NEXT_VERSION
|
NEXT_VERSION
|
||||||
else
|
else
|
||||||
VERSION
|
VERSION
|
||||||
|
|
|
@ -31,7 +31,7 @@ module Gitlab
|
||||||
# Skip inline diff if empty line was replaced with content
|
# Skip inline diff if empty line was replaced with content
|
||||||
return if old_line == ""
|
return if old_line == ""
|
||||||
|
|
||||||
if Feature.enabled?(:improved_merge_diff_highlighting, project)
|
if Feature.enabled?(:improved_merge_diff_highlighting, project, default_enabled: :yaml)
|
||||||
CharDiff.new(old_line, new_line).changed_ranges(offset: offset)
|
CharDiff.new(old_line, new_line).changed_ranges(offset: offset)
|
||||||
else
|
else
|
||||||
deprecated_diff
|
deprecated_diff
|
||||||
|
|
|
@ -13,5 +13,18 @@ module Gitlab
|
||||||
MIN_GAP = 2
|
MIN_GAP = 2
|
||||||
|
|
||||||
NoSpaceLeft = Class.new(StandardError)
|
NoSpaceLeft = Class.new(StandardError)
|
||||||
|
IllegalRange = Class.new(ArgumentError)
|
||||||
|
|
||||||
|
def self.range(lhs, rhs)
|
||||||
|
if lhs && rhs
|
||||||
|
ClosedRange.new(lhs, rhs)
|
||||||
|
elsif lhs
|
||||||
|
StartingFrom.new(lhs)
|
||||||
|
elsif rhs
|
||||||
|
EndingAt.new(rhs)
|
||||||
|
else
|
||||||
|
raise IllegalRange, 'One of rhs or lhs must be provided' unless lhs && rhs
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -2,8 +2,6 @@
|
||||||
|
|
||||||
module Gitlab
|
module Gitlab
|
||||||
module RelativePositioning
|
module RelativePositioning
|
||||||
IllegalRange = Class.new(ArgumentError)
|
|
||||||
|
|
||||||
class Range
|
class Range
|
||||||
attr_reader :lhs, :rhs
|
attr_reader :lhs, :rhs
|
||||||
|
|
||||||
|
@ -34,18 +32,6 @@ module Gitlab
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.range(lhs, rhs)
|
|
||||||
if lhs && rhs
|
|
||||||
ClosedRange.new(lhs, rhs)
|
|
||||||
elsif lhs
|
|
||||||
StartingFrom.new(lhs)
|
|
||||||
elsif rhs
|
|
||||||
EndingAt.new(rhs)
|
|
||||||
else
|
|
||||||
raise IllegalRange, 'One of rhs or lhs must be provided' unless lhs && rhs
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
class ClosedRange < RelativePositioning::Range
|
class ClosedRange < RelativePositioning::Range
|
||||||
def initialize(lhs, rhs)
|
def initialize(lhs, rhs)
|
||||||
@lhs, @rhs = lhs, rhs
|
@lhs, @rhs = lhs, rhs
|
||||||
|
|
|
@ -7525,9 +7525,21 @@ msgstr ""
|
||||||
msgid "ComplianceFrameworks|All"
|
msgid "ComplianceFrameworks|All"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "ComplianceFrameworks|Combines with the CI configuration at runtime."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "ComplianceFrameworks|Compliance pipeline configuration location (optional)"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "ComplianceFrameworks|Could not find this configuration location, please try a different location"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
msgid "ComplianceFrameworks|Error fetching compliance frameworks data. Please refresh the page"
|
msgid "ComplianceFrameworks|Error fetching compliance frameworks data. Please refresh the page"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "ComplianceFrameworks|Invalid format: it should follow the format [PATH].y(a)ml@[GROUP]/[PROJECT]"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
msgid "ComplianceFrameworks|Once you have created a compliance framework it will appear here."
|
msgid "ComplianceFrameworks|Once you have created a compliance framework it will appear here."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
@ -7543,6 +7555,9 @@ msgstr ""
|
||||||
msgid "ComplianceFrameworks|Use %{codeStart}::%{codeEnd} to create a %{linkStart}scoped set%{linkEnd} (eg. %{codeStart}SOX::AWS%{codeEnd})"
|
msgid "ComplianceFrameworks|Use %{codeStart}::%{codeEnd} to create a %{linkStart}scoped set%{linkEnd} (eg. %{codeStart}SOX::AWS%{codeEnd})"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "ComplianceFrameworks|e.g. include-gitlab.ci.yml@group-name/project-name"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
msgid "ComplianceFramework|GDPR"
|
msgid "ComplianceFramework|GDPR"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
@ -24237,6 +24252,9 @@ msgstr ""
|
||||||
msgid "Receive notifications about your own activity"
|
msgid "Receive notifications about your own activity"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Receive product marketing emails"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
msgid "Recent"
|
msgid "Recent"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
|
|
@ -119,10 +119,11 @@ RSpec.describe Profiles::NotificationsController do
|
||||||
it 'updates only permitted attributes' do
|
it 'updates only permitted attributes' do
|
||||||
sign_in(user)
|
sign_in(user)
|
||||||
|
|
||||||
put :update, params: { user: { notification_email: 'new@example.com', notified_of_own_activity: true, admin: true } }
|
put :update, params: { user: { notification_email: 'new@example.com', email_opted_in: true, notified_of_own_activity: true, admin: true } }
|
||||||
|
|
||||||
user.reload
|
user.reload
|
||||||
expect(user.notification_email).to eq('new@example.com')
|
expect(user.notification_email).to eq('new@example.com')
|
||||||
|
expect(user.email_opted_in).to eq(true)
|
||||||
expect(user.notified_of_own_activity).to eq(true)
|
expect(user.notified_of_own_activity).to eq(true)
|
||||||
expect(user.admin).to eq(false)
|
expect(user.admin).to eq(false)
|
||||||
expect(controller).to set_flash[:notice].to('Notification settings saved')
|
expect(controller).to set_flash[:notice].to('Notification settings saved')
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { shallowMount } from '@vue/test-utils';
|
import { shallowMount } from '@vue/test-utils';
|
||||||
import AxiosMockAdapter from 'axios-mock-adapter';
|
import AxiosMockAdapter from 'axios-mock-adapter';
|
||||||
import Vue from 'vue';
|
import Vue, { nextTick } from 'vue';
|
||||||
|
|
||||||
import { TEST_HOST } from 'helpers/test_constants';
|
import { TEST_HOST } from 'helpers/test_constants';
|
||||||
import { listObj } from 'jest/boards/mock_data';
|
import { listObj } from 'jest/boards/mock_data';
|
||||||
|
@ -30,6 +30,7 @@ describe('Board Column Component', () => {
|
||||||
const createComponent = ({
|
const createComponent = ({
|
||||||
listType = ListType.backlog,
|
listType = ListType.backlog,
|
||||||
collapsed = false,
|
collapsed = false,
|
||||||
|
highlighted = false,
|
||||||
withLocalStorage = true,
|
withLocalStorage = true,
|
||||||
} = {}) => {
|
} = {}) => {
|
||||||
const boardId = '1';
|
const boardId = '1';
|
||||||
|
@ -37,6 +38,7 @@ describe('Board Column Component', () => {
|
||||||
const listMock = {
|
const listMock = {
|
||||||
...listObj,
|
...listObj,
|
||||||
list_type: listType,
|
list_type: listType,
|
||||||
|
highlighted,
|
||||||
collapsed,
|
collapsed,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -91,4 +93,14 @@ describe('Board Column Component', () => {
|
||||||
expect(isCollapsed()).toBe(true);
|
expect(isCollapsed()).toBe(true);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('highlighting', () => {
|
||||||
|
it('scrolls to column when highlighted', async () => {
|
||||||
|
createComponent({ highlighted: true });
|
||||||
|
|
||||||
|
await nextTick();
|
||||||
|
|
||||||
|
expect(wrapper.element.scrollIntoView).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import { nextTick } from 'vue';
|
||||||
import { shallowMount } from '@vue/test-utils';
|
import { shallowMount } from '@vue/test-utils';
|
||||||
|
|
||||||
import { listObj } from 'jest/boards/mock_data';
|
import { listObj } from 'jest/boards/mock_data';
|
||||||
|
@ -66,4 +67,16 @@ describe('Board Column Component', () => {
|
||||||
expect(isCollapsed()).toBe(true);
|
expect(isCollapsed()).toBe(true);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('highlighting', () => {
|
||||||
|
it('scrolls to column when highlighted', async () => {
|
||||||
|
createComponent();
|
||||||
|
|
||||||
|
store.state.highlightedLists.push(listObj.id);
|
||||||
|
|
||||||
|
await nextTick();
|
||||||
|
|
||||||
|
expect(wrapper.element.scrollIntoView).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -186,7 +186,27 @@ describe('fetchLists', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('createList', () => {
|
describe('createList', () => {
|
||||||
it('should dispatch addList action when creating backlog list', (done) => {
|
let commit;
|
||||||
|
let dispatch;
|
||||||
|
let getters;
|
||||||
|
let state;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
state = {
|
||||||
|
fullPath: 'gitlab-org',
|
||||||
|
boardId: '1',
|
||||||
|
boardType: 'group',
|
||||||
|
disabled: false,
|
||||||
|
boardLists: [{ type: 'closed' }],
|
||||||
|
};
|
||||||
|
commit = jest.fn();
|
||||||
|
dispatch = jest.fn();
|
||||||
|
getters = {
|
||||||
|
getListByLabelId: jest.fn(),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should dispatch addList action when creating backlog list', async () => {
|
||||||
const backlogList = {
|
const backlogList = {
|
||||||
id: 'gid://gitlab/List/1',
|
id: 'gid://gitlab/List/1',
|
||||||
listType: 'backlog',
|
listType: 'backlog',
|
||||||
|
@ -205,25 +225,35 @@ describe('createList', () => {
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
const state = {
|
await actions.createList({ getters, state, commit, dispatch }, { backlog: true });
|
||||||
fullPath: 'gitlab-org',
|
|
||||||
boardId: '1',
|
|
||||||
boardType: 'group',
|
|
||||||
disabled: false,
|
|
||||||
boardLists: [{ type: 'closed' }],
|
|
||||||
};
|
|
||||||
|
|
||||||
testAction(
|
expect(dispatch).toHaveBeenCalledWith('addList', backlogList);
|
||||||
actions.createList,
|
|
||||||
{ backlog: true },
|
|
||||||
state,
|
|
||||||
[],
|
|
||||||
[{ type: 'addList', payload: backlogList }],
|
|
||||||
done,
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should commit CREATE_LIST_FAILURE mutation when API returns an error', (done) => {
|
it('dispatches highlightList after addList has succeeded', async () => {
|
||||||
|
const list = {
|
||||||
|
id: 'gid://gitlab/List/1',
|
||||||
|
listType: 'label',
|
||||||
|
title: 'Open',
|
||||||
|
labelId: '4',
|
||||||
|
};
|
||||||
|
|
||||||
|
jest.spyOn(gqlClient, 'mutate').mockResolvedValue({
|
||||||
|
data: {
|
||||||
|
boardListCreate: {
|
||||||
|
list,
|
||||||
|
errors: [],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
await actions.createList({ getters, state, commit, dispatch }, { labelId: '4' });
|
||||||
|
|
||||||
|
expect(dispatch).toHaveBeenCalledWith('addList', list);
|
||||||
|
expect(dispatch).toHaveBeenCalledWith('highlightList', list.id);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should commit CREATE_LIST_FAILURE mutation when API returns an error', async () => {
|
||||||
jest.spyOn(gqlClient, 'mutate').mockReturnValue(
|
jest.spyOn(gqlClient, 'mutate').mockReturnValue(
|
||||||
Promise.resolve({
|
Promise.resolve({
|
||||||
data: {
|
data: {
|
||||||
|
@ -235,22 +265,28 @@ describe('createList', () => {
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
const state = {
|
await actions.createList({ getters, state, commit, dispatch }, { backlog: true });
|
||||||
fullPath: 'gitlab-org',
|
|
||||||
boardId: '1',
|
expect(commit).toHaveBeenCalledWith(types.CREATE_LIST_FAILURE);
|
||||||
boardType: 'group',
|
});
|
||||||
disabled: false,
|
|
||||||
boardLists: [{ type: 'closed' }],
|
it('highlights list and does not re-query if it already exists', async () => {
|
||||||
|
const existingList = {
|
||||||
|
id: 'gid://gitlab/List/1',
|
||||||
|
listType: 'label',
|
||||||
|
title: 'Some label',
|
||||||
|
position: 1,
|
||||||
};
|
};
|
||||||
|
|
||||||
testAction(
|
getters = {
|
||||||
actions.createList,
|
getListByLabelId: jest.fn().mockReturnValue(existingList),
|
||||||
{ backlog: true },
|
};
|
||||||
state,
|
|
||||||
[{ type: types.CREATE_LIST_FAILURE }],
|
await actions.createList({ getters, state, commit, dispatch }, { backlog: true });
|
||||||
[],
|
|
||||||
done,
|
expect(dispatch).toHaveBeenCalledWith('highlightList', existingList.id);
|
||||||
);
|
expect(dispatch).toHaveBeenCalledTimes(1);
|
||||||
|
expect(commit).not.toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -12,7 +12,7 @@ RSpec.describe Types::Ci::PipelineType do
|
||||||
id iid sha before_sha status detailed_status config_source duration
|
id iid sha before_sha status detailed_status config_source duration
|
||||||
coverage created_at updated_at started_at finished_at committed_at
|
coverage created_at updated_at started_at finished_at committed_at
|
||||||
stages user retryable cancelable jobs source_job downstream
|
stages user retryable cancelable jobs source_job downstream
|
||||||
upstream path project active user_permissions
|
upstream path project active user_permissions warnings
|
||||||
]
|
]
|
||||||
|
|
||||||
if Gitlab.ee?
|
if Gitlab.ee?
|
||||||
|
|
|
@ -9,19 +9,6 @@ RSpec.describe Analytics::UniqueVisitsHelper do
|
||||||
let(:target_id) { 'p_analytics_valuestream' }
|
let(:target_id) { 'p_analytics_valuestream' }
|
||||||
let(:current_user) { create(:user) }
|
let(:current_user) { create(:user) }
|
||||||
|
|
||||||
before do
|
|
||||||
stub_feature_flags(track_unique_visits: true)
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'does not track visits if feature flag disabled' do
|
|
||||||
stub_feature_flags(track_unique_visits: false)
|
|
||||||
sign_in(current_user)
|
|
||||||
|
|
||||||
expect_any_instance_of(Gitlab::Analytics::UniqueVisits).not_to receive(:track_visit)
|
|
||||||
|
|
||||||
helper.track_visit(target_id)
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'does not track visit if user is not logged in' do
|
it 'does not track visit if user is not logged in' do
|
||||||
expect_any_instance_of(Gitlab::Analytics::UniqueVisits).not_to receive(:track_visit)
|
expect_any_instance_of(Gitlab::Analytics::UniqueVisits).not_to receive(:track_visit)
|
||||||
|
|
||||||
|
|
|
@ -87,18 +87,20 @@ RSpec.describe Gitlab::Ci::Config::Entry::Commands do
|
||||||
describe '#errors' do
|
describe '#errors' do
|
||||||
it 'saves errors' do
|
it 'saves errors' do
|
||||||
expect(entry.errors)
|
expect(entry.errors)
|
||||||
.to include 'commands config should be a string or an array containing strings and arrays of strings'
|
.to include 'commands config should be a string or a nested array of strings up to 10 levels deep'
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'when entry value is multi-level nested array' do
|
context 'when entry value is multi-level nested array' do
|
||||||
let(:config) { [['ls', ['echo 1']], 'pwd'] }
|
let(:config) do
|
||||||
|
['ls 0', ['ls 1', ['ls 2', ['ls 3', ['ls 4', ['ls 5', ['ls 6', ['ls 7', ['ls 8', ['ls 9', ['ls 10']]]]]]]]]]]
|
||||||
|
end
|
||||||
|
|
||||||
describe '#errors' do
|
describe '#errors' do
|
||||||
it 'saves errors' do
|
it 'saves errors' do
|
||||||
expect(entry.errors)
|
expect(entry.errors)
|
||||||
.to include 'commands config should be a string or an array containing strings and arrays of strings'
|
.to include 'commands config should be a string or a nested array of strings up to 10 levels deep'
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,70 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require 'spec_helper'
|
||||||
|
|
||||||
|
RSpec.describe Gitlab::Config::Entry::Validators::NestedArrayHelpers do
|
||||||
|
let(:config_struct) do
|
||||||
|
Struct.new(:value, keyword_init: true) do
|
||||||
|
include ActiveModel::Validations
|
||||||
|
extend Gitlab::Config::Entry::Validators::NestedArrayHelpers
|
||||||
|
|
||||||
|
validates_each :value do |record, attr, value|
|
||||||
|
unless validate_nested_array(value, 2) { |v| v.is_a?(Integer) }
|
||||||
|
record.errors.add(attr, "is invalid")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '#validate_nested_array' do
|
||||||
|
let(:config) { config_struct.new(value: value) }
|
||||||
|
|
||||||
|
subject(:errors) { config.errors }
|
||||||
|
|
||||||
|
before do
|
||||||
|
config.valid?
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'with valid values' do
|
||||||
|
context 'with arrays of integers' do
|
||||||
|
let(:value) { [10, 11] }
|
||||||
|
|
||||||
|
it { is_expected.to be_empty }
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'with nested arrays of integers' do
|
||||||
|
let(:value) { [10, [11, 12]] }
|
||||||
|
|
||||||
|
it { is_expected.to be_empty }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'with invalid values' do
|
||||||
|
subject(:error_messages) { errors.messages }
|
||||||
|
|
||||||
|
context 'with single integers' do
|
||||||
|
let(:value) { 10 }
|
||||||
|
|
||||||
|
it { is_expected.to eq({ value: ['is invalid'] }) }
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when it is nested over the limit' do
|
||||||
|
let(:value) { [10, [11, [12]]] }
|
||||||
|
|
||||||
|
it { is_expected.to eq({ value: ['is invalid'] }) }
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when a value in the array is not valid' do
|
||||||
|
let(:value) { [10, 11.5] }
|
||||||
|
|
||||||
|
it { is_expected.to eq({ value: ['is invalid'] }) }
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when a value in the nested array is not valid' do
|
||||||
|
let(:value) { [10, [11, 12.5]] }
|
||||||
|
|
||||||
|
it { is_expected.to eq({ value: ['is invalid'] }) }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
Loading…
Reference in New Issue