Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--app/assets/javascripts/gfm_auto_complete.js39
-rw-r--r--app/assets/javascripts/mr_notes/init_notes.js8
-rw-r--r--app/assets/javascripts/notes/components/note_form.vue6
-rw-r--r--app/controllers/projects/google_cloud/gcp_regions_controller.rb1
-rw-r--r--app/controllers/projects/google_cloud/service_accounts_controller.rb2
-rw-r--r--app/models/ci/secure_file.rb3
-rw-r--r--app/views/admin/labels/_label.html.haml2
-rw-r--r--app/views/shared/_search_settings.html.haml2
-rw-r--r--config/initializers_before_autoloader/000_inflections.rb1
-rw-r--r--doc/administration/monitoring/prometheus/gitlab_metrics.md10
-rw-r--r--doc/api/geo_nodes.md26
-rw-r--r--doc/api/graphql/reference/index.md58
-rw-r--r--doc/development/snowplow/index.md7
-rw-r--r--lib/gitlab/database/migration_helpers.rb4
-rw-r--r--lib/google_api/cloud_platform/client.rb2
-rw-r--r--locale/gitlab.pot2
-rw-r--r--spec/factories/ci/secure_files.rb6
-rw-r--r--spec/features/merge_request/user_comments_on_merge_request_spec.rb39
-rw-r--r--spec/frontend/gfm_auto_complete/mock_data.js34
-rw-r--r--spec/frontend/gfm_auto_complete_spec.js48
-rw-r--r--spec/frontend/notes/components/note_form_spec.js16
-rw-r--r--spec/lib/gitlab/database/migration_helpers_spec.rb9
-rw-r--r--spec/lib/google_api/cloud_platform/client_spec.rb9
23 files changed, 309 insertions, 25 deletions
diff --git a/app/assets/javascripts/gfm_auto_complete.js b/app/assets/javascripts/gfm_auto_complete.js
index 7aea3715971..bd87d0df01a 100644
--- a/app/assets/javascripts/gfm_auto_complete.js
+++ b/app/assets/javascripts/gfm_auto_complete.js
@@ -12,6 +12,14 @@ import { spriteIcon } from './lib/utils/common_utils';
import { parsePikadayDate } from './lib/utils/datetime_utility';
import glRegexp from './lib/utils/regexp';
+const USERS_ALIAS = 'users';
+const ISSUES_ALIAS = 'issues';
+const MILESTONES_ALIAS = 'milestones';
+const MERGEREQUESTS_ALIAS = 'mergerequests';
+const LABELS_ALIAS = 'labels';
+const SNIPPETS_ALIAS = 'snippets';
+const CONTACTS_ALIAS = 'contacts';
+export const AT_WHO_ACTIVE_CLASS = 'at-who-active';
/**
* Escapes user input before we pass it to at.js, which
* renders it as HTML in the autocomplete dropdown.
@@ -29,6 +37,15 @@ function escape(string) {
return lodashEscape(string).replace(/\$/g, '$');
}
+export function showAndHideHelper($input, alias = '') {
+ $input.on(`hidden${alias ? '-' : ''}${alias}.atwho`, () => {
+ $input.removeClass(AT_WHO_ACTIVE_CLASS);
+ });
+ $input.on(`shown${alias ? '-' : ''}${alias}.atwho`, () => {
+ $input.addClass(AT_WHO_ACTIVE_CLASS);
+ });
+}
+
function createMemberSearchString(member) {
return `${member.name.replace(/ /g, '')} ${member.username}`;
}
@@ -265,6 +282,7 @@ class GfmAutoComplete {
},
},
});
+ showAndHideHelper($input);
}
setupMembers($input) {
@@ -284,7 +302,7 @@ class GfmAutoComplete {
// Team Members
$input.atwho({
at: '@',
- alias: 'users',
+ alias: USERS_ALIAS,
displayTpl(value) {
let tmpl = GfmAutoComplete.Loading.template;
const { avatarTag, username, title, icon, availability } = value;
@@ -374,12 +392,13 @@ class GfmAutoComplete {
},
},
});
+ showAndHideHelper($input, USERS_ALIAS);
}
setupIssues($input) {
$input.atwho({
at: '#',
- alias: 'issues',
+ alias: ISSUES_ALIAS,
searchKey: 'search',
displayTpl(value) {
let tmpl = GfmAutoComplete.Loading.template;
@@ -408,12 +427,13 @@ class GfmAutoComplete {
},
},
});
+ showAndHideHelper($input, ISSUES_ALIAS);
}
setupMilestones($input) {
$input.atwho({
at: '%',
- alias: 'milestones',
+ alias: MILESTONES_ALIAS,
searchKey: 'search',
// eslint-disable-next-line no-template-curly-in-string
insertTpl: '${atwho-at}${title}',
@@ -464,12 +484,13 @@ class GfmAutoComplete {
},
},
});
+ showAndHideHelper($input, MILESTONES_ALIAS);
}
setupMergeRequests($input) {
$input.atwho({
at: '!',
- alias: 'mergerequests',
+ alias: MERGEREQUESTS_ALIAS,
searchKey: 'search',
displayTpl(value) {
let tmpl = GfmAutoComplete.Loading.template;
@@ -498,6 +519,7 @@ class GfmAutoComplete {
},
},
});
+ showAndHideHelper($input, MERGEREQUESTS_ALIAS);
}
setupLabels($input) {
@@ -508,7 +530,7 @@ class GfmAutoComplete {
$input.atwho({
at: '~',
- alias: 'labels',
+ alias: LABELS_ALIAS,
searchKey: 'search',
data: GfmAutoComplete.defaultLoadingData,
displayTpl(value) {
@@ -598,12 +620,13 @@ class GfmAutoComplete {
},
},
});
+ showAndHideHelper($input, LABELS_ALIAS);
}
setupSnippets($input) {
$input.atwho({
at: '$',
- alias: 'snippets',
+ alias: SNIPPETS_ALIAS,
searchKey: 'search',
displayTpl(value) {
let tmpl = GfmAutoComplete.Loading.template;
@@ -631,13 +654,14 @@ class GfmAutoComplete {
},
},
});
+ showAndHideHelper($input, SNIPPETS_ALIAS);
}
setupContacts($input) {
$input.atwho({
at: '[contact:',
suffix: ']',
- alias: 'contacts',
+ alias: CONTACTS_ALIAS,
searchKey: 'search',
displayTpl(value) {
let tmpl = GfmAutoComplete.Loading.template;
@@ -667,6 +691,7 @@ class GfmAutoComplete {
},
},
});
+ showAndHideHelper($input, CONTACTS_ALIAS);
}
getDefaultCallbacks() {
diff --git a/app/assets/javascripts/mr_notes/init_notes.js b/app/assets/javascripts/mr_notes/init_notes.js
index d85fd10be45..cf24d18c7b6 100644
--- a/app/assets/javascripts/mr_notes/init_notes.js
+++ b/app/assets/javascripts/mr_notes/init_notes.js
@@ -44,7 +44,13 @@ export default () => {
},
watch: {
discussionTabCounter() {
- this.updateDiscussionTabCounter();
+ if (window.gon?.features?.paginatedMrDiscussions) {
+ if (this.$store.state.notes.doneFetchingBatchDiscussions) {
+ this.updateDiscussionTabCounter();
+ }
+ } else {
+ this.updateDiscussionTabCounter();
+ }
},
isShowTabActive: {
handler(newVal) {
diff --git a/app/assets/javascripts/notes/components/note_form.vue b/app/assets/javascripts/notes/components/note_form.vue
index a4cd20e6db8..30579a8eb0d 100644
--- a/app/assets/javascripts/notes/components/note_form.vue
+++ b/app/assets/javascripts/notes/components/note_form.vue
@@ -251,8 +251,10 @@ export default {
}
},
cancelHandler(shouldConfirm = false) {
- // Sends information about confirm message and if the textarea has changed
- this.$emit('cancelForm', shouldConfirm, this.noteBody !== this.updatedNoteBody);
+ // check if any dropdowns are active before sending the cancelation event
+ if (!this.$refs.textarea.classList.contains('at-who-active')) {
+ this.$emit('cancelForm', shouldConfirm, this.noteBody !== this.updatedNoteBody);
+ }
},
onInput() {
if (this.isSubmittingWithKeydown) {
diff --git a/app/controllers/projects/google_cloud/gcp_regions_controller.rb b/app/controllers/projects/google_cloud/gcp_regions_controller.rb
index 3fbe9a96284..c526db9ff3e 100644
--- a/app/controllers/projects/google_cloud/gcp_regions_controller.rb
+++ b/app/controllers/projects/google_cloud/gcp_regions_controller.rb
@@ -15,7 +15,6 @@ class Projects::GoogleCloud::GcpRegionsController < Projects::GoogleCloud::BaseC
tags = TagsFinder.new(project.repository, params).execute(gitaly_pagination: true)
refs = (branches + tags).map(&:name)
js_data = {
- screen: 'gcp_regions_form',
availableRegions: AVAILABLE_REGIONS,
refs: refs,
cancelPath: project_google_cloud_configuration_path(project)
diff --git a/app/controllers/projects/google_cloud/service_accounts_controller.rb b/app/controllers/projects/google_cloud/service_accounts_controller.rb
index dbd83be19db..c7245d75e2f 100644
--- a/app/controllers/projects/google_cloud/service_accounts_controller.rb
+++ b/app/controllers/projects/google_cloud/service_accounts_controller.rb
@@ -9,7 +9,6 @@ class Projects::GoogleCloud::ServiceAccountsController < Projects::GoogleCloud::
gcp_projects = google_api_client.list_projects
if gcp_projects.empty?
- @js_data = { screen: 'no_gcp_projects' }.to_json
track_event('service_accounts#index', 'error_form', 'no_gcp_projects')
flash[:warning] = _('No Google Cloud projects - You need at least one Google Cloud project')
redirect_to project_google_cloud_configuration_path(project)
@@ -19,7 +18,6 @@ class Projects::GoogleCloud::ServiceAccountsController < Projects::GoogleCloud::
tags = TagsFinder.new(project.repository, params).execute(gitaly_pagination: true)
refs = (branches + tags).map(&:name)
js_data = {
- screen: 'service_accounts_form',
gcpProjects: gcp_projects,
refs: refs,
cancelPath: project_google_cloud_configuration_path(project)
diff --git a/app/models/ci/secure_file.rb b/app/models/ci/secure_file.rb
index 078b05ff779..1d7c935aa95 100644
--- a/app/models/ci/secure_file.rb
+++ b/app/models/ci/secure_file.rb
@@ -24,6 +24,7 @@ module Ci
before_validation :assign_checksum
scope :order_by_created_at, -> { order(created_at: :desc) }
+ scope :project_id_in, ->(ids) { where(project_id: ids) }
default_value_for(:file_store) { Ci::SecureFileUploader.default_store }
@@ -46,3 +47,5 @@ module Ci
end
end
end
+
+Ci::SecureFile.prepend_mod
diff --git a/app/views/admin/labels/_label.html.haml b/app/views/admin/labels/_label.html.haml
index ae8fed8964f..333c865629f 100644
--- a/app/views/admin/labels/_label.html.haml
+++ b/app/views/admin/labels/_label.html.haml
@@ -1,7 +1,7 @@
%li.label-list-item{ id: dom_id(label) }
= render "shared/label_row", label: label.present(issuable_subject: nil)
.label-actions-list
- = link_to edit_admin_label_path(label), class: 'btn btn-default gl-button btn-default-tertiary label-action has-tooltip', title: _('Edit'), data: { placement: 'bottom' }, aria_label: _('Edit') do
+ = link_to edit_admin_label_path(label), class: 'btn btn-default gl-button btn-default-tertiary label-action has-tooltip', title: _('Edit'), data: { placement: 'bottom' }, aria: { label: _('Edit') } do
= sprite_icon('pencil')
= link_to admin_label_path(label), class: 'btn btn-default gl-button btn-default-tertiary hover-red js-remove-label label-action has-tooltip', title: _('Delete'), data: { placement: 'bottom', confirm: _('Are you sure you want to delete this label?'), confirm_btn_variant: 'danger' }, aria: { label: _('Delete label') }, method: :delete, remote: true do
= sprite_icon('remove')
diff --git a/app/views/shared/_search_settings.html.haml b/app/views/shared/_search_settings.html.haml
index 7265f090967..95eb421dbfe 100644
--- a/app/views/shared/_search_settings.html.haml
+++ b/app/views/shared/_search_settings.html.haml
@@ -4,4 +4,4 @@
%div{ class: container_class }
.js-search-settings-app
- %input.gl-form-input.form-control{ type: "text", placeholder: _("Search settings"), aria_label: _("Search settings"), disabled: true }
+ %input.gl-form-input.form-control{ type: "text", placeholder: _("Search settings"), aria: { label: _("Search settings") }, disabled: true }
diff --git a/config/initializers_before_autoloader/000_inflections.rb b/config/initializers_before_autoloader/000_inflections.rb
index 64686bdd962..70c9ec0a0ba 100644
--- a/config/initializers_before_autoloader/000_inflections.rb
+++ b/config/initializers_before_autoloader/000_inflections.rb
@@ -15,6 +15,7 @@ ActiveSupport::Inflector.inflections do |inflect|
inflect.uncountable %w(
custom_emoji
award_emoji
+ ci_secure_file_registry
container_repository_registry
design_registry
event_log
diff --git a/doc/administration/monitoring/prometheus/gitlab_metrics.md b/doc/administration/monitoring/prometheus/gitlab_metrics.md
index a74f6c39b72..54dc877ead7 100644
--- a/doc/administration/monitoring/prometheus/gitlab_metrics.md
+++ b/doc/administration/monitoring/prometheus/gitlab_metrics.md
@@ -301,6 +301,16 @@ configuration option in `gitlab.yml`. These metrics are served from the
| `geo_uploads_verification_failed` | Gauge | 14.6 | Number of uploads verifications failed on secondary | `url` |
| `gitlab_sli:rails_request_apdex:total` | Counter | 14.4 | The number of request-apdex measurements, [more information the development documentation](../../../development/application_slis/rails_request_apdex.md) | `endpoint_id`, `feature_category`, `request_urgency` |
| `gitlab_sli:rails_request_apdex:success_total` | Counter | 14.4 | The number of successful requests that met the target duration for their urgency. Divide by `gitlab_sli:rails_requests_apdex:total` to get a success ratio | `endpoint_id`, `feature_category`, `request_urgency` |
+| `geo_ci_secure_files` | Gauge | 15.3 | Number of secure files on primary | `url` |
+| `geo_ci_secure_files_checksum_total` | Gauge | 15.3 | Number of secure files tried to checksum on primary | `url` |
+| `geo_ci_secure_files_checksummed` | Gauge | 15.3 | Number of secure files successfully checksummed on primary | `url` |
+| `geo_ci_secure_files_checksum_failed` | Gauge | 15.3 | Number of secure files failed to calculate the checksum on primary | `url` |
+| `geo_ci_secure_files_synced` | Gauge | 15.3 | Number of syncable secure files synced on secondary | `url` |
+| `geo_ci_secure_files_failed` | Gauge | 15.3 | Number of syncable secure files failed to sync on secondary | `url` |
+| `geo_ci_secure_files_registry` | Gauge | 15.3 | Number of secure files in the registry | `url` |
+| `geo_ci_secure_files_verification_total` | Gauge | 15.3 | Number of secure files verifications tried on secondary | `url` |
+| `geo_ci_secure_files_verified` | Gauge | 15.3 | Number of secure files verified on secondary | `url` |
+| `geo_ci_secure_files_verification_failed` | Gauge | 15.3 | Number of secure files verifications failed on secondary | `url` |
## Database load balancing metrics **(PREMIUM SELF)**
diff --git a/doc/api/geo_nodes.md b/doc/api/geo_nodes.md
index fbb583f5a56..b5b920ec0cd 100644
--- a/doc/api/geo_nodes.md
+++ b/doc/api/geo_nodes.md
@@ -495,6 +495,19 @@ Example response:
"job_artifacts_synced_in_percentage": "100.00%",
"job_artifacts_verified_in_percentage": "100.00%",
"job_artifacts_synced_missing_on_primary_count": 0,
+ "ci_secure_files_count": 5,
+ "ci_secure_files_checksum_total_count": 5,
+ "ci_secure_files_checksummed_count": 5,
+ "ci_secure_files_checksum_failed_count": 0,
+ "ci_secure_files_synced_count": 5,
+ "ci_secure_files_failed_count": 0,
+ "ci_secure_files_registry_count": 5,
+ "ci_secure_files_verification_total_count": 5,
+ "ci_secure_files_verified_count": 5,
+ "ci_secure_files_verification_failed_count": 0,
+ "ci_secure_files_synced_in_percentage": "100.00%",
+ "ci_secure_files_verified_in_percentage": "100.00%",
+ "ci_secure_files_synced_missing_on_primary_count": 0,
},
{
"geo_node_id": 2,
@@ -830,6 +843,19 @@ Example response:
"job_artifacts_synced_in_percentage": "100.00%",
"job_artifacts_verified_in_percentage": "100.00%",
"job_artifacts_synced_missing_on_primary_count": 0,
+ "ci_secure_files_count": 5,
+ "ci_secure_files_checksum_total_count": 5,
+ "ci_secure_files_checksummed_count": 5,
+ "ci_secure_files_checksum_failed_count": 0,
+ "ci_secure_files_synced_count": 5,
+ "ci_secure_files_failed_count": 0,
+ "ci_secure_files_registry_count": 5,
+ "ci_secure_files_verification_total_count": 5,
+ "ci_secure_files_verified_count": 5,
+ "ci_secure_files_verification_failed_count": 0,
+ "ci_secure_files_synced_in_percentage": "100.00%",
+ "ci_secure_files_verified_in_percentage": "100.00%",
+ "ci_secure_files_synced_missing_on_primary_count": 0,
}
```
diff --git a/doc/api/graphql/reference/index.md b/doc/api/graphql/reference/index.md
index 5a436c3ff36..2cd7c5ebd00 100644
--- a/doc/api/graphql/reference/index.md
+++ b/doc/api/graphql/reference/index.md
@@ -6302,6 +6302,29 @@ The edge type for [`CiRunner`](#cirunner).
| <a id="cirunneredgenode"></a>`node` | [`CiRunner`](#cirunner) | The item at the end of the edge. |
| <a id="cirunneredgeweburl"></a>`webUrl` | [`String`](#string) | Web URL of the runner. The value depends on where you put this field in the query. You can use it for projects or groups. |
+#### `CiSecureFileRegistryConnection`
+
+The connection type for [`CiSecureFileRegistry`](#cisecurefileregistry).
+
+##### Fields
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| <a id="cisecurefileregistryconnectionedges"></a>`edges` | [`[CiSecureFileRegistryEdge]`](#cisecurefileregistryedge) | A list of edges. |
+| <a id="cisecurefileregistryconnectionnodes"></a>`nodes` | [`[CiSecureFileRegistry]`](#cisecurefileregistry) | A list of nodes. |
+| <a id="cisecurefileregistryconnectionpageinfo"></a>`pageInfo` | [`PageInfo!`](#pageinfo) | Information to aid in pagination. |
+
+#### `CiSecureFileRegistryEdge`
+
+The edge type for [`CiSecureFileRegistry`](#cisecurefileregistry).
+
+##### Fields
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| <a id="cisecurefileregistryedgecursor"></a>`cursor` | [`String!`](#string) | A cursor for use in pagination. |
+| <a id="cisecurefileregistryedgenode"></a>`node` | [`CiSecureFileRegistry`](#cisecurefileregistry) | The item at the end of the edge. |
+
#### `CiStageConnection`
The connection type for [`CiStage`](#cistage).
@@ -10070,6 +10093,25 @@ Returns [`CiRunnerStatus!`](#cirunnerstatus).
| ---- | ---- | ----------- |
| <a id="cirunnerstatuslegacymode"></a>`legacyMode` **{warning-solid}** | [`String`](#string) | **Deprecated** in 15.0. Will be removed in 17.0. In GitLab 16.0 and later, the field will act as if `legacyMode` is null. |
+### `CiSecureFileRegistry`
+
+Represents the Geo replication and verification state of a ci_secure_file.
+
+#### Fields
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| <a id="cisecurefileregistrycisecurefileid"></a>`ciSecureFileId` | [`ID!`](#id) | ID of the Ci Secure File. |
+| <a id="cisecurefileregistrycreatedat"></a>`createdAt` | [`Time`](#time) | Timestamp when the CiSecureFileRegistry was created. |
+| <a id="cisecurefileregistryid"></a>`id` | [`ID!`](#id) | ID of the CiSecureFileRegistry. |
+| <a id="cisecurefileregistrylastsyncfailure"></a>`lastSyncFailure` | [`String`](#string) | Error message during sync of the CiSecureFileRegistry. |
+| <a id="cisecurefileregistrylastsyncedat"></a>`lastSyncedAt` | [`Time`](#time) | Timestamp of the most recent successful sync of the CiSecureFileRegistry. |
+| <a id="cisecurefileregistryretryat"></a>`retryAt` | [`Time`](#time) | Timestamp after which the CiSecureFileRegistry is resynced. |
+| <a id="cisecurefileregistryretrycount"></a>`retryCount` | [`Int`](#int) | Number of consecutive failed sync attempts of the CiSecureFileRegistry. |
+| <a id="cisecurefileregistrystate"></a>`state` | [`RegistryState`](#registrystate) | Sync state of the CiSecureFileRegistry. |
+| <a id="cisecurefileregistryverificationretryat"></a>`verificationRetryAt` | [`Time`](#time) | Timestamp after which the CiSecureFileRegistry is reverified. |
+| <a id="cisecurefileregistryverifiedat"></a>`verifiedAt` | [`Time`](#time) | Timestamp of the most recent successful verification of the CiSecureFileRegistry. |
+
### `CiStage`
#### Fields
@@ -11681,6 +11723,22 @@ Represents an external issue.
#### Fields with arguments
+##### `GeoNode.ciSecureFileRegistries`
+
+Find Ci Secure File registries on this Geo node Available only when feature flag `geo_ci_secure_file_replication` is enabled. This flag is disabled by default, because the feature is experimental and is subject to change without notice.
+
+Returns [`CiSecureFileRegistryConnection`](#cisecurefileregistryconnection).
+
+This field returns a [connection](#connections). It accepts the
+four standard [pagination arguments](#connection-pagination-arguments):
+`before: String`, `after: String`, `first: Int`, `last: Int`.
+
+###### Arguments
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| <a id="geonodecisecurefileregistriesids"></a>`ids` | [`[ID!]`](#id) | Filters registries by their ID. |
+
##### `GeoNode.groupWikiRepositoryRegistries`
Find group wiki repository registries on this Geo node.
diff --git a/doc/development/snowplow/index.md b/doc/development/snowplow/index.md
index 155ce87b8d9..24cd9093267 100644
--- a/doc/development/snowplow/index.md
+++ b/doc/development/snowplow/index.md
@@ -89,10 +89,10 @@ Each click event provides attributes that describe the event.
| Attribute | Type | Required | Description |
| --------- | ------- | -------- | ----------- |
-| category | text | true | The page or backend section of the application. Unless infeasible, use the Rails page attribute by default in the frontend, and namespace + class name on the backend. |
+| category | text | true | The page or backend section of the application. Unless infeasible, use the Rails page attribute by default in the frontend, and namespace + class name on the backend, for example, `Notes::CreateService`. |
| action | text | true | The action the user takes, or aspect that's being instrumented. The first word must describe the action or aspect. For example, clicks must be `click`, activations must be `activate`, creations must be `create`. Use underscores to describe what was acted on. For example, activating a form field is `activate_form_input`, an interface action like clicking on a dropdown is `click_dropdown`, a behavior like creating a project record from the backend is `create_project`. |
-| label | text | false | The specific element or object to act on. This can be one of the following: the label of the element, for example, a tab labeled 'Create from template' for `create_from_template`; a unique identifier if no text is available, for example, `groups_dropdown_close` for closing the Groups dropdown in the top bar; or the name or title attribute of a record being created. |
-| property | text | false | Any additional property of the element, or object being acted on. |
+| label | text | false | The specific element or object to act on. This can be one of the following: the label of the element, for example, a tab labeled 'Create from template' for `create_from_template`; a unique identifier if no text is available, for example, `groups_dropdown_close` for closing the Groups dropdown in the top bar; or the name or title attribute of a record being created. For Service Ping metrics adapted to Snowplow events, this should be the full metric [key path](../service_ping/metrics_dictionary.md#metric-key_path) taken from its definition file. |
+| property | text | false | Any additional property of the element, or object being acted on. For Service Ping metrics adapted to Snowplow events, this should be additional information or context that can help analyze the event. For example, in the case of `usage_activity_by_stage_monthly.create.merge_requests_users`, there are four different possible merge request actions: "create", "merge", "comment", and "close". Each of these would be a possible property value. |
| value | decimal | false | Describes a numeric value (decimal) directly related to the event. This could be the value of an input. For example, `10` when clicking `internal` visibility. |
### Examples
@@ -106,6 +106,7 @@ Each click event provides attributes that describe the event.
| `[projects:blob:show]` | `congratulate_first_pipeline` | `click_button` | `[human_access]` | - |
| `[projects:clusters:new]` | `chart_options` | `generate_link` | `[chart_link]` | - |
| `[projects:clusters:new]` | `chart_options` | `click_add_label_button` | `[label_id]` | - |
+| `API::NpmPackages` | `counts.package_events_i_package_push_package_by_deploy_token` | `push_package` | `npm` | - |
_* If you choose to omit the category you can use the default._<br>
_** Use property for variable strings._
diff --git a/lib/gitlab/database/migration_helpers.rb b/lib/gitlab/database/migration_helpers.rb
index 4bb1d71ce18..ea1beeaf81e 100644
--- a/lib/gitlab/database/migration_helpers.rb
+++ b/lib/gitlab/database/migration_helpers.rb
@@ -1666,7 +1666,9 @@ into similar problems in the future (e.g. when new tables are created).
end
Gitlab::Database::QueryAnalyzers::RestrictAllowedSchemas.with_suppressed do
- update_column_in_batches(table, new, old_value, batch_column_name: batch_column_name)
+ Gitlab::Database::QueryAnalyzers::GitlabSchemasValidateConnection.with_suppressed do
+ update_column_in_batches(table, new, old_value, batch_column_name: batch_column_name)
+ end
end
add_not_null_constraint(table, new) unless old_col.null
diff --git a/lib/google_api/cloud_platform/client.rb b/lib/google_api/cloud_platform/client.rb
index c46ca2783bf..b1a69a5e61f 100644
--- a/lib/google_api/cloud_platform/client.rb
+++ b/lib/google_api/cloud_platform/client.rb
@@ -101,7 +101,7 @@ module GoogleApi
result.append(project)
end
- result
+ result.sort_by(&:project_id)
end
def create_service_account(gcp_project_id, display_name, description)
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index f59981821ac..93fed0d44be 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -12931,7 +12931,7 @@ msgstr ""
msgid "DeploymentApproval|Approve or reject deployment #%{deploymentIid}"
msgstr ""
-msgid "DeploymentApproval|Approved by %{user} %{time}"
+msgid "DeploymentApproval|Approved %{time}"
msgstr ""
msgid "DeploymentApproval|Approved by you %{time}"
diff --git a/spec/factories/ci/secure_files.rb b/spec/factories/ci/secure_files.rb
index 9afec5db858..74988202c71 100644
--- a/spec/factories/ci/secure_files.rb
+++ b/spec/factories/ci/secure_files.rb
@@ -6,5 +6,11 @@ FactoryBot.define do
file { fixture_file_upload('spec/fixtures/ci_secure_files/upload-keystore.jks', 'application/octet-stream') }
checksum { 'foo1234' }
project
+
+ trait :remote_store do
+ after(:create) do |ci_secure_file|
+ ci_secure_file.update!(file_store: ObjectStorage::Store::REMOTE)
+ end
+ end
end
end
diff --git a/spec/features/merge_request/user_comments_on_merge_request_spec.rb b/spec/features/merge_request/user_comments_on_merge_request_spec.rb
index 43096f8e7f9..dbcfc2b968f 100644
--- a/spec/features/merge_request/user_comments_on_merge_request_spec.rb
+++ b/spec/features/merge_request/user_comments_on_merge_request_spec.rb
@@ -51,6 +51,45 @@ RSpec.describe 'User comments on a merge request', :js do
expect(page).to have_button('Resolve thread')
end
+ array = [':', '@', '#', '%', '!', '~', '$', '[contact:']
+ array.each do |x|
+ it 'handles esc key correctly when atwho is active' do
+ page.within('.js-main-target-form') do
+ fill_in('note[note]', with: 'comment 1')
+ click_button('Comment')
+ end
+
+ wait_for_requests
+
+ page.within('.note') do
+ click_button('Reply to comment')
+ fill_in('note[note]', with: x)
+ send_keys :escape
+ end
+
+ wait_for_requests
+ expect(page.html).not_to include('Are you sure you want to cancel creating this comment?')
+ end
+ end
+
+ it 'handles esc key correctly when atwho is not active' do
+ page.within('.js-main-target-form') do
+ fill_in('note[note]', with: 'comment 1')
+ click_button('Comment')
+ end
+
+ wait_for_requests
+
+ page.within('.note') do
+ click_button('Reply to comment')
+ fill_in('note[note]', with: 'comment 2')
+ send_keys :escape
+ end
+
+ wait_for_requests
+ expect(page.html).to include('Are you sure you want to cancel creating this comment?')
+ end
+
it 'loads new comment' do
# Add new comment in background in order to check
# if it's going to be loaded automatically for current user.
diff --git a/spec/frontend/gfm_auto_complete/mock_data.js b/spec/frontend/gfm_auto_complete/mock_data.js
new file mode 100644
index 00000000000..86795ffd0a5
--- /dev/null
+++ b/spec/frontend/gfm_auto_complete/mock_data.js
@@ -0,0 +1,34 @@
+export const eventlistenersMockDefaultMap = [
+ {
+ key: 'shown',
+ namespace: 'atwho',
+ },
+ {
+ key: 'shown-users',
+ namespace: 'atwho',
+ },
+ {
+ key: 'shown-issues',
+ namespace: 'atwho',
+ },
+ {
+ key: 'shown-milestones',
+ namespace: 'atwho',
+ },
+ {
+ key: 'shown-mergerequests',
+ namespace: 'atwho',
+ },
+ {
+ key: 'shown-labels',
+ namespace: 'atwho',
+ },
+ {
+ key: 'shown-snippets',
+ namespace: 'atwho',
+ },
+ {
+ key: 'shown-contacts',
+ namespace: 'atwho',
+ },
+];
diff --git a/spec/frontend/gfm_auto_complete_spec.js b/spec/frontend/gfm_auto_complete_spec.js
index 072cf34d0ef..c3dfc4570f9 100644
--- a/spec/frontend/gfm_auto_complete_spec.js
+++ b/spec/frontend/gfm_auto_complete_spec.js
@@ -10,6 +10,7 @@ import { TEST_HOST } from 'helpers/test_constants';
import waitForPromises from 'helpers/wait_for_promises';
import AjaxCache from '~/lib/utils/ajax_cache';
import axios from '~/lib/utils/axios_utils';
+import { eventlistenersMockDefaultMap } from 'ee_else_ce_jest/gfm_auto_complete/mock_data';
describe('GfmAutoComplete', () => {
const fetchDataMock = { fetchData: jest.fn() };
@@ -457,12 +458,12 @@ describe('GfmAutoComplete', () => {
it('should be false with actual array data', () => {
expect(
- GfmAutoComplete.isLoading([{ title: 'Foo' }, { title: 'Bar' }, { title: 'Qux' }]),
+ GfmAutoComplete.isLoading([{ title: 'events' }, { title: 'Bar' }, { title: 'Qux' }]),
).toBe(false);
});
it('should be false with actual data item', () => {
- expect(GfmAutoComplete.isLoading({ title: 'Foo' })).toBe(false);
+ expect(GfmAutoComplete.isLoading({ title: 'events' })).toBe(false);
});
});
@@ -884,4 +885,47 @@ describe('GfmAutoComplete', () => {
).toBe(`<li><small>${escapedPayload} ${escapedPayload}</small> ${escapedPayload}</li>`);
});
});
+
+ describe('autocomplete show eventlisteners', () => {
+ let $textarea;
+
+ beforeEach(() => {
+ setHTMLFixture('<textarea></textarea>');
+ $textarea = $('textarea');
+ });
+
+ it('sets correct eventlisteners when autocomplete features are enabled', () => {
+ const autocomplete = new GfmAutoComplete({});
+ autocomplete.setup($textarea);
+ autocomplete.setupAtWho($textarea);
+ /* eslint-disable-next-line no-underscore-dangle */
+ const events = $._data($textarea[0], 'events');
+ expect(
+ Object.keys(events)
+ .filter((x) => {
+ return x.startsWith('shown');
+ })
+ .map((e) => {
+ return { key: e, namespace: events[e][0].namespace };
+ }),
+ ).toEqual(expect.arrayContaining(eventlistenersMockDefaultMap));
+ });
+
+ it('sets no eventlisteners when features are disabled', () => {
+ const autocomplete = new GfmAutoComplete({});
+ autocomplete.setup($textarea, {});
+ autocomplete.setupAtWho($textarea);
+ /* eslint-disable-next-line no-underscore-dangle */
+ const events = $._data($textarea[0], 'events');
+ expect(
+ Object.keys(events)
+ .filter((x) => {
+ return x.startsWith('shown');
+ })
+ .map((e) => {
+ return { key: e, namespace: events[e][0].namespace };
+ }),
+ ).toStrictEqual([]);
+ });
+ });
});
diff --git a/spec/frontend/notes/components/note_form_spec.js b/spec/frontend/notes/components/note_form_spec.js
index 252c24d1117..563fd570d52 100644
--- a/spec/frontend/notes/components/note_form_spec.js
+++ b/spec/frontend/notes/components/note_form_spec.js
@@ -6,6 +6,7 @@ import { getDraft, updateDraft } from '~/lib/utils/autosave';
import NoteForm from '~/notes/components/note_form.vue';
import createStore from '~/notes/stores';
import MarkdownField from '~/vue_shared/components/markdown/field.vue';
+import { AT_WHO_ACTIVE_CLASS } from '~/gfm_auto_complete';
import { noteableDataMock, notesDataMock, discussionMock, note } from '../mock_data';
jest.mock('~/lib/utils/autosave');
@@ -201,6 +202,21 @@ describe('issue_note_form component', () => {
expect(wrapper.emitted().cancelForm).toHaveLength(1);
});
+ it('will not cancel form if there is an active at-who-active class', async () => {
+ wrapper.setProps({
+ ...props,
+ });
+ await nextTick();
+
+ const textareaEl = wrapper.vm.$refs.textarea;
+ const cancelButton = findCancelButton();
+ textareaEl.classList.add(AT_WHO_ACTIVE_CLASS);
+ cancelButton.vm.$emit('click');
+ await nextTick();
+
+ expect(wrapper.emitted().cancelForm).toBeUndefined();
+ });
+
it('should be possible to update the note', async () => {
wrapper.setProps({
...props,
diff --git a/spec/lib/gitlab/database/migration_helpers_spec.rb b/spec/lib/gitlab/database/migration_helpers_spec.rb
index 3ccc3a17862..c289277d6be 100644
--- a/spec/lib/gitlab/database/migration_helpers_spec.rb
+++ b/spec/lib/gitlab/database/migration_helpers_spec.rb
@@ -1080,6 +1080,8 @@ RSpec.describe Gitlab::Database::MigrationHelpers do
end
it 'renames a column concurrently' do
+ expect(Gitlab::Database::QueryAnalyzers::GitlabSchemasValidateConnection).to receive(:with_suppressed).and_yield
+
expect(model).to receive(:check_trigger_permissions!).with(:users)
expect(model).to receive(:install_rename_triggers)
@@ -1112,6 +1114,7 @@ RSpec.describe Gitlab::Database::MigrationHelpers do
let(:connection) { ActiveRecord::Migration.connection }
before do
+ expect(Gitlab::Database::QueryAnalyzers::GitlabSchemasValidateConnection).to receive(:with_suppressed).and_yield
expect(Gitlab::Database::UnidirectionalCopyTrigger).to receive(:on_table)
.with(:users, connection: connection).and_return(copy_trigger)
end
@@ -1165,6 +1168,8 @@ RSpec.describe Gitlab::Database::MigrationHelpers do
end
it 'copies the default to the new column' do
+ expect(Gitlab::Database::QueryAnalyzers::GitlabSchemasValidateConnection).to receive(:with_suppressed).and_yield
+
expect(model).to receive(:change_column_default)
.with(:users, :new, old_column.default)
@@ -1246,6 +1251,8 @@ RSpec.describe Gitlab::Database::MigrationHelpers do
end
it 'reverses the operations of cleanup_concurrent_column_rename' do
+ expect(Gitlab::Database::QueryAnalyzers::GitlabSchemasValidateConnection).to receive(:with_suppressed).and_yield
+
expect(model).to receive(:check_trigger_permissions!).with(:users)
expect(model).to receive(:install_rename_triggers)
@@ -1302,6 +1309,8 @@ RSpec.describe Gitlab::Database::MigrationHelpers do
end
it 'copies the default to the old column' do
+ expect(Gitlab::Database::QueryAnalyzers::GitlabSchemasValidateConnection).to receive(:with_suppressed).and_yield
+
expect(model).to receive(:change_column_default)
.with(:users, :old, new_column.default)
diff --git a/spec/lib/google_api/cloud_platform/client_spec.rb b/spec/lib/google_api/cloud_platform/client_spec.rb
index ba49c00245e..d8818d46836 100644
--- a/spec/lib/google_api/cloud_platform/client_spec.rb
+++ b/spec/lib/google_api/cloud_platform/client_spec.rb
@@ -217,7 +217,11 @@ RSpec.describe GoogleApi::CloudPlatform::Client do
describe '#list_projects' do
subject { client.list_projects }
- let(:list_of_projects) { [{}, {}, {}] }
+ let(:gcp_project_01) { Google::Apis::CloudresourcemanagerV1::Project.new(project_id: '01') }
+ let(:gcp_project_02) { Google::Apis::CloudresourcemanagerV1::Project.new(project_id: '02') }
+ let(:gcp_project_03) { Google::Apis::CloudresourcemanagerV1::Project.new(project_id: '03') }
+ let(:list_of_projects) { [gcp_project_03, gcp_project_01, gcp_project_02] }
+
let(:next_page_token) { nil }
let(:operation) { double('projects': list_of_projects, 'next_page_token': next_page_token) }
@@ -225,7 +229,8 @@ RSpec.describe GoogleApi::CloudPlatform::Client do
expect_any_instance_of(Google::Apis::CloudresourcemanagerV1::CloudResourceManagerService)
.to receive(:list_projects)
.and_return(operation)
- is_expected.to eq(list_of_projects)
+
+ is_expected.to contain_exactly(gcp_project_01, gcp_project_02, gcp_project_03)
end
end