Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2022-09-07 12:12:40 +00:00
parent b9bc4d88ea
commit ab15b68754
46 changed files with 420 additions and 123 deletions

View File

@ -1,9 +1,11 @@
import $ from 'jquery'; import $ from 'jquery';
import ClipboardJS from 'clipboard';
import Mousetrap from 'mousetrap'; import Mousetrap from 'mousetrap';
import { clickCopyToClipboardButton } from '~/behaviors/copy_to_clipboard';
import { getSelectedFragment } from '~/lib/utils/common_utils'; import { getSelectedFragment } from '~/lib/utils/common_utils';
import { isElementVisible } from '~/lib/utils/dom_utils'; import { isElementVisible } from '~/lib/utils/dom_utils';
import { DEBOUNCE_DROPDOWN_DELAY } from '~/vue_shared/components/sidebar/labels_select_widget/constants'; import { DEBOUNCE_DROPDOWN_DELAY } from '~/vue_shared/components/sidebar/labels_select_widget/constants';
import toast from '~/vue_shared/plugins/global_toast';
import { s__ } from '~/locale';
import Sidebar from '~/right_sidebar'; import Sidebar from '~/right_sidebar';
import { CopyAsGFM } from '../markdown/copy_as_gfm'; import { CopyAsGFM } from '../markdown/copy_as_gfm';
import { import {
@ -21,6 +23,15 @@ export default class ShortcutsIssuable extends Shortcuts {
constructor() { constructor() {
super(); super();
this.inMemoryButton = document.createElement('button');
this.clipboardInstance = new ClipboardJS(this.inMemoryButton);
this.clipboardInstance.on('success', () => {
toast(s__('GlobalShortcuts|Copied source branch name to clipboard.'));
});
this.clipboardInstance.on('error', () => {
toast(s__('GlobalShortcuts|Unable to copy the source branch name at this time.'));
});
Mousetrap.bind(keysFor(ISSUE_MR_CHANGE_ASSIGNEE), () => Mousetrap.bind(keysFor(ISSUE_MR_CHANGE_ASSIGNEE), () =>
ShortcutsIssuable.openSidebarDropdown('assignee'), ShortcutsIssuable.openSidebarDropdown('assignee'),
); );
@ -32,7 +43,7 @@ export default class ShortcutsIssuable extends Shortcuts {
); );
Mousetrap.bind(keysFor(ISSUABLE_COMMENT_OR_REPLY), ShortcutsIssuable.replyWithSelectedText); Mousetrap.bind(keysFor(ISSUABLE_COMMENT_OR_REPLY), ShortcutsIssuable.replyWithSelectedText);
Mousetrap.bind(keysFor(ISSUABLE_EDIT_DESCRIPTION), ShortcutsIssuable.editIssue); Mousetrap.bind(keysFor(ISSUABLE_EDIT_DESCRIPTION), ShortcutsIssuable.editIssue);
Mousetrap.bind(keysFor(MR_COPY_SOURCE_BRANCH_NAME), ShortcutsIssuable.copyBranchName); Mousetrap.bind(keysFor(MR_COPY_SOURCE_BRANCH_NAME), () => this.copyBranchName());
/** /**
* We're attaching a global focus event listener on document for * We're attaching a global focus event listener on document for
@ -153,17 +164,14 @@ export default class ShortcutsIssuable extends Shortcuts {
return false; return false;
} }
static copyBranchName() { async copyBranchName() {
// There are two buttons - one that is shown when the sidebar const button = document.querySelector('.js-source-branch-copy');
// is expanded, and one that is shown when it's collapsed. const branchName = button?.dataset.clipboardText;
const allCopyBtns = Array.from(document.querySelectorAll('.js-source-branch-copy'));
// Select whichever button is currently visible so that if (branchName) {
// the "Copied" tooltip is shown when a click is simulated. this.inMemoryButton.dataset.clipboardText = branchName;
const visibleBtn = allCopyBtns.find(isElementVisible);
if (visibleBtn) { this.inMemoryButton.dispatchEvent(new CustomEvent('click'));
clickCopyToClipboardButton(visibleBtn);
} }
} }
} }

View File

@ -48,21 +48,12 @@ export default {
listHasNextPage() { listHasNextPage() {
return this.pageInfoByListId[this.list.id]?.hasNextPage; return this.pageInfoByListId[this.list.id]?.hasNextPage;
}, },
firstItemInListId() {
return this.listItems[0]?.id;
},
lengthOfListItemsInBoard() { lengthOfListItemsInBoard() {
return this.listItems?.length; return this.listItems?.length;
}, },
lastItemInTheListId() {
return this.listItems[this.lengthOfListItemsInBoard - 1]?.id;
},
itemIdentifier() { itemIdentifier() {
return `${this.item.id}-${this.item.iid}-${this.index}`; return `${this.item.id}-${this.item.iid}-${this.index}`;
}, },
showMoveToEndOfList() {
return !this.listHasNextPage;
},
isFirstItemInList() { isFirstItemInList() {
return this.index === 0; return this.index === 0;
}, },
@ -80,9 +71,8 @@ export default {
if (this.isFirstItemInList) { if (this.isFirstItemInList) {
return; return;
} }
const moveAfterId = this.firstItemInListId;
this.moveToPosition({ this.moveToPosition({
moveAfterId, positionInList: 0,
}); });
}, },
moveToEnd() { moveToEnd() {
@ -93,20 +83,20 @@ export default {
if (this.isLastItemInList) { if (this.isLastItemInList) {
return; return;
} }
const moveBeforeId = this.lastItemInTheListId;
this.moveToPosition({ this.moveToPosition({
moveBeforeId, positionInList: -1,
}); });
}, },
moveToPosition({ moveAfterId, moveBeforeId }) { moveToPosition({ positionInList }) {
this.moveItem({ this.moveItem({
itemId: this.item.id, itemId: this.item.id,
itemIid: this.item.iid, itemIid: this.item.iid,
itemPath: this.item.referencePath, itemPath: this.item.referencePath,
fromListId: this.list.id, fromListId: this.list.id,
toListId: this.list.id, toListId: this.list.id,
moveAfterId, positionInList,
moveBeforeId, atIndex: this.index,
allItemsLoadedInList: !this.listHasNextPage,
}); });
}, },
}, },
@ -117,7 +107,6 @@ export default {
<gl-dropdown <gl-dropdown
ref="dropdown" ref="dropdown"
:key="itemIdentifier" :key="itemIdentifier"
data-testid="move-card-dropdown"
icon="ellipsis_v" icon="ellipsis_v"
:text="s__('Boards|Move card')" :text="s__('Boards|Move card')"
:text-sr-only="true" :text-sr-only="true"
@ -128,14 +117,10 @@ export default {
@keydown.esc.native="$emit('hide')" @keydown.esc.native="$emit('hide')"
> >
<div> <div>
<gl-dropdown-item data-testid="action-move-to-first" @click.stop="moveToStart"> <gl-dropdown-item @click.stop="moveToStart">
{{ $options.i18n.moveToStartText }} {{ $options.i18n.moveToStartText }}
</gl-dropdown-item> </gl-dropdown-item>
<gl-dropdown-item <gl-dropdown-item @click.stop="moveToEnd">
v-if="showMoveToEndOfList"
data-testid="action-move-to-end"
@click.stop="moveToEnd"
>
{{ $options.i18n.moveToEndText }} {{ $options.i18n.moveToEndText }}
</gl-dropdown-item> </gl-dropdown-item>
</div> </div>

View File

@ -66,7 +66,7 @@ export default {
}, },
}, },
computed: { computed: {
...mapState(['pageInfoByListId', 'listsFlags', 'filterParams']), ...mapState(['pageInfoByListId', 'listsFlags', 'filterParams', 'isUpdateIssueOrderInProgress']),
...mapGetters(['isEpicBoard']), ...mapGetters(['isEpicBoard']),
listItemsCount() { listItemsCount() {
return this.isEpicBoard ? this.list.epicsCount : this.boardList?.issuesCount; return this.isEpicBoard ? this.list.epicsCount : this.boardList?.issuesCount;
@ -132,6 +132,9 @@ export default {
return this.canMoveIssue ? options : {}; return this.canMoveIssue ? options : {};
}, },
disableScrollingWhenMutationInProgress() {
return this.hasNextPage && this.isUpdateIssueOrderInProgress;
},
}, },
watch: { watch: {
boardItems() { boardItems() {
@ -285,9 +288,13 @@ export default {
v-bind="treeRootOptions" v-bind="treeRootOptions"
:data-board="list.id" :data-board="list.id"
:data-board-type="list.listType" :data-board-type="list.listType"
:class="{ 'bg-danger-100': boardItemsSizeExceedsMax }" :class="{
'bg-danger-100': boardItemsSizeExceedsMax,
'gl-overflow-hidden': disableScrollingWhenMutationInProgress,
'gl-overflow-y-auto': !disableScrollingWhenMutationInProgress,
}"
draggable=".board-card" draggable=".board-card"
class="board-list gl-w-full gl-h-full gl-list-style-none gl-mb-0 gl-p-3 gl-pt-0 gl-overflow-y-auto gl-overflow-x-hidden" class="board-list gl-w-full gl-h-full gl-list-style-none gl-mb-0 gl-p-3 gl-pt-0 gl-overflow-x-hidden"
data-testid="tree-root-wrapper" data-testid="tree-root-wrapper"
@start="handleDragOnStart" @start="handleDragOnStart"
@end="handleDragOnEnd" @end="handleDragOnEnd"

View File

@ -479,16 +479,25 @@ export default {
toListId, toListId,
moveBeforeId, moveBeforeId,
moveAfterId, moveAfterId,
positionInList,
allItemsLoadedInList,
} = moveData; } = moveData;
commit(types.REMOVE_BOARD_ITEM_FROM_LIST, { itemId, listId: fromListId }); commit(types.REMOVE_BOARD_ITEM_FROM_LIST, { itemId, listId: fromListId });
if (reordering && !allItemsLoadedInList && positionInList === -1) {
return;
}
if (reordering) { if (reordering) {
commit(types.ADD_BOARD_ITEM_TO_LIST, { commit(types.ADD_BOARD_ITEM_TO_LIST, {
itemId, itemId,
listId: toListId, listId: toListId,
moveBeforeId, moveBeforeId,
moveAfterId, moveAfterId,
positionInList,
atIndex: originalIndex,
allItemsLoadedInList,
}); });
return; return;
@ -500,6 +509,7 @@ export default {
listId: toListId, listId: toListId,
moveBeforeId, moveBeforeId,
moveAfterId, moveAfterId,
positionInList,
}); });
} }
@ -553,7 +563,15 @@ export default {
updateIssueOrder: async ({ commit, dispatch, state }, { moveData, mutationVariables = {} }) => { updateIssueOrder: async ({ commit, dispatch, state }, { moveData, mutationVariables = {} }) => {
try { try {
const { itemId, fromListId, toListId, moveBeforeId, moveAfterId, itemNotInToList } = moveData; const {
itemId,
fromListId,
toListId,
moveBeforeId,
moveAfterId,
itemNotInToList,
positionInList,
} = moveData;
const { const {
fullBoardId, fullBoardId,
filterParams, filterParams,
@ -562,6 +580,8 @@ export default {
}, },
} = state; } = state;
commit(types.MUTATE_ISSUE_IN_PROGRESS, true);
const { data } = await gqlClient.mutate({ const { data } = await gqlClient.mutate({
mutation: issueMoveListMutation, mutation: issueMoveListMutation,
variables: { variables: {
@ -572,6 +592,7 @@ export default {
toListId: getIdFromGraphQLId(toListId), toListId: getIdFromGraphQLId(toListId),
moveBeforeId: moveBeforeId ? getIdFromGraphQLId(moveBeforeId) : undefined, moveBeforeId: moveBeforeId ? getIdFromGraphQLId(moveBeforeId) : undefined,
moveAfterId: moveAfterId ? getIdFromGraphQLId(moveAfterId) : undefined, moveAfterId: moveAfterId ? getIdFromGraphQLId(moveAfterId) : undefined,
positionInList,
// 'mutationVariables' allows EE code to pass in extra parameters. // 'mutationVariables' allows EE code to pass in extra parameters.
...mutationVariables, ...mutationVariables,
}, },
@ -643,7 +664,9 @@ export default {
} }
commit(types.MUTATE_ISSUE_SUCCESS, { issue: data.issueMoveList.issue }); commit(types.MUTATE_ISSUE_SUCCESS, { issue: data.issueMoveList.issue });
commit(types.MUTATE_ISSUE_IN_PROGRESS, false);
} catch { } catch {
commit(types.MUTATE_ISSUE_IN_PROGRESS, false);
commit( commit(
types.SET_ERROR, types.SET_ERROR,
s__('Boards|An error occurred while moving the issue. Please try again.'), s__('Boards|An error occurred while moving the issue. Please try again.'),

View File

@ -44,3 +44,4 @@ export const ADD_LIST_TO_HIGHLIGHTED_LISTS = 'ADD_LIST_TO_HIGHLIGHTED_LISTS';
export const REMOVE_LIST_FROM_HIGHLIGHTED_LISTS = 'REMOVE_LIST_FROM_HIGHLIGHTED_LISTS'; export const REMOVE_LIST_FROM_HIGHLIGHTED_LISTS = 'REMOVE_LIST_FROM_HIGHLIGHTED_LISTS';
export const RESET_BOARD_ITEM_SELECTION = 'RESET_BOARD_ITEM_SELECTION'; export const RESET_BOARD_ITEM_SELECTION = 'RESET_BOARD_ITEM_SELECTION';
export const SET_ERROR = 'SET_ERROR'; export const SET_ERROR = 'SET_ERROR';
export const MUTATE_ISSUE_IN_PROGRESS = 'MUTATE_ISSUE_IN_PROGRESS';

View File

@ -20,17 +20,28 @@ export const removeItemFromList = ({ state, listId, itemId }) => {
updateListItemsCount({ state, listId, value: -1 }); updateListItemsCount({ state, listId, value: -1 });
}; };
export const addItemToList = ({ state, listId, itemId, moveBeforeId, moveAfterId, atIndex }) => { export const addItemToList = ({
state,
listId,
itemId,
moveBeforeId,
moveAfterId,
atIndex,
positionInList,
}) => {
const listIssues = state.boardItemsByListId[listId]; const listIssues = state.boardItemsByListId[listId];
let newIndex = atIndex || 0; let newIndex = atIndex || 0;
const moveToStartOrLast = positionInList !== undefined;
if (moveBeforeId) { if (moveBeforeId) {
newIndex = listIssues.indexOf(moveBeforeId) + 1; newIndex = listIssues.indexOf(moveBeforeId) + 1;
} else if (moveAfterId) { } else if (moveAfterId) {
newIndex = listIssues.indexOf(moveAfterId); newIndex = listIssues.indexOf(moveAfterId);
} else if (moveToStartOrLast) {
newIndex = positionInList === -1 ? listIssues.length : 0;
} }
listIssues.splice(newIndex, 0, itemId); listIssues.splice(newIndex, 0, itemId);
Vue.set(state.boardItemsByListId, listId, listIssues); Vue.set(state.boardItemsByListId, listId, listIssues);
updateListItemsCount({ state, listId, value: 1 }); updateListItemsCount({ state, listId, value: moveToStartOrLast ? 0 : 1 });
}; };
export default { export default {
@ -205,12 +216,34 @@ export default {
Vue.set(state.boardItems, issue.id, formatIssue(issue)); Vue.set(state.boardItems, issue.id, formatIssue(issue));
}, },
[mutationTypes.MUTATE_ISSUE_IN_PROGRESS](state, isLoading) {
state.isUpdateIssueOrderInProgress = isLoading;
},
[mutationTypes.ADD_BOARD_ITEM_TO_LIST]: ( [mutationTypes.ADD_BOARD_ITEM_TO_LIST]: (
state, state,
{ itemId, listId, moveBeforeId, moveAfterId, atIndex, inProgress = false }, {
itemId,
listId,
moveBeforeId,
moveAfterId,
atIndex,
positionInList,
allItemsLoadedInList,
inProgress = false,
},
) => { ) => {
Vue.set(state.listsFlags, listId, { ...state.listsFlags, addItemToListInProgress: inProgress }); Vue.set(state.listsFlags, listId, { ...state.listsFlags, addItemToListInProgress: inProgress });
addItemToList({ state, listId, itemId, moveBeforeId, moveAfterId, atIndex }); addItemToList({
state,
listId,
itemId,
moveBeforeId,
moveAfterId,
atIndex,
positionInList,
allItemsLoadedInList,
});
}, },
[mutationTypes.REMOVE_BOARD_ITEM_FROM_LIST]: (state, { itemId, listId }) => { [mutationTypes.REMOVE_BOARD_ITEM_FROM_LIST]: (state, { itemId, listId }) => {

View File

@ -40,4 +40,5 @@ export default () => ({
}, },
// TODO: remove after ce/ee split of board_content.vue // TODO: remove after ce/ee split of board_content.vue
isShowingEpicsSwimlanes: false, isShowingEpicsSwimlanes: false,
isUpdateIssueOrderInProgress: false,
}); });

View File

@ -77,7 +77,7 @@ module Ci
%i[pipeline project ref tag options name %i[pipeline project ref tag options name
allow_failure stage stage_idx allow_failure stage stage_idx
yaml_variables when description needs_attributes yaml_variables when description needs_attributes
scheduling_type ci_stage].freeze scheduling_type ci_stage partition_id].freeze
end end
def inherit_status_from_downstream!(pipeline) def inherit_status_from_downstream!(pipeline)

View File

@ -217,7 +217,8 @@ module Ci
allow_failure stage stage_idx trigger_request allow_failure stage stage_idx trigger_request
yaml_variables when environment coverage_regex yaml_variables when environment coverage_regex
description tag_list protected needs_attributes description tag_list protected needs_attributes
job_variables_attributes resource_group scheduling_type ci_stage].freeze job_variables_attributes resource_group scheduling_type
ci_stage partition_id].freeze
end end
end end

View File

@ -0,0 +1,6 @@
# frozen_string_literal: true
module Ci
class Partition < Ci::ApplicationRecord
end
end

View File

@ -1,6 +1,6 @@
- form = local_assigns.fetch(:form) - form = local_assigns.fetch(:form)
.form-group .form-group
= form.label :notification_email, class: "label-bold" = form.label :notification_email, _('Notification Email'), class: "label-bold"
= form.select :notification_email, @user.public_verified_emails, { include_blank: _('Use primary email (%{email})') % { email: @user.email }, selected: @user.notification_email }, class: "select2", disabled: local_assigns.fetch(:email_change_disabled, nil) = form.select :notification_email, @user.public_verified_emails, { include_blank: _('Use primary email (%{email})') % { email: @user.email }, selected: @user.notification_email }, 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)

View File

@ -25,7 +25,7 @@
= gitlab_ui_form_for @user, url: profile_notifications_path, method: :put, html: { class: 'update-notifications gl-mt-3' } do |f| = gitlab_ui_form_for @user, url: profile_notifications_path, method: :put, html: { class: 'update-notifications gl-mt-3' } do |f|
= render_if_exists 'profiles/notifications/email_settings', form: f = render_if_exists 'profiles/notifications/email_settings', form: f
= label_tag :global_notification_level, "Global notification level", class: "label-bold" = label_tag :global_notification_level, _('Global notification level'), class: "label-bold"
%br %br
.clearfix .clearfix
.form-group.float-left.global-notification-setting .form-group.float-left.global-notification-setting

View File

@ -12,7 +12,7 @@ time_frame: all
data_source: redis data_source: redis
instrumentation_class: RedisMetric instrumentation_class: RedisMetric
options: options:
counter_class: SourceCodeCounter prefix: source_code
event: pushes event: pushes
distribution: distribution:
- ce - ce

View File

@ -0,0 +1,9 @@
---
table_name: ci_partitions
classes:
- Ci::Partition
feature_categories:
- continuous_integration
description: Database partitioning metadata for CI tables
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/96856
milestone: '15.4'

View File

@ -0,0 +1,9 @@
# frozen_string_literal: true
class CreateCiPartitions < Gitlab::Database::Migration[2.0]
def change
create_table :ci_partitions do |t|
t.timestamps_with_timezone null: false
end
end
end

View File

@ -0,0 +1,21 @@
# frozen_string_literal: true
class CreateDefaultPartitionRecord < Gitlab::Database::Migration[2.0]
disable_ddl_transaction!
restrict_gitlab_migration gitlab_schema: :gitlab_ci
def up
execute(<<~SQL)
INSERT INTO "ci_partitions" ("id", "created_at", "updated_at")
VALUES (100, now(), now());
SQL
reset_pk_sequence!('ci_partitions')
end
def down
execute(<<~SQL)
DELETE FROM "ci_partitions" WHERE "ci_partitions"."id" = 100;
SQL
end
end

View File

@ -0,0 +1,11 @@
# frozen_string_literal: true
class AddPartitionIdToCiBuilds < Gitlab::Database::Migration[2.0]
enable_lock_retries!
# rubocop:disable Migration/AddColumnsToWideTables
def change
add_column :ci_builds, :partition_id, :bigint, default: 100, null: false
end
# rubocop:enable Migration/AddColumnsToWideTables
end

View File

@ -0,0 +1,9 @@
# frozen_string_literal: true
class AddPartitionIdToCiBuildsMetadata < Gitlab::Database::Migration[2.0]
enable_lock_retries!
def change
add_column :ci_builds_metadata, :partition_id, :bigint, default: 100, null: false
end
end

View File

@ -0,0 +1,9 @@
# frozen_string_literal: true
class AddPartitionIdToCiJobArtifacts < Gitlab::Database::Migration[2.0]
enable_lock_retries!
def change
add_column :ci_job_artifacts, :partition_id, :bigint, default: 100, null: false
end
end

View File

@ -0,0 +1,9 @@
# frozen_string_literal: true
class AddPartitionIdToCiPipelines < Gitlab::Database::Migration[2.0]
enable_lock_retries!
def change
add_column :ci_pipelines, :partition_id, :bigint, default: 100, null: false
end
end

View File

@ -0,0 +1,9 @@
# frozen_string_literal: true
class AddPartitionIdToCiStages < Gitlab::Database::Migration[2.0]
enable_lock_retries!
def change
add_column :ci_stages, :partition_id, :bigint, default: 100, null: false
end
end

View File

@ -0,0 +1,9 @@
# frozen_string_literal: true
class AddPartitionIdToCiPipelineVariables < Gitlab::Database::Migration[2.0]
enable_lock_retries!
def change
add_column :ci_pipeline_variables, :partition_id, :bigint, default: 100, null: false
end
end

View File

@ -0,0 +1 @@
d1ca445a17c742d435cba3d898e61242a3df9c92caeadecba147fce858d8cb80

View File

@ -0,0 +1 @@
910d87fbab226671b8e12b236be43970f6b2a3083f30df9586b3f8edf779f4af

View File

@ -0,0 +1 @@
11c65391a6744d7d7c303c6593dafa8e6dca392675974a2a1df2c164afbd4fe1

View File

@ -0,0 +1 @@
cce779cc52b2bb175ccd3d07ac6a7df3711ae362fa0a5004bfc58fa1eb440e1f

View File

@ -0,0 +1 @@
8ec0cc23559ba1b83042bed4abf8c47487ecb999fa66e602fbf4a9edac0569ec

View File

@ -0,0 +1 @@
4f2076138e65849d60cf093f140afa1abaa7beea4d6c95048e6743168a7f17a9

View File

@ -0,0 +1 @@
49a86fa87974f2c0cdc5a38726ab792f70c43e7f215495323d0999fd9f6e45f6

View File

@ -0,0 +1 @@
812f25371d731d03bd4727328ad0daaf954595e24a314dd5f1adccdc3a4532c4

View File

@ -12632,6 +12632,7 @@ CREATE TABLE ci_builds (
scheduling_type smallint, scheduling_type smallint,
id bigint NOT NULL, id bigint NOT NULL,
stage_id bigint, stage_id bigint,
partition_id bigint DEFAULT 100 NOT NULL,
CONSTRAINT check_1e2fbd1b39 CHECK ((lock_version IS NOT NULL)) CONSTRAINT check_1e2fbd1b39 CHECK ((lock_version IS NOT NULL))
); );
@ -12658,7 +12659,8 @@ CREATE TABLE ci_builds_metadata (
build_id bigint NOT NULL, build_id bigint NOT NULL,
id bigint NOT NULL, id bigint NOT NULL,
runtime_runner_features jsonb DEFAULT '{}'::jsonb NOT NULL, runtime_runner_features jsonb DEFAULT '{}'::jsonb NOT NULL,
id_tokens jsonb DEFAULT '{}'::jsonb NOT NULL id_tokens jsonb DEFAULT '{}'::jsonb NOT NULL,
partition_id bigint DEFAULT 100 NOT NULL
); );
CREATE SEQUENCE ci_builds_metadata_id_seq CREATE SEQUENCE ci_builds_metadata_id_seq
@ -12823,6 +12825,7 @@ CREATE TABLE ci_job_artifacts (
job_id bigint NOT NULL, job_id bigint NOT NULL,
locked smallint DEFAULT 2, locked smallint DEFAULT 2,
original_filename text, original_filename text,
partition_id bigint DEFAULT 100 NOT NULL,
CONSTRAINT check_27f0f6dbab CHECK ((file_store IS NOT NULL)), CONSTRAINT check_27f0f6dbab CHECK ((file_store IS NOT NULL)),
CONSTRAINT check_85573000db CHECK ((char_length(original_filename) <= 512)) CONSTRAINT check_85573000db CHECK ((char_length(original_filename) <= 512))
); );
@ -12928,6 +12931,21 @@ CREATE SEQUENCE ci_namespace_monthly_usages_id_seq
ALTER SEQUENCE ci_namespace_monthly_usages_id_seq OWNED BY ci_namespace_monthly_usages.id; ALTER SEQUENCE ci_namespace_monthly_usages_id_seq OWNED BY ci_namespace_monthly_usages.id;
CREATE TABLE ci_partitions (
id bigint NOT NULL,
created_at timestamp with time zone NOT NULL,
updated_at timestamp with time zone NOT NULL
);
CREATE SEQUENCE ci_partitions_id_seq
START WITH 1
INCREMENT BY 1
NO MINVALUE
NO MAXVALUE
CACHE 1;
ALTER SEQUENCE ci_partitions_id_seq OWNED BY ci_partitions.id;
CREATE TABLE ci_pending_builds ( CREATE TABLE ci_pending_builds (
id bigint NOT NULL, id bigint NOT NULL,
build_id bigint NOT NULL, build_id bigint NOT NULL,
@ -13071,7 +13089,8 @@ CREATE TABLE ci_pipeline_variables (
encrypted_value_iv character varying, encrypted_value_iv character varying,
pipeline_id integer NOT NULL, pipeline_id integer NOT NULL,
variable_type smallint DEFAULT 1 NOT NULL, variable_type smallint DEFAULT 1 NOT NULL,
raw boolean DEFAULT true NOT NULL raw boolean DEFAULT true NOT NULL,
partition_id bigint DEFAULT 100 NOT NULL
); );
CREATE SEQUENCE ci_pipeline_variables_id_seq CREATE SEQUENCE ci_pipeline_variables_id_seq
@ -13113,6 +13132,7 @@ CREATE TABLE ci_pipelines (
external_pull_request_id bigint, external_pull_request_id bigint,
ci_ref_id bigint, ci_ref_id bigint,
locked smallint DEFAULT 1 NOT NULL, locked smallint DEFAULT 1 NOT NULL,
partition_id bigint DEFAULT 100 NOT NULL,
CONSTRAINT check_d7e99a025e CHECK ((lock_version IS NOT NULL)) CONSTRAINT check_d7e99a025e CHECK ((lock_version IS NOT NULL))
); );
@ -13417,6 +13437,7 @@ CREATE TABLE ci_stages (
lock_version integer DEFAULT 0, lock_version integer DEFAULT 0,
"position" integer, "position" integer,
id bigint NOT NULL, id bigint NOT NULL,
partition_id bigint DEFAULT 100 NOT NULL,
CONSTRAINT check_81b431e49b CHECK ((lock_version IS NOT NULL)) CONSTRAINT check_81b431e49b CHECK ((lock_version IS NOT NULL))
); );
@ -23241,6 +23262,8 @@ ALTER TABLE ONLY ci_namespace_mirrors ALTER COLUMN id SET DEFAULT nextval('ci_na
ALTER TABLE ONLY ci_namespace_monthly_usages ALTER COLUMN id SET DEFAULT nextval('ci_namespace_monthly_usages_id_seq'::regclass); ALTER TABLE ONLY ci_namespace_monthly_usages ALTER COLUMN id SET DEFAULT nextval('ci_namespace_monthly_usages_id_seq'::regclass);
ALTER TABLE ONLY ci_partitions ALTER COLUMN id SET DEFAULT nextval('ci_partitions_id_seq'::regclass);
ALTER TABLE ONLY ci_pending_builds ALTER COLUMN id SET DEFAULT nextval('ci_pending_builds_id_seq'::regclass); ALTER TABLE ONLY ci_pending_builds ALTER COLUMN id SET DEFAULT nextval('ci_pending_builds_id_seq'::regclass);
ALTER TABLE ONLY ci_pipeline_artifacts ALTER COLUMN id SET DEFAULT nextval('ci_pipeline_artifacts_id_seq'::regclass); ALTER TABLE ONLY ci_pipeline_artifacts ALTER COLUMN id SET DEFAULT nextval('ci_pipeline_artifacts_id_seq'::regclass);
@ -24976,6 +24999,9 @@ ALTER TABLE ONLY ci_namespace_mirrors
ALTER TABLE ONLY ci_namespace_monthly_usages ALTER TABLE ONLY ci_namespace_monthly_usages
ADD CONSTRAINT ci_namespace_monthly_usages_pkey PRIMARY KEY (id); ADD CONSTRAINT ci_namespace_monthly_usages_pkey PRIMARY KEY (id);
ALTER TABLE ONLY ci_partitions
ADD CONSTRAINT ci_partitions_pkey PRIMARY KEY (id);
ALTER TABLE ONLY ci_pending_builds ALTER TABLE ONLY ci_pending_builds
ADD CONSTRAINT ci_pending_builds_pkey PRIMARY KEY (id); ADD CONSTRAINT ci_pending_builds_pkey PRIMARY KEY (id);

View File

@ -76,8 +76,7 @@ Example response:
}, },
"expires_at": "2012-10-22T14:13:35Z", "expires_at": "2012-10-22T14:13:35Z",
"access_level": 30, "access_level": 30,
"group_saml_identity": null, "group_saml_identity": null
"membership_state": "active"
}, },
{ {
"id": 2, "id": 2,
@ -102,8 +101,7 @@ Example response:
"extern_uid":"ABC-1234567890", "extern_uid":"ABC-1234567890",
"provider": "group_saml", "provider": "group_saml",
"saml_provider_id": 10 "saml_provider_id": 10
}, }
"membership_state": "active"
} }
] ]
``` ```
@ -163,8 +161,7 @@ Example response:
}, },
"expires_at": "2012-10-22T14:13:35Z", "expires_at": "2012-10-22T14:13:35Z",
"access_level": 30, "access_level": 30,
"group_saml_identity": null, "group_saml_identity": null
"membership_state": "active"
}, },
{ {
"id": 2, "id": 2,
@ -189,8 +186,7 @@ Example response:
"extern_uid":"ABC-1234567890", "extern_uid":"ABC-1234567890",
"provider": "group_saml", "provider": "group_saml",
"saml_provider_id": 10 "saml_provider_id": 10
}, }
"membership_state": "active"
}, },
{ {
"id": 3, "id": 3,
@ -210,8 +206,7 @@ Example response:
}, },
"expires_at": "2012-11-22T14:13:35Z", "expires_at": "2012-11-22T14:13:35Z",
"access_level": 30, "access_level": 30,
"group_saml_identity": null, "group_saml_identity": null
"membership_state": "active"
} }
] ]
``` ```
@ -257,8 +252,7 @@ Example response:
"web_url": "http://192.168.1.8:3000/root" "web_url": "http://192.168.1.8:3000/root"
}, },
"expires_at": null, "expires_at": null,
"group_saml_identity": null, "group_saml_identity": null
"membership_state": "active"
} }
``` ```
@ -305,8 +299,7 @@ Example response:
}, },
"email": "john@example.com", "email": "john@example.com",
"expires_at": null, "expires_at": null,
"group_saml_identity": null, "group_saml_identity": null
"membership_state": "active"
} }
``` ```
@ -370,7 +363,6 @@ Example response:
"web_url": "http://192.168.1.8:3000/root", "web_url": "http://192.168.1.8:3000/root",
"last_activity_on": "2021-01-27", "last_activity_on": "2021-01-27",
"membership_type": "group_member", "membership_type": "group_member",
"membership_state": "active",
"removable": true, "removable": true,
"created_at": "2021-01-03T12:16:02.000Z" "created_at": "2021-01-03T12:16:02.000Z"
}, },
@ -384,7 +376,6 @@ Example response:
"email": "john@example.com", "email": "john@example.com",
"last_activity_on": "2021-01-25", "last_activity_on": "2021-01-25",
"membership_type": "group_member", "membership_type": "group_member",
"membership_state": "active",
"removable": true, "removable": true,
"created_at": "2021-01-04T18:46:42.000Z" "created_at": "2021-01-04T18:46:42.000Z"
}, },
@ -397,7 +388,6 @@ Example response:
"web_url": "http://192.168.1.8:3000/root", "web_url": "http://192.168.1.8:3000/root",
"last_activity_on": "2021-01-20", "last_activity_on": "2021-01-20",
"membership_type": "group_invite", "membership_type": "group_invite",
"membership_state": "awaiting",
"removable": false, "removable": false,
"created_at": "2021-01-09T07:12:31.000Z" "created_at": "2021-01-09T07:12:31.000Z"
} }

42
doc/ci/mobile_devops.md Normal file
View File

@ -0,0 +1,42 @@
---
stage: none
group: unassigned
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments
type: reference
---
# Mobile DevOps
GitLab Mobile DevOps is a collection of features and tools designed for mobile developers
and teams to automate their build and release process using GitLab CI/CD. Mobile DevOps
is an experimental feature developed by [GitLab Incubation Engineering](https://about.gitlab.com/handbook/engineering/incubation/).
Mobile DevOps is still in development, but you can:
- [Request a feature](https://gitlab.com/gitlab-org/incubation-engineering/mobile-devops/feedback/-/issues/new?issuable_template=feature_request).
- [Report a bug](https://gitlab.com/gitlab-org/incubation-engineering/mobile-devops/feedback/-/issues/new?issuable_template=report_bug).
- [Share feedback](https://gitlab.com/gitlab-org/incubation-engineering/mobile-devops/feedback/-/issues/new?issuable_template=general_feedback).
## Code Signing
[Project-level Secure Files](secure_files/index.md) makes it easier to manage key stores, provision profiles,
and signing certificates directly in a GitLab project.
For a guided walkthrough of this feature, watch the [video demo](https://youtu.be/O7FbJu3H2YM).
## Review Apps for Mobile
You can use [Review Apps](review_apps/index.md) to preview changes directly from a merge request.
Review Apps for Mobile brings that capability to mobile developers through an integration
with [Appetize](https://appetize.io/).
Watch a [video walkthrough](https://youtu.be/X15mI19TXa4) of this feature, or visit the
[setup instructions](https://gitlab.com/gitlab-org/incubation-engineering/mobile-devops/readme/-/issues/15)
to get started.
## Mobile SAST
You can use [Static Application Security Testing (SAST)](../user/application_security/sast/index.md)
to run static analyzers on code to check for known security vulnerabilities. Mobile SAST
expands this functionality for mobile teams with an [experimental SAST feature](../user/application_security/sast/index.md#experimental-features)
based on [Mobile Security Framework (MobSF)](https://github.com/MobSF/Mobile-Security-Framework-MobSF).

View File

@ -272,7 +272,7 @@ Events are handled by counter classes in the `Gitlab::UsageDataCounters` namespa
1. Listed in [`Gitlab::UsageDataCounters::COUNTERS`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/lib/gitlab/usage_data_counters.rb#L5) to be then included in `Gitlab::UsageData`. 1. Listed in [`Gitlab::UsageDataCounters::COUNTERS`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/lib/gitlab/usage_data_counters.rb#L5) to be then included in `Gitlab::UsageData`.
1. Specified in the metric definition using the `RedisMetric` instrumentation class as a `counter_class` option to be picked up using the [metric instrumentation](metrics_instrumentation.md) framework. Refer to the [Redis metrics](metrics_instrumentation.md#redis-metrics) documentation for an example implementation. 1. Specified in the metric definition using the `RedisMetric` instrumentation class by their `prefix` option to be picked up using the [metric instrumentation](metrics_instrumentation.md) framework. Refer to the [Redis metrics](metrics_instrumentation.md#redis-metrics) documentation for an example implementation.
Inheriting classes are expected to override `KNOWN_EVENTS` and `PREFIX` constants to build event names and associated metrics. For example, for prefix `issues` and events array `%w[create, update, delete]`, three metrics will be added to the Service Ping payload: `counts.issues_create`, `counts.issues_update` and `counts.issues_delete`. Inheriting classes are expected to override `KNOWN_EVENTS` and `PREFIX` constants to build event names and associated metrics. For example, for prefix `issues` and events array `%w[create, update, delete]`, three metrics will be added to the Service Ping payload: `counts.issues_create`, `counts.issues_update` and `counts.issues_delete`.

View File

@ -154,14 +154,16 @@ end
You can use Redis metrics to track events not kept in the database, for example, a count of how many times the search bar has been used. You can use Redis metrics to track events not kept in the database, for example, a count of how many times the search bar has been used.
[Example of a merge request that adds a `Redis` metric](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/66582). [Example of a merge request that adds a `Redis` metric](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/97009).
Please note that `RedisMetric` class can only be used as the `instrumentation_class` for Redis metrics with simple counters classes (classes that only inherit `BaseCounter` and set `PREFIX` and `KNOWN_EVENTS` constants). In case the counter class has additional logic included in it, a new `instrumentation_class`, inheriting from `RedisMetric`, needs to be created. This new class needs to include the additional logic from the counter class.
Count unique values for `source_code_pushes` event. Count unique values for `source_code_pushes` event.
Required options: Required options:
- `event`: the event name. - `event`: the event name.
- `counter_class`: one of the counter classes from the `Gitlab::UsageDataCounters` namespace; it should implement `read` method or inherit it from `BaseCounter`. - `prefix`: the value of the `PREFIX` constant used in the counter classes from the `Gitlab::UsageDataCounters` namespace.
```yaml ```yaml
time_frame: all time_frame: all
@ -169,7 +171,7 @@ data_source: redis
instrumentation_class: 'RedisMetric' instrumentation_class: 'RedisMetric'
options: options:
event: pushes event: pushes
counter_class: SourceCodeCounter prefix: source_code
``` ```
### Availability-restrained Redis metrics ### Availability-restrained Redis metrics
@ -200,7 +202,7 @@ data_source: redis
instrumentation_class: 'MergeUsageCountRedisMetric' instrumentation_class: 'MergeUsageCountRedisMetric'
options: options:
event: pushes event: pushes
counter_class: SourceCodeCounter prefix: source_code
``` ```
## Redis HyperLogLog metrics ## Redis HyperLogLog metrics

View File

@ -91,6 +91,7 @@ ci_job_artifact_states: :gitlab_ci
ci_minutes_additional_packs: :gitlab_ci ci_minutes_additional_packs: :gitlab_ci
ci_namespace_monthly_usages: :gitlab_ci ci_namespace_monthly_usages: :gitlab_ci
ci_namespace_mirrors: :gitlab_ci ci_namespace_mirrors: :gitlab_ci
ci_partitions: :gitlab_ci
ci_pending_builds: :gitlab_ci ci_pending_builds: :gitlab_ci
ci_pipeline_artifacts: :gitlab_ci ci_pipeline_artifacts: :gitlab_ci
ci_pipeline_chat_data: :gitlab_ci ci_pipeline_chat_data: :gitlab_ci

View File

@ -11,37 +11,41 @@ module Gitlab
# instrumentation_class: RedisMetric # instrumentation_class: RedisMetric
# options: # options:
# event: pushes # event: pushes
# counter_class: SourceCodeCounter # prefix: source_code
# #
class RedisMetric < BaseMetric class RedisMetric < BaseMetric
include Gitlab::UsageDataCounters::RedisCounter
def initialize(time_frame:, options: {}) def initialize(time_frame:, options: {})
super super
raise ArgumentError, "'event' option is required" unless metric_event.present? raise ArgumentError, "'event' option is required" unless metric_event.present?
raise ArgumentError, "'counter class' option is required" unless counter_class.present? raise ArgumentError, "'prefix' option is required" unless prefix.present?
end end
def metric_event def metric_event
options[:event] options[:event]
end end
def counter_class_name def prefix
options[:counter_class] options[:prefix]
end
def counter_class
"Gitlab::UsageDataCounters::#{counter_class_name}".constantize
end end
def value def value
redis_usage_data do redis_usage_data do
counter_class.read(metric_event) total_count(redis_key)
end end
end end
def suggested_name def suggested_name
Gitlab::Usage::Metrics::NameSuggestion.for(:redis) Gitlab::Usage::Metrics::NameSuggestion.for(:redis)
end end
private
def redis_key
"USAGE_#{prefix}_#{metric_event}".upcase
end
end end
end end
end end

View File

@ -17962,6 +17962,9 @@ msgstr ""
msgid "Global Shortcuts" msgid "Global Shortcuts"
msgstr "" msgstr ""
msgid "Global notification level"
msgstr ""
msgid "Global notification settings" msgid "Global notification settings"
msgstr "" msgstr ""
@ -18049,6 +18052,12 @@ msgstr ""
msgid "GlobalSearch|project" msgid "GlobalSearch|project"
msgstr "" msgstr ""
msgid "GlobalShortcuts|Copied source branch name to clipboard."
msgstr ""
msgid "GlobalShortcuts|Unable to copy the source branch name at this time."
msgstr ""
msgid "Globally-allowed IP ranges" msgid "Globally-allowed IP ranges"
msgstr "" msgstr ""
@ -26698,6 +26707,9 @@ msgstr ""
msgid "Nothing to preview." msgid "Nothing to preview."
msgstr "" msgstr ""
msgid "Notification Email"
msgstr ""
msgid "Notification events" msgid "Notification events"
msgstr "" msgstr ""

View File

@ -53,7 +53,7 @@
"@gitlab/at.js": "1.5.7", "@gitlab/at.js": "1.5.7",
"@gitlab/favicon-overlay": "2.0.0", "@gitlab/favicon-overlay": "2.0.0",
"@gitlab/svgs": "3.3.0", "@gitlab/svgs": "3.3.0",
"@gitlab/ui": "43.9.3", "@gitlab/ui": "43.13.0",
"@gitlab/visual-review-tools": "1.7.3", "@gitlab/visual-review-tools": "1.7.3",
"@gitlab/web-ide": "0.0.1-dev-20220815034418", "@gitlab/web-ide": "0.0.1-dev-20220815034418",
"@rails/actioncable": "6.1.4-7", "@rails/actioncable": "6.1.4-7",
@ -197,7 +197,7 @@
"yaml": "^2.0.0-10" "yaml": "^2.0.0-10"
}, },
"devDependencies": { "devDependencies": {
"@gitlab/eslint-plugin": "16.0.0", "@gitlab/eslint-plugin": "17.0.0",
"@gitlab/stylelint-config": "4.1.0", "@gitlab/stylelint-config": "4.1.0",
"@graphql-eslint/eslint-plugin": "3.10.7", "@graphql-eslint/eslint-plugin": "3.10.7",
"@testing-library/dom": "^7.16.2", "@testing-library/dom": "^7.16.2",

View File

@ -31,9 +31,14 @@ RSpec.describe 'Database schema' do
boards: %w[milestone_id iteration_id], boards: %w[milestone_id iteration_id],
chat_names: %w[chat_id team_id user_id], chat_names: %w[chat_id team_id user_id],
chat_teams: %w[team_id], chat_teams: %w[team_id],
ci_builds: %w[erased_by_id trigger_request_id], ci_builds: %w[erased_by_id trigger_request_id partition_id],
ci_builds_metadata: %w[partition_id],
ci_job_artifacts: %w[partition_id],
ci_namespace_monthly_usages: %w[namespace_id], ci_namespace_monthly_usages: %w[namespace_id],
ci_pipeline_variables: %w[partition_id],
ci_pipelines: %w[partition_id],
ci_runner_projects: %w[runner_id], ci_runner_projects: %w[runner_id],
ci_stages: %w[partition_id],
ci_trigger_requests: %w[commit_id], ci_trigger_requests: %w[commit_id],
cluster_providers_aws: %w[security_group_id vpc_id access_key_id], cluster_providers_aws: %w[security_group_id vpc_id access_key_id],
cluster_providers_gcp: %w[gcp_project_id operation_id], cluster_providers_gcp: %w[gcp_project_id operation_id],

View File

@ -1,8 +1,8 @@
import { shallowMount } from '@vue/test-utils';
import Vue, { nextTick } from 'vue'; import Vue, { nextTick } from 'vue';
import Vuex from 'vuex'; import Vuex from 'vuex';
import { GlDropdown, GlDropdownItem } from '@gitlab/ui'; import { GlDropdown, GlDropdownItem } from '@gitlab/ui';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import BoardCardMoveToPosition from '~/boards/components/board_card_move_to_position.vue'; import BoardCardMoveToPosition from '~/boards/components/board_card_move_to_position.vue';
import { mockList, mockIssue2, mockIssue, mockIssue3, mockIssue4 } from 'jest/boards/mock_data'; import { mockList, mockIssue2, mockIssue, mockIssue3, mockIssue4 } from 'jest/boards/mock_data';
import { mockTracking, unmockTracking } from 'helpers/tracking_helper'; import { mockTracking, unmockTracking } from 'helpers/tracking_helper';
@ -19,6 +19,7 @@ describe('Board Card Move to position', () => {
let trackingSpy; let trackingSpy;
let store; let store;
let dispatch; let dispatch;
const itemIndex = 1;
const createStoreOptions = () => { const createStoreOptions = () => {
const state = { const state = {
@ -42,7 +43,7 @@ describe('Board Card Move to position', () => {
}; };
const createComponent = (propsData) => { const createComponent = (propsData) => {
wrapper = shallowMountExtended(BoardCardMoveToPosition, { wrapper = shallowMount(BoardCardMoveToPosition, {
store, store,
propsData: { propsData: {
item: mockIssue2, item: mockIssue2,
@ -66,7 +67,6 @@ describe('Board Card Move to position', () => {
wrapper.destroy(); wrapper.destroy();
}); });
const findEllipsesButton = () => wrapper.findByTestId('move-card-dropdown');
const findMoveToPositionDropdown = () => wrapper.findComponent(GlDropdown); const findMoveToPositionDropdown = () => wrapper.findComponent(GlDropdown);
const findDropdownItems = () => findMoveToPositionDropdown().findAllComponents(GlDropdownItem); const findDropdownItems = () => findMoveToPositionDropdown().findAllComponents(GlDropdownItem);
const findDropdownItemAtIndex = (index) => findDropdownItems().at(index); const findDropdownItemAtIndex = (index) => findDropdownItems().at(index);
@ -74,7 +74,7 @@ describe('Board Card Move to position', () => {
describe('Dropdown', () => { describe('Dropdown', () => {
describe('Dropdown button', () => { describe('Dropdown button', () => {
it('has an icon with vertical ellipsis', () => { it('has an icon with vertical ellipsis', () => {
expect(findEllipsesButton().exists()).toBe(true); expect(findMoveToPositionDropdown().exists()).toBe(true);
expect(findMoveToPositionDropdown().props('icon')).toBe('ellipsis_v'); expect(findMoveToPositionDropdown().props('icon')).toBe('ellipsis_v');
}); });
@ -82,24 +82,11 @@ describe('Board Card Move to position', () => {
findMoveToPositionDropdown().vm.$emit('click'); findMoveToPositionDropdown().vm.$emit('click');
expect(findDropdownItems()).toHaveLength(dropdownOptions.length); expect(findDropdownItems()).toHaveLength(dropdownOptions.length);
}); });
it('is opened on the click of vertical ellipsis and has 1 dropdown items when number of list items > 10', () => {
wrapper.destroy();
createComponent({
list: {
...mockList,
id: 'gid://gitlab/List/2',
},
});
findMoveToPositionDropdown().vm.$emit('click');
expect(findDropdownItems()).toHaveLength(1);
});
}); });
describe('Dropdown options', () => { describe('Dropdown options', () => {
beforeEach(() => { beforeEach(() => {
createComponent({ index: 1 }); createComponent({ index: itemIndex });
trackingSpy = mockTracking(undefined, wrapper.element, jest.spyOn); trackingSpy = mockTracking(undefined, wrapper.element, jest.spyOn);
dispatch = jest.spyOn(store, 'dispatch').mockImplementation(() => {}); dispatch = jest.spyOn(store, 'dispatch').mockImplementation(() => {});
}); });
@ -109,13 +96,13 @@ describe('Board Card Move to position', () => {
}); });
it.each` it.each`
dropdownIndex | dropdownLabel | trackLabel | moveAfterId | moveBeforeId dropdownIndex | dropdownLabel | trackLabel | positionInList
${0} | ${BoardCardMoveToPosition.i18n.moveToStartText} | ${'move_to_start'} | ${mockIssue.id} | ${undefined} ${0} | ${BoardCardMoveToPosition.i18n.moveToStartText} | ${'move_to_start'} | ${0}
${1} | ${BoardCardMoveToPosition.i18n.moveToEndText} | ${'move_to_end'} | ${undefined} | ${mockIssue4.id} ${1} | ${BoardCardMoveToPosition.i18n.moveToEndText} | ${'move_to_end'} | ${-1}
`( `(
'on click of dropdown index $dropdownIndex with label $dropdownLabel should call moveItem action with tracking label $trackLabel', 'on click of dropdown index $dropdownIndex with label $dropdownLabel should call moveItem action with tracking label $trackLabel',
async ({ dropdownIndex, dropdownLabel, trackLabel, moveAfterId, moveBeforeId }) => { async ({ dropdownIndex, dropdownLabel, trackLabel, positionInList }) => {
await findEllipsesButton().vm.$emit('click'); await findMoveToPositionDropdown().vm.$emit('click');
expect(findDropdownItemAtIndex(dropdownIndex).text()).toBe(dropdownLabel); expect(findDropdownItemAtIndex(dropdownIndex).text()).toBe(dropdownLabel);
await findDropdownItemAtIndex(dropdownIndex).vm.$emit('click', { await findDropdownItemAtIndex(dropdownIndex).vm.$emit('click', {
@ -134,9 +121,10 @@ describe('Board Card Move to position', () => {
itemId: mockIssue2.id, itemId: mockIssue2.id,
itemIid: mockIssue2.iid, itemIid: mockIssue2.iid,
itemPath: mockIssue2.referencePath, itemPath: mockIssue2.referencePath,
moveBeforeId, positionInList,
moveAfterId,
toListId: mockList.id, toListId: mockList.id,
allItemsLoadedInList: true,
atIndex: itemIndex,
}); });
}, },
); );

View File

@ -1056,6 +1056,8 @@ describe('moveIssueCard and undoMoveIssueCard', () => {
originalIndex = 0, originalIndex = 0,
moveBeforeId = undefined, moveBeforeId = undefined,
moveAfterId = undefined, moveAfterId = undefined,
allItemsLoadedInList = true,
listPosition = undefined,
} = {}) => { } = {}) => {
state = { state = {
boardLists: { boardLists: {
@ -1065,12 +1067,28 @@ describe('moveIssueCard and undoMoveIssueCard', () => {
boardItems: { [itemId]: originalIssue }, boardItems: { [itemId]: originalIssue },
boardItemsByListId: { [fromListId]: [123] }, boardItemsByListId: { [fromListId]: [123] },
}; };
params = { itemId, fromListId, toListId, moveBeforeId, moveAfterId }; params = {
itemId,
fromListId,
toListId,
moveBeforeId,
moveAfterId,
listPosition,
allItemsLoadedInList,
};
moveMutations = [ moveMutations = [
{ type: types.REMOVE_BOARD_ITEM_FROM_LIST, payload: { itemId, listId: fromListId } }, { type: types.REMOVE_BOARD_ITEM_FROM_LIST, payload: { itemId, listId: fromListId } },
{ {
type: types.ADD_BOARD_ITEM_TO_LIST, type: types.ADD_BOARD_ITEM_TO_LIST,
payload: { itemId, listId: toListId, moveBeforeId, moveAfterId }, payload: {
itemId,
listId: toListId,
moveBeforeId,
moveAfterId,
listPosition,
allItemsLoadedInList,
atIndex: originalIndex,
},
}, },
]; ];
undoMutations = [ undoMutations = [
@ -1365,10 +1383,18 @@ describe('updateIssueOrder', () => {
{ moveData }, { moveData },
state, state,
[ [
{
type: types.MUTATE_ISSUE_IN_PROGRESS,
payload: true,
},
{ {
type: types.MUTATE_ISSUE_SUCCESS, type: types.MUTATE_ISSUE_SUCCESS,
payload: { issue: rawIssue }, payload: { issue: rawIssue },
}, },
{
type: types.MUTATE_ISSUE_IN_PROGRESS,
payload: false,
},
], ],
[], [],
); );
@ -1389,6 +1415,14 @@ describe('updateIssueOrder', () => {
{ moveData }, { moveData },
state, state,
[ [
{
type: types.MUTATE_ISSUE_IN_PROGRESS,
payload: true,
},
{
type: types.MUTATE_ISSUE_IN_PROGRESS,
payload: false,
},
{ {
type: types.SET_ERROR, type: types.SET_ERROR,
payload: 'An error occurred while moving the issue. Please try again.', payload: 'An error occurred while moving the issue. Please try again.',

View File

@ -513,6 +513,31 @@ describe('Board Store Mutations', () => {
listState: [mockIssue2.id, mockIssue.id], listState: [mockIssue2.id, mockIssue.id],
}, },
], ],
[
'to the top of the list',
{
payload: {
itemId: mockIssue2.id,
listId: mockList.id,
positionInList: 0,
atIndex: 1,
},
listState: [mockIssue2.id, mockIssue.id],
},
],
[
'to the bottom of the list when the list is fully loaded',
{
payload: {
itemId: mockIssue2.id,
listId: mockList.id,
positionInList: -1,
atIndex: 0,
allItemsLoadedInList: true,
},
listState: [mockIssue.id, mockIssue2.id],
},
],
])(`inserts an item into a list %s`, (_, { payload, listState }) => { ])(`inserts an item into a list %s`, (_, { payload, listState }) => {
mutations.ADD_BOARD_ITEM_TO_LIST(state, payload); mutations.ADD_BOARD_ITEM_TO_LIST(state, payload);

View File

@ -210,7 +210,7 @@ describe('IDE commit module actions', () => {
branch, branch,
}); });
store.state.openFiles.forEach((entry) => { store.state.openFiles.forEach((entry) => {
expect(entry.changed).toBeFalsy(); expect(entry.changed).toBe(false);
}); });
}); });

View File

@ -11,18 +11,18 @@ RSpec.describe Gitlab::Usage::Metrics::Instrumentations::RedisMetric, :clean_git
let(:expected_value) { 4 } let(:expected_value) { 4 }
it_behaves_like 'a correct instrumented metric value', { options: { event: 'pushes', counter_class: 'SourceCodeCounter' } } it_behaves_like 'a correct instrumented metric value', { options: { event: 'pushes', prefix: 'source_code' } }
it 'raises an exception if event option is not present' do it 'raises an exception if event option is not present' do
expect { described_class.new(counter_class: 'SourceCodeCounter') }.to raise_error(ArgumentError) expect { described_class.new(prefix: 'source_code') }.to raise_error(ArgumentError)
end end
it 'raises an exception if counter_class option is not present' do it 'raises an exception if prefix option is not present' do
expect { described_class.new(event: 'pushes') }.to raise_error(ArgumentError) expect { described_class.new(event: 'pushes') }.to raise_error(ArgumentError)
end end
describe 'children classes' do describe 'children classes' do
let(:options) { { event: 'pushes', counter_class: 'SourceCodeCounter' } } let(:options) { { event: 'pushes', prefix: 'source_code' } }
context 'availability not defined' do context 'availability not defined' do
subject { Class.new(described_class).new(time_frame: nil, options: options) } subject { Class.new(described_class).new(time_frame: nil, options: options) }

View File

@ -1027,10 +1027,10 @@
resolved "https://registry.yarnpkg.com/@gitlab/at.js/-/at.js-1.5.7.tgz#1ee6f838cc4410a1d797770934df91d90df8179e" resolved "https://registry.yarnpkg.com/@gitlab/at.js/-/at.js-1.5.7.tgz#1ee6f838cc4410a1d797770934df91d90df8179e"
integrity sha512-c6ySRK/Ma7lxwpIVbSAF3P+xiTLrNTGTLRx4/pHK111AdFxwgUwrYF6aVZFXvmG65jHOJHoa0eQQ21RW6rm0Rg== integrity sha512-c6ySRK/Ma7lxwpIVbSAF3P+xiTLrNTGTLRx4/pHK111AdFxwgUwrYF6aVZFXvmG65jHOJHoa0eQQ21RW6rm0Rg==
"@gitlab/eslint-plugin@16.0.0": "@gitlab/eslint-plugin@17.0.0":
version "16.0.0" version "17.0.0"
resolved "https://registry.yarnpkg.com/@gitlab/eslint-plugin/-/eslint-plugin-16.0.0.tgz#83b71bb3f749c6e52138d2c1c17ac623e7b2e3db" resolved "https://registry.yarnpkg.com/@gitlab/eslint-plugin/-/eslint-plugin-17.0.0.tgz#5451fbbad96b09d812af2afb247f6602fe0be6c6"
integrity sha512-2n7geoRPkeMAq4GCqyvFzcTgcSrTM7pdCOxfcqIeuTmh/PFGhh+m7YC+YC4enhGOCN8lo08buLZhXkSgWiHSqA== integrity sha512-c+sJtjzYl+KGPtZScU8Mji9seJw7dSEn31APyYEYTyWp72yMsFvXmg46txT2QCz+ueZlqk0/C2IQmgfe6fLcBw==
dependencies: dependencies:
"@babel/core" "^7.17.0" "@babel/core" "^7.17.0"
"@babel/eslint-parser" "^7.17.0" "@babel/eslint-parser" "^7.17.0"
@ -1064,10 +1064,10 @@
resolved "https://registry.yarnpkg.com/@gitlab/svgs/-/svgs-3.3.0.tgz#99b044484fcf3d5a6431281e320e2405540ff5a9" resolved "https://registry.yarnpkg.com/@gitlab/svgs/-/svgs-3.3.0.tgz#99b044484fcf3d5a6431281e320e2405540ff5a9"
integrity sha512-S8Hqf+ms8aNrSgmci9SVoIyj/0qQnizU5uV5vUPAOwiufMDFDyI5qfcgn4EYZ6mnju3LiO+ReSL/PPTD4qNgHA== integrity sha512-S8Hqf+ms8aNrSgmci9SVoIyj/0qQnizU5uV5vUPAOwiufMDFDyI5qfcgn4EYZ6mnju3LiO+ReSL/PPTD4qNgHA==
"@gitlab/ui@43.9.3": "@gitlab/ui@43.13.0":
version "43.9.3" version "43.13.0"
resolved "https://registry.yarnpkg.com/@gitlab/ui/-/ui-43.9.3.tgz#2dd91b14da769a873e45ffe07b5863f6c47211ba" resolved "https://registry.yarnpkg.com/@gitlab/ui/-/ui-43.13.0.tgz#7e4e7d41287cfba8a46dbdd3c8ba998a853a9ad2"
integrity sha512-TONSf+6UJYWTVs5qnItR1uLZ/0kBE8jGN8aLOVv4CDAsORvln0ZxtcZvMTCFp76YEtzXLkMUfvm7ZngQ26tIiA== integrity sha512-y0BrVKsqRBEQMrsJseakBeMrFHVMTg7DVMa3tbdkKkrruV8SYOsX8wLrv20taDhiMlceKRB8lF5gLTPPHLwCGA==
dependencies: dependencies:
"@popperjs/core" "^2.11.2" "@popperjs/core" "^2.11.2"
bootstrap-vue "2.20.1" bootstrap-vue "2.20.1"