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 ClipboardJS from 'clipboard';
import Mousetrap from 'mousetrap';
import { clickCopyToClipboardButton } from '~/behaviors/copy_to_clipboard';
import { getSelectedFragment } from '~/lib/utils/common_utils';
import { isElementVisible } from '~/lib/utils/dom_utils';
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 { CopyAsGFM } from '../markdown/copy_as_gfm';
import {
@ -21,6 +23,15 @@ export default class ShortcutsIssuable extends Shortcuts {
constructor() {
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), () =>
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_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
@ -153,17 +164,14 @@ export default class ShortcutsIssuable extends Shortcuts {
return false;
}
static copyBranchName() {
// There are two buttons - one that is shown when the sidebar
// is expanded, and one that is shown when it's collapsed.
const allCopyBtns = Array.from(document.querySelectorAll('.js-source-branch-copy'));
async copyBranchName() {
const button = document.querySelector('.js-source-branch-copy');
const branchName = button?.dataset.clipboardText;
// Select whichever button is currently visible so that
// the "Copied" tooltip is shown when a click is simulated.
const visibleBtn = allCopyBtns.find(isElementVisible);
if (branchName) {
this.inMemoryButton.dataset.clipboardText = branchName;
if (visibleBtn) {
clickCopyToClipboardButton(visibleBtn);
this.inMemoryButton.dispatchEvent(new CustomEvent('click'));
}
}
}

View File

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

View File

@ -66,7 +66,7 @@ export default {
},
},
computed: {
...mapState(['pageInfoByListId', 'listsFlags', 'filterParams']),
...mapState(['pageInfoByListId', 'listsFlags', 'filterParams', 'isUpdateIssueOrderInProgress']),
...mapGetters(['isEpicBoard']),
listItemsCount() {
return this.isEpicBoard ? this.list.epicsCount : this.boardList?.issuesCount;
@ -132,6 +132,9 @@ export default {
return this.canMoveIssue ? options : {};
},
disableScrollingWhenMutationInProgress() {
return this.hasNextPage && this.isUpdateIssueOrderInProgress;
},
},
watch: {
boardItems() {
@ -285,9 +288,13 @@ export default {
v-bind="treeRootOptions"
:data-board="list.id"
: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"
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"
@start="handleDragOnStart"
@end="handleDragOnEnd"

View File

@ -479,16 +479,25 @@ export default {
toListId,
moveBeforeId,
moveAfterId,
positionInList,
allItemsLoadedInList,
} = moveData;
commit(types.REMOVE_BOARD_ITEM_FROM_LIST, { itemId, listId: fromListId });
if (reordering && !allItemsLoadedInList && positionInList === -1) {
return;
}
if (reordering) {
commit(types.ADD_BOARD_ITEM_TO_LIST, {
itemId,
listId: toListId,
moveBeforeId,
moveAfterId,
positionInList,
atIndex: originalIndex,
allItemsLoadedInList,
});
return;
@ -500,6 +509,7 @@ export default {
listId: toListId,
moveBeforeId,
moveAfterId,
positionInList,
});
}
@ -553,7 +563,15 @@ export default {
updateIssueOrder: async ({ commit, dispatch, state }, { moveData, mutationVariables = {} }) => {
try {
const { itemId, fromListId, toListId, moveBeforeId, moveAfterId, itemNotInToList } = moveData;
const {
itemId,
fromListId,
toListId,
moveBeforeId,
moveAfterId,
itemNotInToList,
positionInList,
} = moveData;
const {
fullBoardId,
filterParams,
@ -562,6 +580,8 @@ export default {
},
} = state;
commit(types.MUTATE_ISSUE_IN_PROGRESS, true);
const { data } = await gqlClient.mutate({
mutation: issueMoveListMutation,
variables: {
@ -572,6 +592,7 @@ export default {
toListId: getIdFromGraphQLId(toListId),
moveBeforeId: moveBeforeId ? getIdFromGraphQLId(moveBeforeId) : undefined,
moveAfterId: moveAfterId ? getIdFromGraphQLId(moveAfterId) : undefined,
positionInList,
// 'mutationVariables' allows EE code to pass in extra parameters.
...mutationVariables,
},
@ -643,7 +664,9 @@ export default {
}
commit(types.MUTATE_ISSUE_SUCCESS, { issue: data.issueMoveList.issue });
commit(types.MUTATE_ISSUE_IN_PROGRESS, false);
} catch {
commit(types.MUTATE_ISSUE_IN_PROGRESS, false);
commit(
types.SET_ERROR,
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 RESET_BOARD_ITEM_SELECTION = 'RESET_BOARD_ITEM_SELECTION';
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 });
};
export const addItemToList = ({ state, listId, itemId, moveBeforeId, moveAfterId, atIndex }) => {
export const addItemToList = ({
state,
listId,
itemId,
moveBeforeId,
moveAfterId,
atIndex,
positionInList,
}) => {
const listIssues = state.boardItemsByListId[listId];
let newIndex = atIndex || 0;
const moveToStartOrLast = positionInList !== undefined;
if (moveBeforeId) {
newIndex = listIssues.indexOf(moveBeforeId) + 1;
} else if (moveAfterId) {
newIndex = listIssues.indexOf(moveAfterId);
} else if (moveToStartOrLast) {
newIndex = positionInList === -1 ? listIssues.length : 0;
}
listIssues.splice(newIndex, 0, itemId);
Vue.set(state.boardItemsByListId, listId, listIssues);
updateListItemsCount({ state, listId, value: 1 });
updateListItemsCount({ state, listId, value: moveToStartOrLast ? 0 : 1 });
};
export default {
@ -205,12 +216,34 @@ export default {
Vue.set(state.boardItems, issue.id, formatIssue(issue));
},
[mutationTypes.MUTATE_ISSUE_IN_PROGRESS](state, isLoading) {
state.isUpdateIssueOrderInProgress = isLoading;
},
[mutationTypes.ADD_BOARD_ITEM_TO_LIST]: (
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 });
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 }) => {

View File

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

View File

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

View File

@ -217,7 +217,8 @@ module Ci
allow_failure stage stage_idx trigger_request
yaml_variables when environment coverage_regex
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

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-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)
.help-block
= 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|
= 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
.clearfix
.form-group.float-left.global-notification-setting

View File

@ -12,7 +12,7 @@ time_frame: all
data_source: redis
instrumentation_class: RedisMetric
options:
counter_class: SourceCodeCounter
prefix: source_code
event: pushes
distribution:
- 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,
id bigint NOT NULL,
stage_id bigint,
partition_id bigint DEFAULT 100 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,
id bigint 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
@ -12823,6 +12825,7 @@ CREATE TABLE ci_job_artifacts (
job_id bigint NOT NULL,
locked smallint DEFAULT 2,
original_filename text,
partition_id bigint DEFAULT 100 NOT NULL,
CONSTRAINT check_27f0f6dbab CHECK ((file_store IS NOT NULL)),
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;
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 (
id bigint NOT NULL,
build_id bigint NOT NULL,
@ -13071,7 +13089,8 @@ CREATE TABLE ci_pipeline_variables (
encrypted_value_iv character varying,
pipeline_id integer 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
@ -13113,6 +13132,7 @@ CREATE TABLE ci_pipelines (
external_pull_request_id bigint,
ci_ref_id bigint,
locked smallint DEFAULT 1 NOT NULL,
partition_id bigint DEFAULT 100 NOT NULL,
CONSTRAINT check_d7e99a025e CHECK ((lock_version IS NOT NULL))
);
@ -13417,6 +13437,7 @@ CREATE TABLE ci_stages (
lock_version integer DEFAULT 0,
"position" integer,
id bigint NOT NULL,
partition_id bigint DEFAULT 100 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_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_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
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
ADD CONSTRAINT ci_pending_builds_pkey PRIMARY KEY (id);

View File

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

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.
[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.
Required options:
- `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
time_frame: all
@ -169,7 +171,7 @@ data_source: redis
instrumentation_class: 'RedisMetric'
options:
event: pushes
counter_class: SourceCodeCounter
prefix: source_code
```
### Availability-restrained Redis metrics
@ -200,7 +202,7 @@ data_source: redis
instrumentation_class: 'MergeUsageCountRedisMetric'
options:
event: pushes
counter_class: SourceCodeCounter
prefix: source_code
```
## Redis HyperLogLog metrics

View File

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

View File

@ -11,37 +11,41 @@ module Gitlab
# instrumentation_class: RedisMetric
# options:
# event: pushes
# counter_class: SourceCodeCounter
# prefix: source_code
#
class RedisMetric < BaseMetric
include Gitlab::UsageDataCounters::RedisCounter
def initialize(time_frame:, options: {})
super
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
def metric_event
options[:event]
end
def counter_class_name
options[:counter_class]
end
def counter_class
"Gitlab::UsageDataCounters::#{counter_class_name}".constantize
def prefix
options[:prefix]
end
def value
redis_usage_data do
counter_class.read(metric_event)
total_count(redis_key)
end
end
def suggested_name
Gitlab::Usage::Metrics::NameSuggestion.for(:redis)
end
private
def redis_key
"USAGE_#{prefix}_#{metric_event}".upcase
end
end
end
end

View File

@ -17962,6 +17962,9 @@ msgstr ""
msgid "Global Shortcuts"
msgstr ""
msgid "Global notification level"
msgstr ""
msgid "Global notification settings"
msgstr ""
@ -18049,6 +18052,12 @@ msgstr ""
msgid "GlobalSearch|project"
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"
msgstr ""
@ -26698,6 +26707,9 @@ msgstr ""
msgid "Nothing to preview."
msgstr ""
msgid "Notification Email"
msgstr ""
msgid "Notification events"
msgstr ""

View File

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

View File

@ -31,9 +31,14 @@ RSpec.describe 'Database schema' do
boards: %w[milestone_id iteration_id],
chat_names: %w[chat_id team_id user_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_pipeline_variables: %w[partition_id],
ci_pipelines: %w[partition_id],
ci_runner_projects: %w[runner_id],
ci_stages: %w[partition_id],
ci_trigger_requests: %w[commit_id],
cluster_providers_aws: %w[security_group_id vpc_id access_key_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 Vuex from 'vuex';
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 { mockList, mockIssue2, mockIssue, mockIssue3, mockIssue4 } from 'jest/boards/mock_data';
import { mockTracking, unmockTracking } from 'helpers/tracking_helper';
@ -19,6 +19,7 @@ describe('Board Card Move to position', () => {
let trackingSpy;
let store;
let dispatch;
const itemIndex = 1;
const createStoreOptions = () => {
const state = {
@ -42,7 +43,7 @@ describe('Board Card Move to position', () => {
};
const createComponent = (propsData) => {
wrapper = shallowMountExtended(BoardCardMoveToPosition, {
wrapper = shallowMount(BoardCardMoveToPosition, {
store,
propsData: {
item: mockIssue2,
@ -66,7 +67,6 @@ describe('Board Card Move to position', () => {
wrapper.destroy();
});
const findEllipsesButton = () => wrapper.findByTestId('move-card-dropdown');
const findMoveToPositionDropdown = () => wrapper.findComponent(GlDropdown);
const findDropdownItems = () => findMoveToPositionDropdown().findAllComponents(GlDropdownItem);
const findDropdownItemAtIndex = (index) => findDropdownItems().at(index);
@ -74,7 +74,7 @@ describe('Board Card Move to position', () => {
describe('Dropdown', () => {
describe('Dropdown button', () => {
it('has an icon with vertical ellipsis', () => {
expect(findEllipsesButton().exists()).toBe(true);
expect(findMoveToPositionDropdown().exists()).toBe(true);
expect(findMoveToPositionDropdown().props('icon')).toBe('ellipsis_v');
});
@ -82,24 +82,11 @@ describe('Board Card Move to position', () => {
findMoveToPositionDropdown().vm.$emit('click');
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', () => {
beforeEach(() => {
createComponent({ index: 1 });
createComponent({ index: itemIndex });
trackingSpy = mockTracking(undefined, wrapper.element, jest.spyOn);
dispatch = jest.spyOn(store, 'dispatch').mockImplementation(() => {});
});
@ -109,13 +96,13 @@ describe('Board Card Move to position', () => {
});
it.each`
dropdownIndex | dropdownLabel | trackLabel | moveAfterId | moveBeforeId
${0} | ${BoardCardMoveToPosition.i18n.moveToStartText} | ${'move_to_start'} | ${mockIssue.id} | ${undefined}
${1} | ${BoardCardMoveToPosition.i18n.moveToEndText} | ${'move_to_end'} | ${undefined} | ${mockIssue4.id}
dropdownIndex | dropdownLabel | trackLabel | positionInList
${0} | ${BoardCardMoveToPosition.i18n.moveToStartText} | ${'move_to_start'} | ${0}
${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',
async ({ dropdownIndex, dropdownLabel, trackLabel, moveAfterId, moveBeforeId }) => {
await findEllipsesButton().vm.$emit('click');
async ({ dropdownIndex, dropdownLabel, trackLabel, positionInList }) => {
await findMoveToPositionDropdown().vm.$emit('click');
expect(findDropdownItemAtIndex(dropdownIndex).text()).toBe(dropdownLabel);
await findDropdownItemAtIndex(dropdownIndex).vm.$emit('click', {
@ -134,9 +121,10 @@ describe('Board Card Move to position', () => {
itemId: mockIssue2.id,
itemIid: mockIssue2.iid,
itemPath: mockIssue2.referencePath,
moveBeforeId,
moveAfterId,
positionInList,
toListId: mockList.id,
allItemsLoadedInList: true,
atIndex: itemIndex,
});
},
);

View File

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

View File

@ -210,7 +210,7 @@ describe('IDE commit module actions', () => {
branch,
});
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 }
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
expect { described_class.new(counter_class: 'SourceCodeCounter') }.to raise_error(ArgumentError)
expect { described_class.new(prefix: 'source_code') }.to raise_error(ArgumentError)
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)
end
describe 'children classes' do
let(:options) { { event: 'pushes', counter_class: 'SourceCodeCounter' } }
let(:options) { { event: 'pushes', prefix: 'source_code' } }
context 'availability not defined' do
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"
integrity sha512-c6ySRK/Ma7lxwpIVbSAF3P+xiTLrNTGTLRx4/pHK111AdFxwgUwrYF6aVZFXvmG65jHOJHoa0eQQ21RW6rm0Rg==
"@gitlab/eslint-plugin@16.0.0":
version "16.0.0"
resolved "https://registry.yarnpkg.com/@gitlab/eslint-plugin/-/eslint-plugin-16.0.0.tgz#83b71bb3f749c6e52138d2c1c17ac623e7b2e3db"
integrity sha512-2n7geoRPkeMAq4GCqyvFzcTgcSrTM7pdCOxfcqIeuTmh/PFGhh+m7YC+YC4enhGOCN8lo08buLZhXkSgWiHSqA==
"@gitlab/eslint-plugin@17.0.0":
version "17.0.0"
resolved "https://registry.yarnpkg.com/@gitlab/eslint-plugin/-/eslint-plugin-17.0.0.tgz#5451fbbad96b09d812af2afb247f6602fe0be6c6"
integrity sha512-c+sJtjzYl+KGPtZScU8Mji9seJw7dSEn31APyYEYTyWp72yMsFvXmg46txT2QCz+ueZlqk0/C2IQmgfe6fLcBw==
dependencies:
"@babel/core" "^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"
integrity sha512-S8Hqf+ms8aNrSgmci9SVoIyj/0qQnizU5uV5vUPAOwiufMDFDyI5qfcgn4EYZ6mnju3LiO+ReSL/PPTD4qNgHA==
"@gitlab/ui@43.9.3":
version "43.9.3"
resolved "https://registry.yarnpkg.com/@gitlab/ui/-/ui-43.9.3.tgz#2dd91b14da769a873e45ffe07b5863f6c47211ba"
integrity sha512-TONSf+6UJYWTVs5qnItR1uLZ/0kBE8jGN8aLOVv4CDAsORvln0ZxtcZvMTCFp76YEtzXLkMUfvm7ZngQ26tIiA==
"@gitlab/ui@43.13.0":
version "43.13.0"
resolved "https://registry.yarnpkg.com/@gitlab/ui/-/ui-43.13.0.tgz#7e4e7d41287cfba8a46dbdd3c8ba998a853a9ad2"
integrity sha512-y0BrVKsqRBEQMrsJseakBeMrFHVMTg7DVMa3tbdkKkrruV8SYOsX8wLrv20taDhiMlceKRB8lF5gLTPPHLwCGA==
dependencies:
"@popperjs/core" "^2.11.2"
bootstrap-vue "2.20.1"