diff --git a/Gemfile b/Gemfile index e14957707b5..54c77bee3c7 100644 --- a/Gemfile +++ b/Gemfile @@ -146,7 +146,7 @@ gem 'fog-aws', '~> 3.15' # Locked until fog-google resolves https://github.com/fog/fog-google/issues/421. # Also see config/initializers/fog_core_patch.rb. gem 'fog-core', '= 2.1.0' -gem 'fog-google', '~> 1.15', require: 'fog/google' +gem 'fog-google', '~> 1.19', require: 'fog/google' gem 'fog-local', '~> 0.8' gem 'fog-openstack', '~> 1.0' gem 'fog-rackspace', '~> 0.1.1' @@ -551,6 +551,7 @@ gem 'valid_email', '~> 0.1' gem 'json', '~> 2.5.1' gem 'json_schemer', '~> 0.2.18' gem 'oj', '~> 3.13.21' +gem 'oj-introspect', '~> 0.7' gem 'multi_json', '~> 1.14.1' gem 'yajl-ruby', '~> 1.4.3', require: 'yajl' diff --git a/Gemfile.checksum b/Gemfile.checksum index 6497d75cdea..29bacd317fa 100644 --- a/Gemfile.checksum +++ b/Gemfile.checksum @@ -182,7 +182,7 @@ {"name":"fog-aliyun","version":"0.3.3","platform":"ruby","checksum":"d0aa317f7c1473a1d684fff51699f216bb9cb78b9ee9ce55a81c9bcc93fb85ee"}, {"name":"fog-aws","version":"3.15.0","platform":"ruby","checksum":"09752931ea0c6165b018e1a89253248d86b246645086ccf19bc44fabe3381e8c"}, {"name":"fog-core","version":"2.1.0","platform":"ruby","checksum":"53e5d793554d7080d015ef13cd44b54027e421d924d9dba4ce3d83f95f37eda9"}, -{"name":"fog-google","version":"1.15.0","platform":"ruby","checksum":"2f840780fbf2384718e961b05ef2fc522b4213bbda6f25b28c1bbd875ff0b306"}, +{"name":"fog-google","version":"1.19.0","platform":"ruby","checksum":"3c909a230837fe84117fffdfd927b523821b88f61d3aeab531e1417a9810f488"}, {"name":"fog-json","version":"1.2.0","platform":"ruby","checksum":"dd4f5ab362dbc72b687240bba9d2dd841d5dfe888a285797533f85c03ea548fe"}, {"name":"fog-local","version":"0.8.0","platform":"ruby","checksum":"263b2d09e54c69d1b87ad7f235a1a1e53c8a674edcedf7512c1715765ad7ef79"}, {"name":"fog-openstack","version":"1.0.8","platform":"ruby","checksum":"8f174ab5e5b1bc107c7da90cc7c47a24930e1566cd88ab4df447026ea8b63d9c"}, @@ -193,6 +193,7 @@ {"name":"fuubar","version":"2.2.0","platform":"ruby","checksum":"9b0263c4074f39c68b37f1e4e69a7d3cfc7523c41bea43601235daa723179b4a"}, {"name":"fuzzyurl","version":"0.9.0","platform":"ruby","checksum":"542efa80f2bcaadbdc402c2f0b572f2e335a1d53e375aecad68bbb3d86860c0f"}, {"name":"gemoji","version":"3.0.1","platform":"ruby","checksum":"80553f2f4932a7a95fb1b3c7c63f7dd937e7c8c610164bbdea28fd06eba5f36d"}, +{"name":"gems","version":"1.2.0","platform":"ruby","checksum":"343d74bd54d906f38193f3ccd983f9d08c4b54cd01ee7e5fe8467ab41a9946f0"}, {"name":"get_process_mem","version":"0.2.7","platform":"ruby","checksum":"4afd3c3641dd6a817c09806c7d6d509d8a9984512ac38dea8b917426bbf77eba"}, {"name":"gettext","version":"3.3.6","platform":"ruby","checksum":"ee6bbd1b2f833ee52d7797fa68acbfecc4726aec6b6280fd7eab92aa0190b413"}, {"name":"gettext_i18n_rails","version":"1.8.0","platform":"ruby","checksum":"95e5cf8440b1e08705b27f2bccb56143272c5a7a0dabcf54ea1bd701140a496f"}, @@ -216,7 +217,17 @@ {"name":"gitlab_omniauth-ldap","version":"2.2.0","platform":"ruby","checksum":"bb4d20acb3b123ed654a8f6a47d3fac673ece7ed0b6992edb92dca14bad2838c"}, {"name":"globalid","version":"1.0.0","platform":"ruby","checksum":"1253641b1dc3392721c964351773755d75135d3d3c5cc65d88b0a3880a60bed8"}, {"name":"gon","version":"6.4.0","platform":"ruby","checksum":"e3a618d659392890f1aa7db420f17c75fd7d35aeb5f8fe003697d02c4b88d2f0"}, -{"name":"google-api-client","version":"0.50.0","platform":"ruby","checksum":"3ae45e972f293f3a66e53950ecc0fd350d85d6347c06a430bb971bd1ae5ad617"}, +{"name":"google-api-client","version":"0.53.0","platform":"ruby","checksum":"41006ef21fe02a70cff39a10aebf84fa7fb5f24c63566ab12b149ff1f1d9d7ff"}, +{"name":"google-apis-compute_v1","version":"0.53.0","platform":"ruby","checksum":"629537cf9efc1aeda0bb00d78c2a6ffa8488de833a8b19bdb150ce0a6a105f4b"}, +{"name":"google-apis-core","version":"0.9.1","platform":"ruby","checksum":"c012a364891a4602b4b1aa8468400dd3fa50b00e694edb4411af6b85aa3eb034"}, +{"name":"google-apis-discovery_v1","version":"0.12.0","platform":"ruby","checksum":"2e5accfe126884e5ebd8540b3a17a878a3a050d0dfdf0ece6b231846fc485a15"}, +{"name":"google-apis-dns_v1","version":"0.28.0","platform":"ruby","checksum":"f523631ea2737b67096e21eff25e426edb51ffefa9979a42f798936a950df34c"}, +{"name":"google-apis-generator","version":"0.11.0","platform":"ruby","checksum":"4656febed121b21e9071118c79ab67cbec9e40a39b6a38acc05d07fafa321279"}, +{"name":"google-apis-iamcredentials_v1","version":"0.15.0","platform":"ruby","checksum":"e9a256a6d80fbfc77d44bd7e65bc94b9e1e9863a00e6d413edc0102d6cb5551b"}, +{"name":"google-apis-monitoring_v3","version":"0.37.0","platform":"ruby","checksum":"2d9262ae8dfa83ac7db895b03c7deeaae9f13107e94c8781a432202fbc20736a"}, +{"name":"google-apis-pubsub_v1","version":"0.30.0","platform":"ruby","checksum":"b8905915388041bf54f9b7e988c8cc64fe00c2132475d5c753d10479415ee13d"}, +{"name":"google-apis-sqladmin_v1beta4","version":"0.38.0","platform":"ruby","checksum":"d00279cdcc5548bf4f4e40cc29cbd942b79708011e59c75a18726b6826be1665"}, +{"name":"google-apis-storage_v1","version":"0.20.0","platform":"ruby","checksum":"8a1ace07fc909966d6f76e777d6adc7d86dddd91a629fef8914ebd5baf86d850"}, {"name":"google-cloud-env","version":"1.6.0","platform":"ruby","checksum":"6179acb946975892c7908748df5722a4ebadfc8cf5bb7b0d8d933ca67183fa15"}, {"name":"google-protobuf","version":"3.21.9","platform":"java","checksum":"8483ab2487170434f7a139d6534b3a166e4ec244a6fd8929f758d87abbb82fee"}, {"name":"google-protobuf","version":"3.21.9","platform":"ruby","checksum":"5a656c159aa2c85008af7eab3f603cf22921b748e09438f6682dcf696d518adc"}, @@ -227,7 +238,7 @@ {"name":"google-protobuf","version":"3.21.9","platform":"x86_64-darwin","checksum":"9e948a08ee27cca8acf794c798db16d918ce503eae06525d7551dc05ac3324c0"}, {"name":"google-protobuf","version":"3.21.9","platform":"x86_64-linux","checksum":"d4053012022f7bf47cd54c7c19416f600325e6cc1e1604a631c2fde69dd920a4"}, {"name":"googleapis-common-protos-types","version":"1.3.0","platform":"ruby","checksum":"c5411f3197cc3e02547ded1858303b1f830b4dc89c588c142ad6c8a231050671"}, -{"name":"googleauth","version":"0.14.0","platform":"ruby","checksum":"4659b563d5b2727e775ba9231e75485c1b55ac8fc319e0bf1bc87d5e9705a632"}, +{"name":"googleauth","version":"1.3.0","platform":"ruby","checksum":"51dd7362353cf1e90a2d01e1fb94321ae3926c776d4dc4a79db65230217ffcc2"}, {"name":"gpgme","version":"2.0.20","platform":"ruby","checksum":"fc194689cff40cd4ccafb3086031e930650b3efc15348bbfdf7a2f8b5a826f75"}, {"name":"grape","version":"1.5.2","platform":"ruby","checksum":"1df3b734c3862e235174232bc629587eddda9ef3df648230827575186700ae29"}, {"name":"grape-entity","version":"0.10.0","platform":"ruby","checksum":"9aed1e7cbbc96d9e73f72e5f32c776d4ba8a5baf54c3acda2682008dba2b2cfe"}, @@ -372,7 +383,8 @@ {"name":"oauth2","version":"2.0.9","platform":"ruby","checksum":"b21f9defcf52dc1610e0dfab4c868342173dcd707fd15c777d9f4f04e153f7fb"}, {"name":"octokit","version":"4.25.1","platform":"ruby","checksum":"c02092ee82dcdfe84db0e0ea630a70d32becc54245a4f0bacfd21c010df09b96"}, {"name":"ohai","version":"16.10.6","platform":"ruby","checksum":"b835806e585faea4ac8346b68c722fb5fc29a29f73fd7e3a022f9073132dec22"}, -{"name":"oj","version":"3.13.21","platform":"ruby","checksum":"aef31a8dcc6f0b9b4bb5cc7ac6cc5272b2d851deb11a1804c2ed6b5501b50e46"}, +{"name":"oj","version":"3.13.23","platform":"ruby","checksum":"206dfdc4020ad9974705037f269cfba211d61b7662a58c717cce771829ccef51"}, +{"name":"oj-introspect","version":"0.7.0","platform":"ruby","checksum":"dacd2504fedf67ed26733efa753e3b4da1888ad2a9752e81948acb8a196b8420"}, {"name":"omniauth","version":"2.1.0","platform":"ruby","checksum":"bff7234f5ec9323622b217c7f26d52f850de0b0e2b8c807c3358fc79fe572300"}, {"name":"omniauth-alicloud","version":"2.0.0","platform":"ruby","checksum":"8ecf369d51cd5317c1e7c6b80276891f76cff210a534ec654326af5c62265de3"}, {"name":"omniauth-atlassian-oauth2","version":"0.2.0","platform":"ruby","checksum":"eb07574a188ab8a03376ce288bce86bc2dd4a1382ffa5781cb5e2b7bc15d76c9"}, diff --git a/Gemfile.lock b/Gemfile.lock index 557c366cbb3..f1585eb75ff 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -501,11 +501,17 @@ GEM excon (~> 0.58) formatador (~> 0.2) mime-types - fog-google (1.15.0) - fog-core (<= 2.1.0) + fog-google (1.19.0) + fog-core (< 2.3) fog-json (~> 1.2) fog-xml (~> 0.1.0) - google-api-client (>= 0.44.2, < 0.51) + google-apis-compute_v1 (~> 0.14) + google-apis-dns_v1 (~> 0.12) + google-apis-iamcredentials_v1 (~> 0.6) + google-apis-monitoring_v3 (~> 0.12) + google-apis-pubsub_v1 (~> 0.7) + google-apis-sqladmin_v1beta4 (~> 0.13) + google-apis-storage_v1 (~> 0.6) google-cloud-env (~> 1.2) fog-json (1.2.0) fog-core @@ -533,6 +539,7 @@ GEM ruby-progressbar (~> 1.4) fuzzyurl (0.9.0) gemoji (3.0.1) + gems (1.2.0) get_process_mem (0.2.7) ffi (~> 1.0) gettext (3.3.6) @@ -608,27 +615,52 @@ GEM i18n (>= 0.7) multi_json request_store (>= 1.0) - google-api-client (0.50.0) + google-api-client (0.53.0) + google-apis-core (~> 0.1) + google-apis-generator (~> 0.1) + google-apis-compute_v1 (0.53.0) + google-apis-core (>= 0.9.0, < 2.a) + google-apis-core (0.9.1) addressable (~> 2.5, >= 2.5.1) - googleauth (~> 0.9) - httpclient (>= 2.8.1, < 3.0) + googleauth (>= 0.16.2, < 2.a) + httpclient (>= 2.8.1, < 3.a) mini_mime (~> 1.0) representable (~> 3.0) - retriable (>= 2.0, < 4.0) + retriable (>= 2.0, < 4.a) rexml - signet (~> 0.12) + webrick + google-apis-discovery_v1 (0.12.0) + google-apis-core (>= 0.9.0, < 2.a) + google-apis-dns_v1 (0.28.0) + google-apis-core (>= 0.9.0, < 2.a) + google-apis-generator (0.11.0) + activesupport (>= 5.0) + gems (~> 1.2) + google-apis-core (>= 0.9.1, < 2.a) + google-apis-discovery_v1 (~> 0.5) + thor (>= 0.20, < 2.a) + google-apis-iamcredentials_v1 (0.15.0) + google-apis-core (>= 0.9.0, < 2.a) + google-apis-monitoring_v3 (0.37.0) + google-apis-core (>= 0.9.1, < 2.a) + google-apis-pubsub_v1 (0.30.0) + google-apis-core (>= 0.9.1, < 2.a) + google-apis-sqladmin_v1beta4 (0.38.0) + google-apis-core (>= 0.9.0, < 2.a) + google-apis-storage_v1 (0.20.0) + google-apis-core (>= 0.9.1, < 2.a) google-cloud-env (1.6.0) faraday (>= 0.17.3, < 3.0) google-protobuf (3.21.9) googleapis-common-protos-types (1.3.0) google-protobuf (~> 3.14) - googleauth (0.14.0) - faraday (>= 0.17.3, < 2.0) + googleauth (1.3.0) + faraday (>= 0.17.3, < 3.a) jwt (>= 1.4, < 3.0) memoist (~> 0.16) multi_json (~> 1.11) os (>= 0.9, < 2.0) - signet (~> 0.14) + signet (>= 0.16, < 2.a) gpgme (2.0.20) mini_portile2 (~> 2.3) grape (1.5.2) @@ -937,7 +969,9 @@ GEM plist (~> 3.1) train-core wmi-lite (~> 1.0) - oj (3.13.21) + oj (3.13.23) + oj-introspect (0.7.0) + oj (>= 3.13.23) omniauth (2.1.0) hashie (>= 3.4.6) rack (>= 2.2.3) @@ -1616,7 +1650,7 @@ DEPENDENCIES fog-aliyun (~> 0.3) fog-aws (~> 3.15) fog-core (= 2.1.0) - fog-google (~> 1.15) + fog-google (~> 1.19) fog-local (~> 0.8) fog-openstack (~> 1.0) fog-rackspace (~> 0.1.1) @@ -1705,6 +1739,7 @@ DEPENDENCIES octokit (~> 4.15) ohai (~> 16.10) oj (~> 3.13.21) + oj-introspect (~> 0.7) omniauth (~> 2.1.0) omniauth-alicloud (~> 2.0.0) omniauth-atlassian-oauth2 (~> 0.2.0) diff --git a/app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/labels_select_root.vue b/app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/labels_select_root.vue index 0e8da7281d8..2c27a69d587 100644 --- a/app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/labels_select_root.vue +++ b/app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/labels_select_root.vue @@ -129,9 +129,6 @@ export default { issuableId() { return this.issuable?.id; }, - isRealtimeEnabled() { - return this.glFeatures.realtimeLabels; - }, }, apollo: { issuable: { @@ -163,7 +160,7 @@ export default { }; }, skip() { - return !this.issuableId || !this.isDropdownVariantSidebar || !this.isRealtimeEnabled; + return !this.issuableId || !this.isDropdownVariantSidebar; }, updateQuery( _, diff --git a/app/assets/javascripts/vue_shared/issuable/list/components/issuable_item.vue b/app/assets/javascripts/vue_shared/issuable/list/components/issuable_item.vue index 9fbf8042784..30b7b073ac3 100644 --- a/app/assets/javascripts/vue_shared/issuable/list/components/issuable_item.vue +++ b/app/assets/javascripts/vue_shared/issuable/list/components/issuable_item.vue @@ -3,7 +3,7 @@ import { GlLink, GlIcon, GlLabel, GlFormCheckbox, GlSprintf, GlTooltipDirective import { getIdFromGraphQLId } from '~/graphql_shared/utils'; import { isScopedLabel } from '~/lib/utils/common_utils'; -import { differenceInSeconds, getTimeago, SECONDS_IN_DAY } from '~/lib/utils/datetime_utility'; +import { getTimeago } from '~/lib/utils/datetime_utility'; import { isExternal, setUrlFragment } from '~/lib/utils/url_utility'; import { __, n__, sprintf } from '~/locale'; import IssuableAssignees from '~/issuable/components/issue_assignees.vue'; @@ -65,10 +65,6 @@ export default { issuableIid() { return this.issuable.iid; }, - createdInPastDay() { - const createdSecondsAgo = differenceInSeconds(new Date(this.issuable.createdAt), new Date()); - return createdSecondsAgo < SECONDS_IN_DAY; - }, author() { return this.issuable.author || {}; }, @@ -187,7 +183,7 @@ export default {
  • diff --git a/app/assets/javascripts/work_items/components/work_item_description.vue b/app/assets/javascripts/work_items/components/work_item_description.vue index 556a467fb6e..57930951856 100644 --- a/app/assets/javascripts/work_items/components/work_item_description.vue +++ b/app/assets/javascripts/work_items/components/work_item_description.vue @@ -7,7 +7,9 @@ import { confirmAction } from '~/lib/utils/confirm_via_gl_modal/confirm_via_gl_m import { __, s__ } from '~/locale'; import EditedAt from '~/issues/show/components/edited.vue'; import Tracking from '~/tracking'; +import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; import MarkdownField from '~/vue_shared/components/markdown/field.vue'; +import MarkdownEditor from '~/vue_shared/components/markdown/markdown_editor.vue'; import { getWorkItemQuery } from '../utils'; import workItemDescriptionSubscription from '../graphql/work_item_description.subscription.graphql'; import updateWorkItemMutation from '../graphql/update_work_item.mutation.graphql'; @@ -19,10 +21,11 @@ export default { EditedAt, GlButton, GlFormGroup, + MarkdownEditor, MarkdownField, WorkItemDescriptionRendered, }, - mixins: [Tracking.mixin()], + mixins: [glFeatureFlagMixin(), Tracking.mixin()], props: { workItemId: { type: String, @@ -133,7 +136,7 @@ export default { await this.$nextTick(); - this.$refs.textarea.focus(); + this.$refs.textarea?.focus(); }, async cancelEditing() { const isDirty = this.descriptionText !== this.workItemDescription?.description; @@ -200,6 +203,10 @@ export default { this.isSubmitting = false; }, + setDescriptionText(newText) { + this.descriptionText = newText; + updateDraft(this.autosaveKey, this.descriptionText); + }, handleDescriptionTextUpdated(newText) { this.descriptionText = newText; this.updateWorkItem(); @@ -216,7 +223,24 @@ export default { :label="__('Description')" label-for="work-item-description" > + li gl-emoji { .merge-request, .issue { - &.today { - background: $issues-today-bg; - border-color: $issues-today-border; - } - &.closed, &.merged { background: $gray-light; diff --git a/app/assets/stylesheets/themes/_dark.scss b/app/assets/stylesheets/themes/_dark.scss index 8e8cabbe511..ae33e9ef7d4 100644 --- a/app/assets/stylesheets/themes/_dark.scss +++ b/app/assets/stylesheets/themes/_dark.scss @@ -255,9 +255,6 @@ $popover-arrow-outer-color: $gray-800; $secondary: $gray-600; -$issues-today-bg: #333838; -$issues-today-border: #333a40; - $yiq-text-dark: $gray-50; $yiq-text-light: $gray-950; diff --git a/app/controllers/groups/boards_controller.rb b/app/controllers/groups/boards_controller.rb index c5a24be26fb..e1ba86220c7 100644 --- a/app/controllers/groups/boards_controller.rb +++ b/app/controllers/groups/boards_controller.rb @@ -8,7 +8,6 @@ class Groups::BoardsController < Groups::ApplicationController before_action do push_frontend_feature_flag(:board_multi_select, group) push_frontend_feature_flag(:apollo_boards, group) - push_frontend_feature_flag(:realtime_labels, group) experiment(:prominent_create_board_btn, subject: current_user) do |e| e.control {} e.candidate {} diff --git a/app/controllers/projects/boards_controller.rb b/app/controllers/projects/boards_controller.rb index 7af3cb9294a..84872d1e978 100644 --- a/app/controllers/projects/boards_controller.rb +++ b/app/controllers/projects/boards_controller.rb @@ -8,7 +8,6 @@ class Projects::BoardsController < Projects::ApplicationController before_action do push_frontend_feature_flag(:board_multi_select, project) push_frontend_feature_flag(:apollo_boards, project) - push_frontend_feature_flag(:realtime_labels, project&.group) experiment(:prominent_create_board_btn, subject: current_user) do |e| e.control {} e.candidate {} diff --git a/app/controllers/projects/issues_controller.rb b/app/controllers/projects/issues_controller.rb index d2edf1d8739..c2575629f0e 100644 --- a/app/controllers/projects/issues_controller.rb +++ b/app/controllers/projects/issues_controller.rb @@ -51,7 +51,6 @@ class Projects::IssuesController < Projects::ApplicationController before_action only: :show do push_frontend_feature_flag(:issue_assignees_widget, project) - push_frontend_feature_flag(:realtime_labels, project) push_frontend_feature_flag(:work_items_mvc, project&.group) push_force_frontend_feature_flag(:work_items_mvc_2, project&.work_items_mvc_2_feature_flag_enabled?) push_frontend_feature_flag(:epic_widget_edit_confirmation, project) diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb index daa193312bb..c4a8bcf0191 100644 --- a/app/controllers/projects/merge_requests_controller.rb +++ b/app/controllers/projects/merge_requests_controller.rb @@ -35,7 +35,6 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo push_frontend_feature_flag(:merge_request_widget_graphql, project) push_frontend_feature_flag(:core_security_mr_widget_counts, project) push_frontend_feature_flag(:issue_assignees_widget, @project) - push_frontend_feature_flag(:realtime_labels, project) push_frontend_feature_flag(:refactor_security_extension, @project) push_frontend_feature_flag(:refactor_code_quality_inline_findings, project) push_frontend_feature_flag(:moved_mr_sidebar, project) diff --git a/app/controllers/search_controller.rb b/app/controllers/search_controller.rb index 0310728d99f..a3eae32b381 100644 --- a/app/controllers/search_controller.rb +++ b/app/controllers/search_controller.rb @@ -9,7 +9,11 @@ class SearchController < ApplicationController RESCUE_FROM_TIMEOUT_ACTIONS = [:count, :show, :autocomplete, :aggregations].freeze - track_event :show, name: 'i_search_total', destinations: [:redis_hll, :snowplow] + track_custom_event :show, + name: 'i_search_total', + label: 'redis_hll_counters.search.search_total_unique_counts_monthly', + action: 'executed', + destinations: [:redis_hll, :snowplow] def self.search_rate_limited_endpoints %i[show count autocomplete] @@ -243,6 +247,10 @@ class SearchController < ApplicationController search_service.project&.namespace || search_service.group end + def tracking_project_source + search_service.project + end + def search_type 'basic' end diff --git a/app/graphql/types/issue_connection.rb b/app/graphql/types/issue_connection.rb index 8e5c88648ea..2f07888b43e 100644 --- a/app/graphql/types/issue_connection.rb +++ b/app/graphql/types/issue_connection.rb @@ -1,15 +1,22 @@ # frozen_string_literal: true # Normally this wouldn't be needed and we could use +# # type Types::IssueType.connection_type, null: true -# in a resolver. However we can end up with cyclic definitions, -# which can result in errors like +# +# in a resolver. However we can end up with cyclic definitions. +# Running the spec locally can result in errors like +# # NameError: uninitialized constant Resolvers::GroupIssuesResolver # -# Now we would use +# or other errors. To fix this, we created this file and use +# # type "Types::IssueConnection", null: true +# # which gives a delayed resolution, and the proper connection type. +# # See app/graphql/resolvers/base_issues_resolver.rb # Reference: https://github.com/rmosolgo/graphql-ruby/issues/3974#issuecomment-1084444214 - +# and https://docs.gitlab.com/ee/development/api_graphql_styleguide.html#testing-tips-and-tricks +# Types::IssueConnection = Types::IssueType.connection_type diff --git a/app/helpers/issues_helper.rb b/app/helpers/issues_helper.rb index b9c7602126e..932a50d9451 100644 --- a/app/helpers/issues_helper.rb +++ b/app/helpers/issues_helper.rb @@ -10,7 +10,6 @@ module IssuesHelper def issue_css_classes(issue) classes = ["issue"] classes << "closed" if issue.closed? - classes << "today" if issue.new? classes << "gl-cursor-grab" if @sort == 'relative_position' classes.join(' ') end diff --git a/app/models/concerns/issuable.rb b/app/models/concerns/issuable.rb index cdbc7092b63..14cb6659d03 100644 --- a/app/models/concerns/issuable.rb +++ b/app/models/concerns/issuable.rb @@ -464,18 +464,6 @@ module Issuable end end - def today? - Date.today == created_at.to_date - end - - def created_hours_ago - (Time.now.utc.to_i - created_at.utc.to_i) / 3600 - end - - def new? - created_hours_ago < 24 - end - def open? opened? end diff --git a/app/models/hooks/web_hook_log.rb b/app/models/hooks/web_hook_log.rb index c32957fbef9..2b26147b494 100644 --- a/app/models/hooks/web_hook_log.rb +++ b/app/models/hooks/web_hook_log.rb @@ -56,7 +56,7 @@ class WebHookLog < ApplicationRecord def redact_user_emails self.request_data.deep_transform_values! do |value| - value =~ URI::MailTo::EMAIL_REGEXP ? _('[REDACTED]') : value + value.to_s =~ URI::MailTo::EMAIL_REGEXP ? _('[REDACTED]') : value end end end diff --git a/config/feature_flags/development/realtime_labels.yml b/config/feature_flags/development/realtime_labels.yml deleted file mode 100644 index 0c047a09a6d..00000000000 --- a/config/feature_flags/development/realtime_labels.yml +++ /dev/null @@ -1,8 +0,0 @@ ---- -name: realtime_labels -introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/83743 -rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/357370 -milestone: '14.10' -type: development -group: group::project management -default_enabled: true diff --git a/config/initializers/google_api_client_patch.rb b/config/initializers/google_api_client_patch.rb index 1408dcb0501..2a832790f97 100644 --- a/config/initializers/google_api_client_patch.rb +++ b/config/initializers/google_api_client_patch.rb @@ -1,8 +1,9 @@ # frozen_string_literal: true require 'google/apis/core/http_command' +require 'google/apis/version' -raise 'This patch is only tested with google-api-client-ruby v0.50.0' unless Google::Apis::VERSION == "0.50.0" +raise 'This patch is only tested with google-api-client-ruby v0.53.0' unless Google::Apis::VERSION == "0.53.0" # The google-api-ruby-client does not have a way to increase or disable # the maximum allowed time for a request to be retried. By default, it diff --git a/data/removals/16_0/source_code-approvals-endpoint.yml b/data/removals/16_0/source_code-approvals-endpoint.yml new file mode 100644 index 00000000000..e754c1cfebf --- /dev/null +++ b/data/removals/16_0/source_code-approvals-endpoint.yml @@ -0,0 +1,20 @@ +- name: "Changing merge request approvals with the `/approvals` API endpoint" + announcement_milestone: "12.3" + announcement_date: "2019-09-22" + removal_milestone: "16.0" + removal_date: "2023-03-22" + breaking_change: true + reporter: tlinz + stage: Create + issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/353097 + body: | # (required) Do not modify this line, instead modify the lines below. + To change the approvals required for a merge request, you should no longer use the `/approvals` API endpoint, which was deprecated in GitLab 12.3. + + Instead, use the [`/approval_rules` endpoint](https://docs.gitlab.com/ee/api/merge_request_approvals.html#merge-request-level-mr-approvals) to [create](https://docs.gitlab.com/ee/api/merge_request_approvals.html#create-merge-request-level-rule) or [update](https://docs.gitlab.com/ee/api/merge_request_approvals.html#update-merge-request-level-rule) the approval rules for a merge request. +# +# OPTIONAL FIELDS +# + tiers: Premium + documentation_url: https://docs.gitlab.com/ee/api/merge_request_approvals.html + image_url: # (optional) This is a link to a thumbnail image depicting the feature + video_url: # (optional) Use the youtube thumbnail URL with the structure of https://img.youtube.com/vi/UNIQUEID/hqdefault.jpg diff --git a/doc/api/graphql/reference/index.md b/doc/api/graphql/reference/index.md index 132dfabeda7..2750ad6a2cd 100644 --- a/doc/api/graphql/reference/index.md +++ b/doc/api/graphql/reference/index.md @@ -8581,6 +8581,52 @@ The edge type for [`PipelineSecurityReportFinding`](#pipelinesecurityreportfindi | `cursor` | [`String!`](#string) | A cursor for use in pagination. | | `node` | [`PipelineSecurityReportFinding`](#pipelinesecurityreportfinding) | The item at the end of the edge. | +#### `ProductAnalyticsDashboardConnection` + +The connection type for [`ProductAnalyticsDashboard`](#productanalyticsdashboard). + +##### Fields + +| Name | Type | Description | +| ---- | ---- | ----------- | +| `edges` | [`[ProductAnalyticsDashboardEdge]`](#productanalyticsdashboardedge) | A list of edges. | +| `nodes` | [`[ProductAnalyticsDashboard]`](#productanalyticsdashboard) | A list of nodes. | +| `pageInfo` | [`PageInfo!`](#pageinfo) | Information to aid in pagination. | + +#### `ProductAnalyticsDashboardEdge` + +The edge type for [`ProductAnalyticsDashboard`](#productanalyticsdashboard). + +##### Fields + +| Name | Type | Description | +| ---- | ---- | ----------- | +| `cursor` | [`String!`](#string) | A cursor for use in pagination. | +| `node` | [`ProductAnalyticsDashboard`](#productanalyticsdashboard) | The item at the end of the edge. | + +#### `ProductAnalyticsDashboardWidgetConnection` + +The connection type for [`ProductAnalyticsDashboardWidget`](#productanalyticsdashboardwidget). + +##### Fields + +| Name | Type | Description | +| ---- | ---- | ----------- | +| `edges` | [`[ProductAnalyticsDashboardWidgetEdge]`](#productanalyticsdashboardwidgetedge) | A list of edges. | +| `nodes` | [`[ProductAnalyticsDashboardWidget]`](#productanalyticsdashboardwidget) | A list of nodes. | +| `pageInfo` | [`PageInfo!`](#pageinfo) | Information to aid in pagination. | + +#### `ProductAnalyticsDashboardWidgetEdge` + +The edge type for [`ProductAnalyticsDashboardWidget`](#productanalyticsdashboardwidget). + +##### Fields + +| Name | Type | Description | +| ---- | ---- | ----------- | +| `cursor` | [`String!`](#string) | A cursor for use in pagination. | +| `node` | [`ProductAnalyticsDashboardWidget`](#productanalyticsdashboardwidget) | The item at the end of the edge. | + #### `ProjectConnection` The connection type for [`Project`](#project). @@ -16483,6 +16529,29 @@ Represents vulnerability finding of a security report on the pipeline. | `newBillableUserCount` | [`Int`](#int) | Total number of billable users after change. | | `seatsInSubscription` | [`Int`](#int) | Number of seats in subscription. | +### `ProductAnalyticsDashboard` + +Represents a product analytics dashboard. + +#### Fields + +| Name | Type | Description | +| ---- | ---- | ----------- | +| `description` | [`String`](#string) | Description of the dashboard. | +| `title` | [`String!`](#string) | Title of the dashboard. | +| `widgets` | [`ProductAnalyticsDashboardWidgetConnection!`](#productanalyticsdashboardwidgetconnection) | Widgets shown on the dashboard. (see [Connections](#connections)) | + +### `ProductAnalyticsDashboardWidget` + +Represents a product analytics dashboard widget. + +#### Fields + +| Name | Type | Description | +| ---- | ---- | ----------- | +| `gridAttributes` | [`JSON`](#json) | Description of the position and size of the widget. | +| `title` | [`String!`](#string) | Title of the widget. | + ### `Project` #### Fields @@ -17401,6 +17470,26 @@ four standard [pagination arguments](#connection-pagination-arguments): | `updatedBefore` | [`Time`](#time) | Pipelines updated before this date. | | `username` | [`String`](#string) | Filter pipelines by the user that triggered the pipeline. | +##### `Project.productAnalyticsDashboards` + +Product Analytics dashboards of the project. + +WARNING: +**Introduced** in 15.6. +This feature is in Alpha. It can be changed or removed at any time. + +Returns [`ProductAnalyticsDashboardConnection`](#productanalyticsdashboardconnection). + +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 | +| ---- | ---- | ----------- | +| `slug` | [`String`](#string) | Find by dashboard slug. | + ##### `Project.projectMembers` Members of the project. diff --git a/doc/api/merge_request_approvals.md b/doc/api/merge_request_approvals.md index e65263030b1..0476035784a 100644 --- a/doc/api/merge_request_approvals.md +++ b/doc/api/merge_request_approvals.md @@ -6,6 +6,8 @@ info: "To determine the technical writer assigned to the Stage/Group associated # Merge request approvals API **(PREMIUM)** +> Changing approval configuration with the `/approvals` endpoint was [deprecated](https://gitlab.com/gitlab-org/gitlab/-/issues/11132) in GitLab 12.3. + Configuration for [approvals on all merge requests](../user/project/merge_requests/approvals/index.md) in the project. Must be authenticated for all endpoints. @@ -57,7 +59,7 @@ Supported attributes: | Attribute | Type | Required | Description | | ------------------------------------------------ | ------- | -------- | -- | | `id` | integer or string | **{check-circle}** Yes | The ID or [URL-encoded path of a project](index.md#namespaced-path-encoding). | -| `approvals_before_merge` | integer | **{dotted-circle}** No | How many approvals are required before a merge request can be merged. Deprecated in GitLab 12.0 in favor of Approval Rules API. | +| `approvals_before_merge` (deprecated) | integer | **{dotted-circle}** No | How many approvals are required before a merge request can be merged. [Deprecated](https://gitlab.com/gitlab-org/gitlab/-/issues/11132) in GitLab 12.3. | | `disable_overriding_approvers_per_merge_request` | boolean | **{dotted-circle}** No | Allow or prevent overriding approvers per merge request. | | `merge_requests_author_approval` | boolean | **{dotted-circle}** No | Allow or prevent authors from self approving merge requests; `true` means authors can self approve. | | `merge_requests_disable_committers_approval` | boolean | **{dotted-circle}** No | Allow or prevent committers from self approving merge requests. | @@ -582,9 +584,16 @@ Supported attributes: } ``` -### Change approval configuration +### Change approval configuration (deprecated) -> Moved to GitLab Premium in 13.9. +> - Moved to GitLab Premium in GitLab 13.9. +> - Endpoint `/approvals` [deprecated](https://gitlab.com/gitlab-org/gitlab/-/issues/11132) in GitLab 12.3. + +WARNING: +The `/approvals` endpoint was [deprecated](https://gitlab.com/gitlab-org/gitlab/-/issues/11132) in GitLab 12.3 +and is planned for removal in 16.0. To change the approvals required for a merge request, +use the `/approval_rules` endpoint described in [Create merge request level rule](#create-merge-request-level-rule). +on this page. This change is a breaking change. If you are allowed to, you can change `approvals_required` using the following endpoint: @@ -598,7 +607,7 @@ Supported attributes: | Attribute | Type | Required | Description | |----------------------|-------------------|----------|-------------| | `id` | integer or string | **{check-circle}** Yes | The ID or [URL-encoded path of a project](index.md#namespaced-path-encoding). | -| `approvals_required` | integer | **{check-circle}** Yes | Approvals required before MR can be merged. Deprecated in GitLab 12.0 in favor of Approval Rules API. | +| `approvals_required` | integer | **{check-circle}** Yes | Approvals required before MR can be merged. | | `merge_request_iid` | integer | **{check-circle}** Yes | The IID of the merge request. | ```json diff --git a/doc/api/status_checks.md b/doc/api/status_checks.md index a30bfbd4dfb..e6a9c633418 100644 --- a/doc/api/status_checks.md +++ b/doc/api/status_checks.md @@ -121,8 +121,8 @@ defined external service. This includes confidential merge requests. | Attribute | Type | Required | Description | |------------------------|------------------|----------|------------------------------------------------| | `id` | integer | yes | ID of a project | -| `name` | string | yes | Display name of status check | -| `external_url` | string | yes | URL of status check resource | +| `name` | string | yes | Display name of external status check | +| `external_url` | string | yes | URL of external status check resource | | `protected_branch_ids` | `array` | no | IDs of protected branches to scope the rule by | ## Delete external status check @@ -135,7 +135,7 @@ DELETE /projects/:id/external_status_checks/:check_id | Attribute | Type | Required | Description | |------------------------|----------------|----------|-----------------------| -| `rule_id` | integer | yes | ID of an status check | +| `check_id` | integer | yes | ID of an external status check | | `id` | integer | yes | ID of a project | ## Update external status check @@ -149,8 +149,8 @@ PUT /projects/:id/external_status_checks/:check_id | Attribute | Type | Required | Description | |------------------------|------------------|----------|------------------------------------------------| | `id` | integer | yes | ID of a project | -| `rule_id` | integer | yes | ID of an external status check | -| `name` | string | no | Display name of status check | +| `check_id` | integer | yes | ID of an external status check | +| `name` | string | no | Display name of external status check | | `external_url` | string | no | URL of external status check resource | | `protected_branch_ids` | `array` | no | IDs of protected branches to scope the rule by | diff --git a/doc/development/api_graphql_styleguide.md b/doc/development/api_graphql_styleguide.md index c6138c9c646..c19341a1404 100644 --- a/doc/development/api_graphql_styleguide.md +++ b/doc/development/api_graphql_styleguide.md @@ -2178,32 +2178,44 @@ end ```ruby NameError: uninitialized constant Resolvers::GroupIssuesResolver + + or + + GraphQL::Pagination::Connections::ImplementationMissingError ``` + though you might see something different. + To fix this, we must create a new file that encapsulates the connection type, and then reference it using double quotes. This gives a delayed resolution, and the proper connection type. For example: - ```ruby - module Types - # rubocop: disable Graphql/AuthorizeTypes - class IssueConnectionType < CountableConnectionType - end - end + [app/graphql/resolvers/base_issues_resolver.rb](https://gitlab.com/gitlab-org/gitlab/-/blob/master/app/graphql/resolvers/base_issues_resolver.rb) + originally contained the line - Types::IssueConnectionType.prepend_mod_with('Types::IssueConnectionType') + ```ruby + type Types::IssueType.connection_type, null: true ``` - in [types/issue_connection_type.rb](https://gitlab.com/gitlab-org/gitlab/-/blob/master/app/graphql/types/issue_connection_type.rb) - defines a new `Types::IssueConnectionType`, and is then referenced in - [app/graphql/resolvers/base_issues_resolver.rb](https://gitlab.com/gitlab-org/gitlab/-/blob/master/app/graphql/resolvers/base_issues_resolver.rb) + Running the specs locally for this file caused the + `NameError: uninitialized constant Resolvers::GroupIssuesResolver` error. + + The fix was to create a new file, [app/graphql/types/issue_connection.rb](https://gitlab.com/gitlab-org/gitlab/-/blob/master/app/graphql/types/issue_connection.rb) with the + line: + + ```ruby + Types::IssueConnection = Types::IssueType.connection_type + ``` + + and in [app/graphql/resolvers/base_issues_resolver.rb](https://gitlab.com/gitlab-org/gitlab/-/blob/master/app/graphql/resolvers/base_issues_resolver.rb) + we use the line ```ruby type "Types::IssueConnection", null: true ``` Only use this style if you are having spec failures. This is not intended to be a new - pattern that we use. This issue may disappear after we've upgraded to `2.x`. + pattern that we use. This issue should disappear after we've upgraded to `2.x`. - There can be instances where a spec fails because the class is not loaded correctly. It relates to the diff --git a/doc/development/internal_api/index.md b/doc/development/internal_api/index.md index 9b77a20a0d6..68d9b88bc05 100644 --- a/doc/development/internal_api/index.md +++ b/doc/development/internal_api/index.md @@ -496,8 +496,6 @@ metric counters. | Attribute | Type | Required | Description | |:---------------------------------------------------------------------------|:--------------|:---------|:-----------------------------------------------------------------------------------------------------------------| -| `gitops_sync_count` (DEPRECATED) | integer | no | The number to increase the `gitops_sync` counter by | -| `k8s_api_proxy_request_count` (DEPRECATED) | integer | no | The number to increase the `k8s_api_proxy_request` counter by | | `counters` | hash | no | The number to increase the `k8s_api_proxy_request` counter by | | `counters["k8s_api_proxy_request"]` | integer | no | The number to increase the `k8s_api_proxy_request` counter by | | `counters["gitops_sync"]` | integer | no | The number to increase the `gitops_sync` counter by | @@ -512,7 +510,7 @@ Example Request: ```shell curl --request POST --header "Gitlab-Kas-Api-Request: " --header "Content-Type: application/json" \ - --data '{"gitops_sync_count":1}' "http://localhost:3000/api/v4/internal/kubernetes/usage_metrics" + --data '{"counters": {"gitops_sync":1}}' "http://localhost:3000/api/v4/internal/kubernetes/usage_metrics" ``` ### Create Starboard vulnerability diff --git a/doc/development/service_ping/metrics_lifecycle.md b/doc/development/service_ping/metrics_lifecycle.md index f13aebeb16e..92c5d2d317d 100644 --- a/doc/development/service_ping/metrics_lifecycle.md +++ b/doc/development/service_ping/metrics_lifecycle.md @@ -25,6 +25,10 @@ Any such changes lead to inconsistent reports from multiple GitLab instances. If there is a problem with an existing metric, it's best to deprecate the existing metric, and use it, side by side, with the desired new metric. +If you do need to change a metric, please notify the Customer Success Ops team (`@csops-team`), Analytics Engineers (`@gitlab-data/analytics-engineers`), and Product Analysts (`@gitlab-data/product-analysts`) teams by `@` mentioning those groups in a comment on the MR. +Many Service Ping metrics are relied upon for health score and XMAU reporting and +unexpected changes to those metrics could break reporting. + Example: Consider following change. Before GitLab 12.6, the `example_metric` was implemented as: @@ -135,3 +139,6 @@ To remove a metric: 1. Remove any other records related to the metric: - The feature flag YAML file at [`config/feature_flags/*/*.yaml`](https://gitlab.com/gitlab-org/gitlab/-/tree/master/config/feature_flags). - The entry in the known events YAML file at [`lib/gitlab/usage_data_counters/known_events/*.yaml`](https://gitlab.com/gitlab-org/gitlab/-/tree/master/lib/gitlab/usage_data_counters/known_events). + +1. Notify the Customer Success Ops team (`@csops-team`), Analytics Engineers (`@gitlab-data/analytics-engineers`), and Product Analysts (`@gitlab-data/product-analysts`) by `@` mentioning those groups in a comment on the MR. + Many Service Ping metrics are relied upon for health score and XMAU reporting and unexpected changes to those metrics could break reporting. diff --git a/doc/development/service_ping/review_guidelines.md b/doc/development/service_ping/review_guidelines.md index a1806551303..70f7f3dca54 100644 --- a/doc/development/service_ping/review_guidelines.md +++ b/doc/development/service_ping/review_guidelines.md @@ -68,6 +68,7 @@ are regular backend changes. Read the [stages file](https://gitlab.com/gitlab-com/www-gitlab-com/blob/master/data/stages.yml). - Check the file location. Consider the time frame, and if the file should be under `ee`. - Check the tiers. +- If a metric was changed or removed: Make sure the MR author notified the Customer Success Ops team (`@csops-team`), Analytics Engineers (`@gitlab-data/analytics-engineers`), and Product Analysts (`@gitlab-data/product-analysts`) by `@` mentioning those groups in a comment on the MR. - Metrics instrumentations - Recommend using metrics instrumentation for new metrics, [if possible](metrics_instrumentation.md#support-for-instrumentation-classes). - Approve the MR, and relabel the MR with `~"product intelligence::approved"`. diff --git a/doc/update/removals.md b/doc/update/removals.md index 84450006d82..0bc82403f60 100644 --- a/doc/update/removals.md +++ b/doc/update/removals.md @@ -31,6 +31,18 @@ For removal reviewers (Technical Writers only): https://about.gitlab.com/handbook/marketing/blog/release-posts/#update-the-removals-doc --> +## Removed in 16.0 + +### Changing merge request approvals with the `/approvals` API endpoint + +WARNING: +This is a [breaking change](https://docs.gitlab.com/ee/development/deprecation_guidelines/). +Review the details carefully before upgrading. + +To change the approvals required for a merge request, you should no longer use the `/approvals` API endpoint, which was deprecated in GitLab 12.3. + +Instead, use the [`/approval_rules` endpoint](https://docs.gitlab.com/ee/api/merge_request_approvals.html#merge-request-level-mr-approvals) to [create](https://docs.gitlab.com/ee/api/merge_request_approvals.html#create-merge-request-level-rule) or [update](https://docs.gitlab.com/ee/api/merge_request_approvals.html#update-merge-request-level-rule) the approval rules for a merge request. + ## Removed in 15.4 ### SAST analyzer consolidation and CI/CD template changes diff --git a/doc/user/application_security/iac_scanning/index.md b/doc/user/application_security/iac_scanning/index.md index 026567383be..1c14c529523 100644 --- a/doc/user/application_security/iac_scanning/index.md +++ b/doc/user/application_security/iac_scanning/index.md @@ -4,7 +4,7 @@ group: Static Analysis info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/product/ux/technical-writing/#assignments --- -# Infrastructure as Code (IaC) Scanning +# Infrastructure as Code (IaC) Scanning **(FREE)** > [Introduced](https://gitlab.com/groups/gitlab-org/-/epics/6655) in GitLab 14.5. diff --git a/doc/user/award_emojis.md b/doc/user/award_emojis.md index ea631b0e251..5df38550c40 100644 --- a/doc/user/award_emojis.md +++ b/doc/user/award_emojis.md @@ -46,5 +46,9 @@ To remove an award emoji, select the emoji again. You can upload custom emojis to a GitLab instance with the GraphQL API. For more, visit [Use custom emojis with GraphQL](../api/graphql/custom_emoji.md). +Custom emojis don't show in the emoji picker. +To use them in a text box, type the filename without the extension and surrounded by colons. +For example, for a file named `thank-you.png`, type `:thank-you:`. + For the list of custom emojis available for GitLab.com, visit [the `custom_emoji` project](https://gitlab.com/custom_emoji/custom_emoji/-/tree/main/img). diff --git a/doc/user/project/issues/managing_issues.md b/doc/user/project/issues/managing_issues.md index 9baf6d99b61..25fcd376b4f 100644 --- a/doc/user/project/issues/managing_issues.md +++ b/doc/user/project/issues/managing_issues.md @@ -673,7 +673,7 @@ you can see the change without having to refresh the page. The following sections are updated in real time: - [Assignee](#assignee) -- Labels, [if enabled](../labels.md#real-time-changes-to-labels) +- [Labels](../labels.md#assign-and-unassign-labels) ## Assignee diff --git a/doc/user/project/labels.md b/doc/user/project/labels.md index b2135cd3e42..bb72ab0052d 100644 --- a/doc/user/project/labels.md +++ b/doc/user/project/labels.md @@ -28,10 +28,21 @@ You can use two types of labels in GitLab: ## Assign and unassign labels -> Unassigning labels with the **X** button [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/216881) in GitLab 13.5. +> - Unassigning labels with the **X** button [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/216881) in GitLab 13.5. +> - Real-time updates in the sidebar [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/241538) in GitLab 14.10 with a [feature flag](../../administration/feature_flags.md) named `realtime_labels`, disabled by default. +> - Real-time updates in the sidebar [enabled on GitLab.com](https://gitlab.com/gitlab-org/gitlab/-/issues/357370#note_991987201) in GitLab 15.1. +> - Real-time updates in the sidebar [enabled by default](https://gitlab.com/gitlab-org/gitlab/-/issues/357370) in GitLab 15.5. +> - Real-time updates in the sidebar [generally available](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/103199) in GitLab 15.6. Feature flag `realtime_labels` removed. You can assign labels to any issue, merge request, or epic. +Changed labels are immediately visible to other users, without refreshing the page, on the following: + +- Epics +- Incidents +- Issues +- Merge requests + To assign or unassign a label: 1. In the **Labels** section of the sidebar, select **Edit**. @@ -444,23 +455,6 @@ The labels higher in the list get higher priority. To learn what happens when you sort by priority or label priority, see [Sorting and ordering issue lists](issues/sorting_issue_lists.md). -## Real-time changes to labels - -> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/241538) in GitLab 14.10 with a [feature flag](../../administration/feature_flags.md) named `realtime_labels`, disabled by default. -> - [Enabled on GitLab.com](https://gitlab.com/gitlab-org/gitlab/-/issues/357370#note_991987201) in GitLab 15.1. -> - [Enabled by default](https://gitlab.com/gitlab-org/gitlab/-/issues/357370) in GitLab 15.5. - -FLAG: -On self-managed GitLab, to prevent updating labels in real-time, you can ask an administrator to [disable the feature flag](../../administration/feature_flags.md) named `realtime_labels`. -On GitLab.com, this feature is available. - -Changed labels are immediately visible to other users, without refreshing the page, on the following: - -- Epics -- Incidents -- Issues -- Merge requests - ## Troubleshooting ### Some label titles end with `_duplicate` diff --git a/lib/api/api.rb b/lib/api/api.rb index 3db3799bcd9..41884731489 100644 --- a/lib/api/api.rb +++ b/lib/api/api.rb @@ -195,6 +195,7 @@ module API mount ::API::FreezePeriods mount ::API::GroupClusters mount ::API::GroupExport + mount ::API::GroupVariables mount ::API::ImportBitbucketServer mount ::API::ImportGithub mount ::API::Keys @@ -272,7 +273,6 @@ module API mount ::API::GroupLabels mount ::API::GroupMilestones mount ::API::GroupPackages - mount ::API::GroupVariables mount ::API::Groups mount ::API::HelmPackages mount ::API::Integrations diff --git a/lib/api/group_variables.rb b/lib/api/group_variables.rb index e60f58a62e8..a42f9045b9d 100644 --- a/lib/api/group_variables.rb +++ b/lib/api/group_variables.rb @@ -11,12 +11,14 @@ module API helpers ::API::Helpers::VariablesHelpers params do - requires :id, type: String, desc: 'The ID of a group' + requires :id, type: String, desc: 'The ID of a group or URL-encoded path of the group owned by the authenticated + user' end resource :groups, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do - desc 'Get group-level variables' do + desc 'Get a list of group-level variables' do success Entities::Ci::Variable + tags %w[ci_variables] end params do use :pagination @@ -26,8 +28,10 @@ module API present paginate(variables), with: Entities::Ci::Variable end - desc 'Get a specific variable from a group' do + desc 'Get the details of a group’s specific variable' do success Entities::Ci::Variable + failure [{ code: 404, message: 'Group Variable Not Found' }] + tags %w[ci_variables] end params do requires :key, type: String, desc: 'The key of the variable' @@ -42,16 +46,19 @@ module API desc 'Create a new variable in a group' do success Entities::Ci::Variable + failure [{ code: 400, message: '400 Bad Request' }] + tags %w[ci_variables] end route_setting :log_safety, { safe: %w[key], unsafe: %w[value] } params do - requires :key, type: String, desc: 'The key of the variable' - requires :value, type: String, desc: 'The value of the variable' + requires :key, type: String, desc: 'The ID of a group or URL-encoded path of the group owned by the + authenticated user' + requires :value, type: String, desc: 'The value of a variable' optional :protected, type: String, desc: 'Whether the variable is protected' optional :masked, type: String, desc: 'Whether the variable is masked' optional :raw, type: String, desc: 'Whether the variable will be expanded' - optional :variable_type, type: String, values: ::Ci::GroupVariable.variable_types.keys, desc: 'The type of variable, must be one of env_var or file. Defaults to env_var' - + optional :variable_type, type: String, values: ::Ci::GroupVariable.variable_types.keys, desc: 'The type of the variable. Default: env_var' + optional :environment_scope, type: String, desc: 'The environment scope of a variable' use :optional_group_variable_params_ee end post ':id/variables' do @@ -75,15 +82,18 @@ module API desc 'Update an existing variable from a group' do success Entities::Ci::Variable + failure [{ code: 400, message: '400 Bad Request' }, { code: 404, message: 'Group Variable Not Found' }] + tags %w[ci_variables] end route_setting :log_safety, { safe: %w[key], unsafe: %w[value] } params do - optional :key, type: String, desc: 'The key of the variable' - optional :value, type: String, desc: 'The value of the variable' + optional :key, type: String, desc: 'The key of a variable' + optional :value, type: String, desc: 'The value of a variable' optional :protected, type: String, desc: 'Whether the variable is protected' optional :masked, type: String, desc: 'Whether the variable is masked' optional :raw, type: String, desc: 'Whether the variable will be expanded' - optional :variable_type, type: String, values: ::Ci::GroupVariable.variable_types.keys, desc: 'The type of variable, must be one of env_var or file' + optional :variable_type, type: String, values: ::Ci::GroupVariable.variable_types.keys, desc: 'The type of the variable. Default: env_var' + optional :environment_scope, type: String, desc: 'The environment scope of a variable' use :optional_group_variable_params_ee end @@ -110,9 +120,11 @@ module API desc 'Delete an existing variable from a group' do success Entities::Ci::Variable + failure [{ code: 404, message: 'Group Variable Not Found' }] + tags %w[ci_variables] end params do - requires :key, type: String, desc: 'The key of the variable' + requires :key, type: String, desc: 'The key of a variable' end delete ':id/variables/:key' do variable = find_variable(user_group, params) diff --git a/lib/api/internal/kubernetes.rb b/lib/api/internal/kubernetes.rb index 6f964d5636b..d06d1e9862a 100644 --- a/lib/api/internal/kubernetes.rb +++ b/lib/api/internal/kubernetes.rb @@ -61,15 +61,6 @@ module API Guest.can?(:download_code, project) || agent.has_access_to?(project) end - def count_events - strong_memoize(:count_events) do - events = params.slice(:gitops_sync_count, :k8s_api_proxy_request_count) - events.transform_keys! { |event| event.to_s.chomp('_count') } - events = params[:counters]&.slice(:gitops_sync, :k8s_api_proxy_request) unless events.present? - events - end - end - def increment_unique_events events = params[:unique_counters]&.slice(:agent_users_using_ci_tunnel) @@ -77,6 +68,12 @@ module API increment_unique_values(event, entity_ids) end end + + def increment_count_events + events = params[:counters]&.slice(:gitops_sync, :k8s_api_proxy_request) + + Gitlab::UsageDataCounters::KubernetesAgentCounter.increment_event_counts(events) + end end namespace 'internal' do @@ -144,26 +141,17 @@ module API detail 'Updates usage metrics for agent' end params do - # Todo: Remove gitops_sync_count and k8s_api_proxy_request_count in the next milestone - # https://gitlab.com/gitlab-org/gitlab/-/issues/369489 - # We're only keeping it for backwards compatibility until KAS is released - # using `counts:` instead - optional :gitops_sync_count, type: Integer, desc: 'The count to increment the gitops_sync metric by' - optional :k8s_api_proxy_request_count, type: Integer, desc: 'The count to increment the k8s_api_proxy_request_count metric by' optional :counters, type: Hash do optional :gitops_sync, type: Integer, desc: 'The count to increment the gitops_sync metric by' - optional :k8s_api_proxy_request, type: Integer, desc: 'The count to increment the k8s_api_proxy_request_count metric by' + optional :k8s_api_proxy_request, type: Integer, desc: 'The count to increment the k8s_api_proxy_request metric by' end - mutually_exclusive :counters, :gitops_sync_count - mutually_exclusive :counters, :k8s_api_proxy_request_count optional :unique_counters, type: Hash do optional :agent_users_using_ci_tunnel, type: Set[Integer], desc: 'A set of user ids that have interacted a CI Tunnel to' end end post '/' do - Gitlab::UsageDataCounters::KubernetesAgentCounter.increment_event_counts(count_events) if count_events - + increment_count_events increment_unique_events no_content! diff --git a/lib/gitlab/ci/parsers/security/common.rb b/lib/gitlab/ci/parsers/security/common.rb index c437bec965f..0ac012b9fd1 100644 --- a/lib/gitlab/ci/parsers/security/common.rb +++ b/lib/gitlab/ci/parsers/security/common.rb @@ -41,7 +41,7 @@ module Gitlab private - attr_reader :json_data, :report, :validate + attr_reader :json_data, :report, :validate, :project def valid? return true unless validate diff --git a/lib/gitlab/ci/reports/security/finding.rb b/lib/gitlab/ci/reports/security/finding.rb index 911a7f5d358..dd9b9cc6d55 100644 --- a/lib/gitlab/ci/reports/security/finding.rb +++ b/lib/gitlab/ci/reports/security/finding.rb @@ -156,6 +156,14 @@ module Gitlab signatures.present? end + def false_positive? + flags.any?(&:false_positive?) + end + + def remediation_byte_offsets + remediations.map(&:byte_offsets).compact + end + def raw_metadata @raw_metadata ||= original_data.to_json end @@ -176,6 +184,10 @@ module Gitlab original_data['location'] end + def assets + original_data['assets'] || [] + end + # Returns either the max priority signature hex # or the location fingerprint def location_fingerprint diff --git a/lib/gitlab/ci/reports/security/flag.rb b/lib/gitlab/ci/reports/security/flag.rb index 8370dd60418..e1fbd4c0eff 100644 --- a/lib/gitlab/ci/reports/security/flag.rb +++ b/lib/gitlab/ci/reports/security/flag.rb @@ -27,6 +27,10 @@ module Gitlab description: description }.compact end + + def false_positive? + flag_type == :false_positive + end end end end diff --git a/lib/gitlab/ci/variables/builder.rb b/lib/gitlab/ci/variables/builder.rb index cf5f04215ad..8db8ea3a720 100644 --- a/lib/gitlab/ci/variables/builder.rb +++ b/lib/gitlab/ci/variables/builder.rb @@ -171,16 +171,6 @@ module Gitlab end end - def strong_memoize_with(name, *args) - container = strong_memoize(name) { {} } - - if container.key?(args) - container[args] - else - container[args] = yield - end - end - def release return unless @pipeline.tag? diff --git a/lib/gitlab/config_checker/external_database_checker.rb b/lib/gitlab/config_checker/external_database_checker.rb index 64950fb4eef..ff20833b5be 100644 --- a/lib/gitlab/config_checker/external_database_checker.rb +++ b/lib/gitlab/config_checker/external_database_checker.rb @@ -9,19 +9,23 @@ module Gitlab 'database requirements' def check - unsupported_database = Gitlab::Database + unsupported_databases = Gitlab::Database .database_base_models - .map { |_, model| Gitlab::Database::Reflection.new(model) } - .reject(&:postgresql_minimum_supported_version?) + .each_with_object({}) do |(database_name, base_model), databases| + database = Gitlab::Database::Reflection.new(base_model) - unsupported_database.map do |database| + databases[database_name] = database unless database.postgresql_minimum_supported_version? + end + + unsupported_databases.map do |database_name, database| { type: 'warning', - message: _('You are using PostgreSQL %{pg_version_current}, but PostgreSQL ' \ - '%{pg_version_minimum} is required for this version of GitLab. ' \ + message: _('Database \'%{database_name}\' is using PostgreSQL %{pg_version_current}, ' \ + 'but PostgreSQL %{pg_version_minimum} is required for this version of GitLab. ' \ 'Please upgrade your environment to a supported PostgreSQL version, ' \ 'see %{pg_requirements_url} for details.') % \ { + database_name: database_name, pg_version_current: database.version, pg_version_minimum: Gitlab::Database::MINIMUM_POSTGRES_VERSION, pg_requirements_url: PG_REQUIREMENTS_LINK diff --git a/lib/gitlab/usage_data_counters/kubernetes_agent_counter.rb b/lib/gitlab/usage_data_counters/kubernetes_agent_counter.rb index 8b9ca0fc220..d6e05f30a0d 100644 --- a/lib/gitlab/usage_data_counters/kubernetes_agent_counter.rb +++ b/lib/gitlab/usage_data_counters/kubernetes_agent_counter.rb @@ -8,6 +8,8 @@ module Gitlab class << self def increment_event_counts(events) + return unless events.present? + validate!(events) events.each do |event, incr| diff --git a/lib/gitlab/utils/strong_memoize.rb b/lib/gitlab/utils/strong_memoize.rb index cb3c8f181f9..b3d14d2b2fc 100644 --- a/lib/gitlab/utils/strong_memoize.rb +++ b/lib/gitlab/utils/strong_memoize.rb @@ -45,6 +45,16 @@ module Gitlab end end + def strong_memoize_with(name, *args) + container = strong_memoize(name) { {} } + + if container.key?(args) + container[args] + else + container[args] = yield + end + end + def strong_memoized?(name) instance_variable_defined?(ivar(name)) end diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 15a9eeb8a60..adc909b0ab0 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -12597,6 +12597,9 @@ msgstr "" msgid "Data type" msgstr "" +msgid "Database '%{database_name}' is using PostgreSQL %{pg_version_current}, but PostgreSQL %{pg_version_minimum} is required for this version of GitLab. Please upgrade your environment to a supported PostgreSQL version, see %{pg_requirements_url} for details." +msgstr "" + msgid "Database update failed" msgstr "" @@ -32257,6 +32260,9 @@ msgstr "" msgid "ProjectSettings|Users can only push commits to this repository if the committer email is one of their own verified emails." msgstr "" +msgid "ProjectSettings|Users can only push commits to this repository if the committer name is consistent with their git config username." +msgstr "" + msgid "ProjectSettings|Users can request access" msgstr "" @@ -33295,6 +33301,9 @@ msgstr "" msgid "PushRule|Push rules" msgstr "" +msgid "PushRule|Reject inconsistent user name" +msgstr "" + msgid "PushRule|Reject unverified users" msgstr "" @@ -46436,9 +46445,6 @@ msgstr "" msgid "You are trying to upload something other than an image. Please upload a .png, .jpg, .jpeg, .gif, .bmp, .tiff or .ico." msgstr "" -msgid "You are using PostgreSQL %{pg_version_current}, but PostgreSQL %{pg_version_minimum} is required for this version of GitLab. Please upgrade your environment to a supported PostgreSQL version, see %{pg_requirements_url} for details." -msgstr "" - msgid "You can %{gitlabLinkStart}resolve conflicts on GitLab%{gitlabLinkEnd} or %{resolveLocallyStart}resolve it locally%{resolveLocallyEnd}." msgstr "" diff --git a/spec/controllers/search_controller_spec.rb b/spec/controllers/search_controller_spec.rb index 392dc2229aa..21df53fb074 100644 --- a/spec/controllers/search_controller_spec.rb +++ b/spec/controllers/search_controller_spec.rb @@ -223,7 +223,14 @@ RSpec.describe SearchController do let(:project) { nil } let(:category) { described_class.to_s } - let(:action) { 'i_search_total' } + let(:action) { 'executed' } + let(:label) { 'redis_hll_counters.search.search_total_unique_counts_monthly' } + let(:property) { 'i_search_total' } + let(:context) do + [Gitlab::Tracking::ServicePingContext.new(data_source: :redis_hll, + event: property).to_context] + end + let(:namespace) { create(:group) } let(:feature_flag_name) { :route_hll_to_snowplow_phase2 } end diff --git a/spec/features/admin/admin_hook_logs_spec.rb b/spec/features/admin/admin_hook_logs_spec.rb index 6caf2b24555..a2ee6343886 100644 --- a/spec/features/admin/admin_hook_logs_spec.rb +++ b/spec/features/admin/admin_hook_logs_spec.rb @@ -3,12 +3,11 @@ require 'spec_helper' RSpec.describe 'Admin::HookLogs' do - let(:project) { create(:project) } - let(:system_hook) { create(:system_hook) } - let(:hook_log) { create(:web_hook_log, web_hook: system_hook, internal_error_message: 'some error') } + let_it_be(:system_hook) { create(:system_hook) } + let_it_be(:hook_log) { create(:web_hook_log, web_hook: system_hook, internal_error_message: 'some error') } + let_it_be(:admin) { create(:admin) } before do - admin = create(:admin) sign_in(admin) gitlab_enable_admin_mode_sign_in(admin) end diff --git a/spec/features/admin/admin_hooks_spec.rb b/spec/features/admin/admin_hooks_spec.rb index 901315752d6..dc5b0ae009e 100644 --- a/spec/features/admin/admin_hooks_spec.rb +++ b/spec/features/admin/admin_hooks_spec.rb @@ -5,7 +5,7 @@ require 'spec_helper' RSpec.describe 'Admin::Hooks' do include Spec::Support::Helpers::ModalHelpers - let(:user) { create(:admin) } + let_it_be(:user) { create(:admin) } before do sign_in(user) diff --git a/spec/frontend/vue_shared/components/sidebar/labels_select_widget/labels_select_root_spec.js b/spec/frontend/vue_shared/components/sidebar/labels_select_widget/labels_select_root_spec.js index b58c44645d6..74ddd07d041 100644 --- a/spec/frontend/vue_shared/components/sidebar/labels_select_widget/labels_select_root_spec.js +++ b/spec/frontend/vue_shared/components/sidebar/labels_select_widget/labels_select_root_spec.js @@ -49,7 +49,6 @@ describe('LabelsSelectRoot', () => { issuableType = IssuableType.Issue, queryHandler = successfulQueryHandler, mutationHandler = successfulMutationHandler, - isRealtimeEnabled = false, } = {}) => { const mockApollo = createMockApollo([ [issueLabelsQuery, queryHandler], @@ -74,9 +73,6 @@ describe('LabelsSelectRoot', () => { allowLabelEdit: true, allowLabelCreate: true, labelsManagePath: 'test', - glFeatures: { - realtimeLabels: isRealtimeEnabled, - }, }, }); }; @@ -204,17 +200,10 @@ describe('LabelsSelectRoot', () => { }); }); - it('does not emit `updateSelectedLabels` event when the subscription is triggered and FF is disabled', async () => { + it('emits `updateSelectedLabels` event when the subscription is triggered', async () => { createComponent(); await waitForPromises(); - expect(wrapper.emitted('updateSelectedLabels')).toBeUndefined(); - }); - - it('emits `updateSelectedLabels` event when the subscription is triggered and FF is enabled', async () => { - createComponent({ isRealtimeEnabled: true }); - await waitForPromises(); - expect(wrapper.emitted('updateSelectedLabels')).toEqual([ [ { diff --git a/spec/frontend/vue_shared/issuable/list/components/issuable_item_spec.js b/spec/frontend/vue_shared/issuable/list/components/issuable_item_spec.js index f55d3156581..e1c6020686c 100644 --- a/spec/frontend/vue_shared/issuable/list/components/issuable_item_spec.js +++ b/spec/frontend/vue_shared/issuable/list/components/issuable_item_spec.js @@ -543,24 +543,6 @@ describe('IssuableItem', () => { }); }); - describe('when issuable was created within the past 24 hours', () => { - it('renders issuable card with a recently-created style', () => { - wrapper = createComponent({ - issuable: { ...mockIssuable, createdAt: '2020-12-10T12:34:56' }, - }); - - expect(wrapper.classes()).toContain('today'); - }); - }); - - describe('when issuable was created earlier than the past 24 hours', () => { - it('renders issuable card without a recently-created style', () => { - wrapper = createComponent({ issuable: { ...mockIssuable, createdAt: '2020-12-09' } }); - - expect(wrapper.classes()).not.toContain('today'); - }); - }); - describe('scoped labels', () => { describe.each` description | labelPosition | hasScopedLabelsFeature | scoped diff --git a/spec/frontend/work_items/components/work_item_description_spec.js b/spec/frontend/work_items/components/work_item_description_spec.js index 60889870750..c79b049442d 100644 --- a/spec/frontend/work_items/components/work_item_description_spec.js +++ b/spec/frontend/work_items/components/work_item_description_spec.js @@ -8,6 +8,7 @@ import EditedAt from '~/issues/show/components/edited.vue'; import { updateDraft } from '~/lib/utils/autosave'; import { confirmAction } from '~/lib/utils/confirm_via_gl_modal/confirm_via_gl_modal'; import MarkdownField from '~/vue_shared/components/markdown/field.vue'; +import MarkdownEditor from '~/vue_shared/components/markdown/markdown_editor.vue'; import WorkItemDescription from '~/work_items/components/work_item_description.vue'; import WorkItemDescriptionRendered from '~/work_items/components/work_item_description_rendered.vue'; import { TRACKING_CATEGORY_SHOW } from '~/work_items/constants'; @@ -37,12 +38,19 @@ describe('WorkItemDescription', () => { const subscriptionHandler = jest.fn().mockResolvedValue(workItemDescriptionSubscriptionResponse); const workItemByIidResponseHandler = jest.fn().mockResolvedValue(projectWorkItemResponse); let workItemResponseHandler; + let workItemsMvc2; const findMarkdownField = () => wrapper.findComponent(MarkdownField); + const findMarkdownEditor = () => wrapper.findComponent(MarkdownEditor); const findRenderedDescription = () => wrapper.findComponent(WorkItemDescriptionRendered); const findEditedAt = () => wrapper.findComponent(EditedAt); - const editDescription = (newText) => wrapper.find('textarea').setValue(newText); + const editDescription = (newText) => { + if (workItemsMvc2) { + return findMarkdownEditor().vm.$emit('input', newText); + } + return wrapper.find('textarea').setValue(newText); + }; const clickCancel = () => wrapper.find('[data-testid="cancel"]').vm.$emit('click'); const clickSave = () => wrapper.find('[data-testid="save-description"]').vm.$emit('click', {}); @@ -72,6 +80,11 @@ describe('WorkItemDescription', () => { }, fetchByIid, }, + provide: { + glFeatures: { + workItemsMvc2, + }, + }, stubs: { MarkdownField, }, @@ -90,175 +103,178 @@ describe('WorkItemDescription', () => { wrapper.destroy(); }); - it('has a subscription', async () => { - createComponent(); - - await waitForPromises(); - - expect(subscriptionHandler).toHaveBeenCalledWith({ - issuableId: workItemQueryResponse.data.workItem.id, - }); - }); - - describe('editing description', () => { - it('shows edited by text', async () => { - const lastEditedAt = '2022-09-21T06:18:42Z'; - const lastEditedBy = { - name: 'Administrator', - webPath: '/root', - }; - - await createComponent({ - workItemResponse: workItemResponseFactory({ - lastEditedAt, - lastEditedBy, - }), + describe.each([true, false])( + 'editing description with workItemsMvc2 %workItemsMvc2Enabled', + (workItemsMvc2Enabled) => { + beforeEach(() => { + beforeEach(() => { + workItemsMvc2 = workItemsMvc2Enabled; + }); }); - expect(findEditedAt().props()).toEqual({ - updatedAt: lastEditedAt, - updatedByName: lastEditedBy.name, - updatedByPath: lastEditedBy.webPath, - }); - }); + describe('editing description', () => { + it('shows edited by text', async () => { + const lastEditedAt = '2022-09-21T06:18:42Z'; + const lastEditedBy = { + name: 'Administrator', + webPath: '/root', + }; - it('does not show edited by text', async () => { - await createComponent(); + await createComponent({ + workItemResponse: workItemResponseFactory({ + lastEditedAt, + lastEditedBy, + }), + }); - expect(findEditedAt().exists()).toBe(false); - }); + expect(findEditedAt().props()).toEqual({ + updatedAt: lastEditedAt, + updatedByName: lastEditedBy.name, + updatedByPath: lastEditedBy.webPath, + }); + }); - it('cancels when clicking cancel', async () => { - await createComponent({ - isEditing: true, - }); + it('does not show edited by text', async () => { + await createComponent(); - clickCancel(); + expect(findEditedAt().exists()).toBe(false); + }); - await nextTick(); + it('cancels when clicking cancel', async () => { + await createComponent({ + isEditing: true, + }); - expect(confirmAction).not.toHaveBeenCalled(); - expect(findMarkdownField().exists()).toBe(false); - }); + clickCancel(); - it('prompts for confirmation when clicking cancel after changes', async () => { - await createComponent({ - isEditing: true, - }); + await nextTick(); - editDescription('updated desc'); + expect(confirmAction).not.toHaveBeenCalled(); + expect(findMarkdownField().exists()).toBe(false); + }); - clickCancel(); + it('prompts for confirmation when clicking cancel after changes', async () => { + await createComponent({ + isEditing: true, + }); - await nextTick(); + editDescription('updated desc'); - expect(confirmAction).toHaveBeenCalled(); - }); + clickCancel(); - it('calls update widgets mutation', async () => { - await createComponent({ - isEditing: true, - }); + await nextTick(); - editDescription('updated desc'); + expect(confirmAction).toHaveBeenCalled(); + }); - clickSave(); + it('calls update widgets mutation', async () => { + const updatedDesc = 'updated desc'; - await waitForPromises(); + await createComponent({ + isEditing: true, + }); - expect(mutationSuccessHandler).toHaveBeenCalledWith({ - input: { - id: workItemId, - descriptionWidget: { - description: 'updated desc', - }, - }, - }); - }); + editDescription(updatedDesc); - it('tracks editing description', async () => { - await createComponent({ - isEditing: true, - markdownPreviewPath: '/preview', - }); - const trackingSpy = mockTracking(undefined, wrapper.element, jest.spyOn); + clickSave(); - clickSave(); + await waitForPromises(); - await waitForPromises(); - - expect(trackingSpy).toHaveBeenCalledWith(TRACKING_CATEGORY_SHOW, 'updated_description', { - category: TRACKING_CATEGORY_SHOW, - label: 'item_description', - property: 'type_Task', - }); - }); - - it('emits error when mutation returns error', async () => { - const error = 'eror'; - - await createComponent({ - isEditing: true, - mutationHandler: jest.fn().mockResolvedValue({ - data: { - workItemUpdate: { - workItem: {}, - errors: [error], + expect(mutationSuccessHandler).toHaveBeenCalledWith({ + input: { + id: workItemId, + descriptionWidget: { + description: updatedDesc, + }, }, - }, - }), + }); + }); + + it('tracks editing description', async () => { + await createComponent({ + isEditing: true, + markdownPreviewPath: '/preview', + }); + const trackingSpy = mockTracking(undefined, wrapper.element, jest.spyOn); + + clickSave(); + + await waitForPromises(); + + expect(trackingSpy).toHaveBeenCalledWith(TRACKING_CATEGORY_SHOW, 'updated_description', { + category: TRACKING_CATEGORY_SHOW, + label: 'item_description', + property: 'type_Task', + }); + }); + + it('emits error when mutation returns error', async () => { + const error = 'eror'; + + await createComponent({ + isEditing: true, + mutationHandler: jest.fn().mockResolvedValue({ + data: { + workItemUpdate: { + workItem: {}, + errors: [error], + }, + }, + }), + }); + + editDescription('updated desc'); + + clickSave(); + + await waitForPromises(); + + expect(wrapper.emitted('error')).toEqual([[error]]); + }); + + it('emits error when mutation fails', async () => { + const error = 'eror'; + + await createComponent({ + isEditing: true, + mutationHandler: jest.fn().mockRejectedValue(new Error(error)), + }); + + editDescription('updated desc'); + + clickSave(); + + await waitForPromises(); + + expect(wrapper.emitted('error')).toEqual([[error]]); + }); + + it('autosaves description', async () => { + await createComponent({ + isEditing: true, + }); + + editDescription('updated desc'); + + expect(updateDraft).toHaveBeenCalled(); + }); }); - editDescription('updated desc'); + it('calls the global ID work item query when `fetchByIid` prop is false', async () => { + createComponent({ fetchByIid: false }); + await waitForPromises(); - clickSave(); - - await waitForPromises(); - - expect(wrapper.emitted('error')).toEqual([[error]]); - }); - - it('emits error when mutation fails', async () => { - const error = 'eror'; - - await createComponent({ - isEditing: true, - mutationHandler: jest.fn().mockRejectedValue(new Error(error)), + expect(workItemResponseHandler).toHaveBeenCalled(); + expect(workItemByIidResponseHandler).not.toHaveBeenCalled(); }); - editDescription('updated desc'); + it('calls the IID work item query when when `fetchByIid` prop is true', async () => { + createComponent({ fetchByIid: true }); + await waitForPromises(); - clickSave(); - - await waitForPromises(); - - expect(wrapper.emitted('error')).toEqual([[error]]); - }); - - it('autosaves description', async () => { - await createComponent({ - isEditing: true, + expect(workItemResponseHandler).not.toHaveBeenCalled(); + expect(workItemByIidResponseHandler).toHaveBeenCalled(); }); - - editDescription('updated desc'); - - expect(updateDraft).toHaveBeenCalled(); - }); - }); - - it('calls the global ID work item query when `fetchByIid` prop is false', async () => { - createComponent({ fetchByIid: false }); - await waitForPromises(); - - expect(workItemResponseHandler).toHaveBeenCalled(); - expect(workItemByIidResponseHandler).not.toHaveBeenCalled(); - }); - - it('calls the IID work item query when when `fetchByIid` prop is true', async () => { - createComponent({ fetchByIid: true }); - await waitForPromises(); - - expect(workItemResponseHandler).not.toHaveBeenCalled(); - expect(workItemByIidResponseHandler).toHaveBeenCalled(); - }); + }, + ); }); diff --git a/spec/lib/gitlab/ci/reports/security/flag_spec.rb b/spec/lib/gitlab/ci/reports/security/flag_spec.rb index 6ee074f7aeb..0ef8f6c75a0 100644 --- a/spec/lib/gitlab/ci/reports/security/flag_spec.rb +++ b/spec/lib/gitlab/ci/reports/security/flag_spec.rb @@ -29,5 +29,11 @@ RSpec.describe Gitlab::Ci::Reports::Security::Flag do ) end end + + describe '#false_positive?' do + subject { security_flag.false_positive? } + + it { is_expected.to be_truthy } + end end end diff --git a/spec/lib/gitlab/config_checker/external_database_checker_spec.rb b/spec/lib/gitlab/config_checker/external_database_checker_spec.rb index 9af6aed2b02..963c9fe1576 100644 --- a/spec/lib/gitlab/config_checker/external_database_checker_spec.rb +++ b/spec/lib/gitlab/config_checker/external_database_checker_spec.rb @@ -36,7 +36,7 @@ RSpec.describe Gitlab::ConfigChecker::ExternalDatabaseChecker do end it 'reports deprecated database notice' do - is_expected.to contain_exactly(notice_deprecated_database(old_database_version)) + is_expected.to contain_exactly(notice_deprecated_database('main', old_database_version)) end end end @@ -59,13 +59,13 @@ RSpec.describe Gitlab::ConfigChecker::ExternalDatabaseChecker do it 'reports deprecated database notice if the main database is using an old version' do allow(Gitlab::Database::Reflection).to receive(:new).with(ActiveRecord::Base).and_return(old_database) allow(Gitlab::Database::Reflection).to receive(:new).with(Ci::ApplicationRecord).and_return(new_database) - is_expected.to contain_exactly(notice_deprecated_database(old_database_version)) + is_expected.to contain_exactly(notice_deprecated_database('main', old_database_version)) end it 'reports deprecated database notice if the ci database is using an old version' do allow(Gitlab::Database::Reflection).to receive(:new).with(ActiveRecord::Base).and_return(new_database) allow(Gitlab::Database::Reflection).to receive(:new).with(Ci::ApplicationRecord).and_return(old_database) - is_expected.to contain_exactly(notice_deprecated_database(old_database_version)) + is_expected.to contain_exactly(notice_deprecated_database('ci', old_database_version)) end end @@ -77,22 +77,23 @@ RSpec.describe Gitlab::ConfigChecker::ExternalDatabaseChecker do it 'reports deprecated database notice' do is_expected.to match_array [ - notice_deprecated_database(old_database_version), - notice_deprecated_database(old_database_version) + notice_deprecated_database('main', old_database_version), + notice_deprecated_database('ci', old_database_version) ] end end end end - def notice_deprecated_database(database_version) + def notice_deprecated_database(database_name, database_version) { type: 'warning', - message: _('You are using PostgreSQL %{pg_version_current}, but PostgreSQL ' \ - '%{pg_version_minimum} is required for this version of GitLab. ' \ - 'Please upgrade your environment to a supported PostgreSQL version, ' \ - 'see %{pg_requirements_url} for details.') % \ + message: _('Database \'%{database_name}\' is using PostgreSQL %{pg_version_current}, ' \ + 'but PostgreSQL %{pg_version_minimum} is required for this version of GitLab. ' \ + 'Please upgrade your environment to a supported PostgreSQL version, ' \ + 'see %{pg_requirements_url} for details.') % \ { + database_name: database_name, pg_version_current: database_version, pg_version_minimum: Gitlab::Database::MINIMUM_POSTGRES_VERSION, pg_requirements_url: Gitlab::ConfigChecker::ExternalDatabaseChecker::PG_REQUIREMENTS_LINK diff --git a/spec/lib/gitlab/usage_data_counters/kubernetes_agent_counter_spec.rb b/spec/lib/gitlab/usage_data_counters/kubernetes_agent_counter_spec.rb index e7edb8b9cf1..ced9ec7f221 100644 --- a/spec/lib/gitlab/usage_data_counters/kubernetes_agent_counter_spec.rb +++ b/spec/lib/gitlab/usage_data_counters/kubernetes_agent_counter_spec.rb @@ -26,6 +26,12 @@ RSpec.describe Gitlab::UsageDataCounters::KubernetesAgentCounter do expect(described_class.totals).to eq(kubernetes_agent_gitops_sync: 3, kubernetes_agent_k8s_api_proxy_request: 6) end + context 'with empty events' do + let(:events) { nil } + + it { expect { subject }.not_to change(described_class, :totals) } + end + context 'event is unknown' do let(:events) do { diff --git a/spec/lib/gitlab/utils/strong_memoize_spec.rb b/spec/lib/gitlab/utils/strong_memoize_spec.rb index cb03797b3d9..7ed602e3b70 100644 --- a/spec/lib/gitlab/utils/strong_memoize_spec.rb +++ b/spec/lib/gitlab/utils/strong_memoize_spec.rb @@ -46,6 +46,13 @@ RSpec.describe Gitlab::Utils::StrongMemoize do true end + def method_name_with_args(*args) + strong_memoize_with(:method_name_with_args, args) do + trace << [value, args] + value + end + end + def trace @trace ||= [] end @@ -141,6 +148,36 @@ RSpec.describe Gitlab::Utils::StrongMemoize do end end + describe '#strong_memoize_with' do + [nil, false, true, 'value', 0, [0]].each do |value| + context "with value #{value}" do + let(:value) { value } + + it 'only calls the block once' do + value0 = object.method_name_with_args(1) + value1 = object.method_name_with_args(1) + value2 = object.method_name_with_args([2, 3]) + value3 = object.method_name_with_args([2, 3]) + + expect(value0).to eq(value) + expect(value1).to eq(value) + expect(value2).to eq(value) + expect(value3).to eq(value) + + expect(object.trace).to contain_exactly([value, [1]], [value, [[2, 3]]]) + end + + it 'returns and defines the instance variable for the exact value' do + returned_value = object.method_name_with_args(1, 2, 3) + memoized_value = object.instance_variable_get(:@method_name_with_args) + + expect(returned_value).to eql(value) + expect(memoized_value).to eql({ [[1, 2, 3]] => value }) + end + end + end + end + describe '#strong_memoized?' do let(:value) { :anything } diff --git a/spec/models/concerns/issuable_spec.rb b/spec/models/concerns/issuable_spec.rb index 43ec0559eb3..e553e34ab51 100644 --- a/spec/models/concerns/issuable_spec.rb +++ b/spec/models/concerns/issuable_spec.rb @@ -337,31 +337,6 @@ RSpec.describe Issuable do it { expect(MergeRequest.to_ability_name).to eq("merge_request") } end - describe "#today?" do - it "returns true when created today" do - # Avoid timezone differences and just return exactly what we want - allow(Date).to receive(:today).and_return(issue.created_at.to_date) - expect(issue.today?).to be_truthy - end - - it "returns false when not created today" do - allow(Date).to receive(:today).and_return(Date.yesterday) - expect(issue.today?).to be_falsey - end - end - - describe "#new?" do - it "returns false when created 30 hours ago" do - allow(issue).to receive(:created_at).and_return(Time.current - 30.hours) - expect(issue.new?).to be_falsey - end - - it "returns true when created 20 hours ago" do - allow(issue).to receive(:created_at).and_return(Time.current - 20.hours) - expect(issue.new?).to be_truthy - end - end - describe "#sort_by_attribute" do let(:project) { create(:project) } diff --git a/spec/models/hooks/active_hook_filter_spec.rb b/spec/models/hooks/active_hook_filter_spec.rb index 38e4d9d3a72..47c0fbdb106 100644 --- a/spec/models/hooks/active_hook_filter_spec.rb +++ b/spec/models/hooks/active_hook_filter_spec.rb @@ -9,8 +9,8 @@ RSpec.describe ActiveHookFilter do using RSpec::Parameterized::TableSyntax context 'for various types of branch_filter' do - let_it_be_with_reload(:hook) do - create(:project_hook, push_events: true, issues_events: true) + let(:hook) do + build(:project_hook, push_events: true, issues_events: true) end where(:branch_filter_strategy, :branch_filter, :ref, :expected_matches?) do @@ -53,8 +53,8 @@ RSpec.describe ActiveHookFilter do end context 'when the branch filter is a invalid regex' do - let_it_be(:hook) do - create( + let(:hook) do + build( :project_hook, push_events: true, push_events_branch_filter: 'master', @@ -70,8 +70,8 @@ RSpec.describe ActiveHookFilter do end context 'when the branch filter is not properly set to nil' do - let_it_be(:hook) do - create( + let(:hook) do + build( :project_hook, push_events: true, branch_filter_strategy: 'all_branches' @@ -91,8 +91,8 @@ RSpec.describe ActiveHookFilter do stub_feature_flags(enhanced_webhook_support_regex: false) end - let_it_be(:hook) do - create( + let(:hook) do + build( :project_hook, push_events: true, push_events_branch_filter: '(master)', diff --git a/spec/models/hooks/project_hook_spec.rb b/spec/models/hooks/project_hook_spec.rb index 923a6f92424..3d8c377ab21 100644 --- a/spec/models/hooks/project_hook_spec.rb +++ b/spec/models/hooks/project_hook_spec.rb @@ -12,14 +12,14 @@ RSpec.describe ProjectHook do end it_behaves_like 'includes Limitable concern' do - subject { build(:project_hook, project: create(:project)) } + subject { build(:project_hook) } end describe '.for_projects' do it 'finds related project hooks' do - hook_a = create(:project_hook) - hook_b = create(:project_hook) - hook_c = create(:project_hook) + hook_a = create(:project_hook, project: build(:project)) + hook_b = create(:project_hook, project: build(:project)) + hook_c = create(:project_hook, project: build(:project)) expect(described_class.for_projects([hook_a.project, hook_b.project])) .to contain_exactly(hook_a, hook_b) @@ -30,16 +30,18 @@ RSpec.describe ProjectHook do describe '.push_hooks' do it 'returns hooks for push events only' do - hook = create(:project_hook, push_events: true) - create(:project_hook, push_events: false) + project = build(:project) + hook = create(:project_hook, project: project, push_events: true) + create(:project_hook, project: project, push_events: false) expect(described_class.push_hooks).to eq([hook]) end end describe '.tag_push_hooks' do it 'returns hooks for tag push events only' do - hook = create(:project_hook, tag_push_events: true) - create(:project_hook, tag_push_events: false) + project = build(:project) + hook = create(:project_hook, project: project, tag_push_events: true) + create(:project_hook, project: project, tag_push_events: false) expect(described_class.tag_push_hooks).to eq([hook]) end end @@ -65,7 +67,7 @@ RSpec.describe ProjectHook do end describe '#update_last_failure', :clean_gitlab_redis_shared_state do - let_it_be(:hook) { create(:project_hook) } + let(:hook) { build(:project_hook) } it 'is a method of this class' do expect { hook.update_last_failure }.not_to raise_error diff --git a/spec/models/hooks/system_hook_spec.rb b/spec/models/hooks/system_hook_spec.rb index 6520cbb6219..ba94730b1dd 100644 --- a/spec/models/hooks/system_hook_spec.rb +++ b/spec/models/hooks/system_hook_spec.rb @@ -32,10 +32,10 @@ RSpec.describe SystemHook do end describe "execute", :sidekiq_might_not_need_inline do - let(:system_hook) { create(:system_hook) } - let(:user) { create(:user) } - let(:project) { create(:project, namespace: user.namespace) } - let(:group) { create(:group) } + let_it_be(:system_hook) { create(:system_hook) } + let_it_be(:user) { create(:user) } + let(:project) { build(:project, namespace: user.namespace) } + let(:group) { build(:group) } let(:params) do { name: 'John Doe', username: 'jduser', email: 'jg@example.com', password: User.random_password } end @@ -145,6 +145,7 @@ RSpec.describe SystemHook do end it 'group member update hook' do + group = create(:group) group.add_guest(user) group.add_maintainer(user) diff --git a/spec/models/hooks/web_hook_log_spec.rb b/spec/models/hooks/web_hook_log_spec.rb index 3441dfda7d6..fafca144cae 100644 --- a/spec/models/hooks/web_hook_log_spec.rb +++ b/spec/models/hooks/web_hook_log_spec.rb @@ -12,7 +12,7 @@ RSpec.describe WebHookLog do it { is_expected.to validate_presence_of(:web_hook) } describe '.recent' do - let(:hook) { create(:project_hook) } + let(:hook) { build(:project_hook) } it 'does not return web hook logs that are too old' do create(:web_hook_log, web_hook: hook, created_at: 10.days.ago) @@ -30,8 +30,10 @@ RSpec.describe WebHookLog do end describe '#save' do + let(:hook) { build(:project_hook) } + context 'with basic auth credentials' do - let(:web_hook_log) { build(:web_hook_log, url: 'http://test:123@example.com') } + let(:web_hook_log) { build(:web_hook_log, web_hook: hook, url: 'http://test:123@example.com') } subject { web_hook_log.save! } @@ -45,9 +47,9 @@ RSpec.describe WebHookLog do end context "with users' emails" do - let(:author) { create(:user) } - let(:user) { create(:user) } - let(:web_hook_log) { create(:web_hook_log, request_data: data) } + let(:author) { build(:user) } + let(:user) { build(:user) } + let(:web_hook_log) { create(:web_hook_log, web_hook: hook, request_data: data) } let(:data) do { user: { @@ -93,11 +95,12 @@ RSpec.describe WebHookLog do end describe '.delete_batch_for' do - let(:hook) { create(:project_hook) } + let_it_be(:hook) { build(:project_hook) } + let_it_be(:hook2) { build(:project_hook) } - before do + before_all do create_list(:web_hook_log, 3, web_hook: hook) - create_list(:web_hook_log, 3) + create_list(:web_hook_log, 3, web_hook: hook2) end subject { described_class.delete_batch_for(hook, batch_size: batch_size) } diff --git a/spec/models/hooks/web_hook_spec.rb b/spec/models/hooks/web_hook_spec.rb index 8c802628559..a55ce9c411c 100644 --- a/spec/models/hooks/web_hook_spec.rb +++ b/spec/models/hooks/web_hook_spec.rb @@ -198,7 +198,7 @@ RSpec.describe WebHook do describe '.web_hooks_disable_failed?' do it 'returns true when feature is enabled for parent' do - second_hook = build(:project_hook, project: create(:project)) + second_hook = build(:project_hook) stub_feature_flags(web_hooks_disable_failed: [false, second_hook.project]) expect(described_class.web_hooks_disable_failed?(hook)).to eq(false) diff --git a/spec/requests/api/internal/kubernetes_spec.rb b/spec/requests/api/internal/kubernetes_spec.rb index 67d8a18dfd8..3c6604cf409 100644 --- a/spec/requests/api/internal/kubernetes_spec.rb +++ b/spec/requests/api/internal/kubernetes_spec.rb @@ -69,48 +69,6 @@ RSpec.describe API::Internal::Kubernetes do context 'is authenticated for an agent' do let!(:agent_token) { create(:cluster_agent_token) } - # Todo: Remove gitops_sync_count and k8s_api_proxy_request_count in the next milestone - # https://gitlab.com/gitlab-org/gitlab/-/issues/369489 - # We're only keeping it for backwards compatibility until KAS is released - # using `counts:` instead - context 'deprecated events' do - it 'returns no_content for valid events' do - send_request(params: { gitops_sync_count: 10, k8s_api_proxy_request_count: 5 }) - - expect(response).to have_gitlab_http_status(:no_content) - end - - it 'returns no_content for counts of zero' do - send_request(params: { gitops_sync_count: 0, k8s_api_proxy_request_count: 0 }) - - expect(response).to have_gitlab_http_status(:no_content) - end - - it 'returns 400 for non number' do - send_request(params: { gitops_sync_count: 'string', k8s_api_proxy_request_count: 1 }) - - expect(response).to have_gitlab_http_status(:bad_request) - end - - it 'returns 400 for negative number' do - send_request(params: { gitops_sync_count: -1, k8s_api_proxy_request_count: 1 }) - - expect(response).to have_gitlab_http_status(:bad_request) - end - - it 'tracks events' do - counters = { gitops_sync_count: 10, k8s_api_proxy_request_count: 5 } - expected_counters = { - kubernetes_agent_gitops_sync: counters[:gitops_sync_count], - kubernetes_agent_k8s_api_proxy_request: counters[:k8s_api_proxy_request_count] - } - - send_request(params: counters) - - expect(Gitlab::UsageDataCounters::KubernetesAgentCounter.totals).to eq(expected_counters) - end - end - it 'returns no_content for valid events' do counters = { gitops_sync: 10, k8s_api_proxy_request: 5 } unique_counters = { agent_users_using_ci_tunnel: [10] } diff --git a/spec/support/shared_examples/models/concerns/limitable_shared_examples.rb b/spec/support/shared_examples/models/concerns/limitable_shared_examples.rb index 3d393e6dcb5..c6d6e00c781 100644 --- a/spec/support/shared_examples/models/concerns/limitable_shared_examples.rb +++ b/spec/support/shared_examples/models/concerns/limitable_shared_examples.rb @@ -2,7 +2,7 @@ RSpec.shared_examples 'includes Limitable concern' do describe '#exceeds_limits?' do - let(:plan_limits) { create(:plan_limits, :default_plan) } + let_it_be_with_reload(:plan_limits) { create(:plan_limits, :default_plan) } context 'without plan limits configured' do it { expect(subject.exceeds_limits?).to eq false } @@ -26,7 +26,7 @@ RSpec.shared_examples 'includes Limitable concern' do end describe 'validations' do - let(:plan_limits) { create(:plan_limits, :default_plan) } + let_it_be_with_reload(:plan_limits) { create(:plan_limits, :default_plan) } it { is_expected.to be_a(Limitable) }