diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2022-11-16 18:10:52 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2022-11-16 18:10:52 +0300 |
commit | 0552020767452da44de2bf5424096f2cb2ea6bf5 (patch) | |
tree | 9579d9f0ad3c730c33883130ec23420e80d1c5dc | |
parent | e3748b81ca29b24197276767e245158d8f84fda3 (diff) |
Add latest changes from gitlab-org/gitlab@master
112 files changed, 1541 insertions, 916 deletions
diff --git a/.eslintrc.yml b/.eslintrc.yml index 659ed2a0010..f814bdc6434 100644 --- a/.eslintrc.yml +++ b/.eslintrc.yml @@ -163,6 +163,7 @@ overrides: - '*.stories.js' rules: filenames/match-regex: off + '@gitlab/require-i18n-strings': off - files: - '*.graphql' plugins: diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 936b9410a60..edbbe90a774 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -52,7 +52,7 @@ workflow: CREATE_INCIDENT_FOR_PIPELINE_FAILURE: "true" NOTIFY_PIPELINE_FAILURE_CHANNEL: "master-broken" # Run pipelines for ruby3 branch - - if: '$CI_COMMIT_BRANCH == "ruby3"' + - if: '$CI_COMMIT_BRANCH == "ruby3" && $CI_PIPELINE_SOURCE == "schedule"' variables: RUBY_VERSION: "3.0" NOTIFY_PIPELINE_FAILURE_CHANNEL: "f_ruby3" diff --git a/.gitlab/ci/review-apps/main.gitlab-ci.yml b/.gitlab/ci/review-apps/main.gitlab-ci.yml index 0c3fd847c99..7ecc8db3b1b 100644 --- a/.gitlab/ci/review-apps/main.gitlab-ci.yml +++ b/.gitlab/ci/review-apps/main.gitlab-ci.yml @@ -118,7 +118,7 @@ review-deploy: - run_timed_command "download_chart" - run_timed_command "deploy" || (display_deployment_debug && exit 1) - run_timed_command "verify_deploy"|| (display_deployment_debug && exit 1) - - run_timed_command "disable_sign_ups" || (delete_release && exit 1) + - run_timed_command "disable_sign_ups" after_script: # Run seed-dast-test-data.sh only when DAST_RUN is set to true. This is to pupulate review app with data for DAST scan. # Set DAST_RUN to true when jobs are manually scheduled. @@ -166,7 +166,7 @@ review-delete-deployment: - .review:rules:review-delete-deployment stage: prepare script: - - delete_release + - delete_helm_release review-stop: extends: @@ -176,4 +176,4 @@ review-stop: stage: deploy needs: [] script: - - delete_namespace + - delete_helm_release diff --git a/.rubocop_todo/layout/line_length.yml b/.rubocop_todo/layout/line_length.yml index 75e04ddcbd4..b5fc199c0ca 100644 --- a/.rubocop_todo/layout/line_length.yml +++ b/.rubocop_todo/layout/line_length.yml @@ -2983,7 +2983,6 @@ Layout/LineLength: - 'lib/api/members.rb' - 'lib/api/merge_request_diffs.rb' - 'lib/api/merge_requests.rb' - - 'lib/api/metrics/dashboard/annotations.rb' - 'lib/api/metrics/user_starred_dashboards.rb' - 'lib/api/milestone_responses.rb' - 'lib/api/namespaces.rb' diff --git a/.rubocop_todo/style/next.yml b/.rubocop_todo/style/next.yml index 9570bd7b036..295aa2f6878 100644 --- a/.rubocop_todo/style/next.yml +++ b/.rubocop_todo/style/next.yml @@ -2,32 +2,4 @@ # Cop supports --auto-correct. Style/Next: Exclude: - - 'app/models/concerns/integrations/slack_mattermost_notifier.rb' - - 'app/validators/nested_attributes_duplicates_validator.rb' - - 'ee/app/services/security/ingestion/tasks/update_vulnerability_uuids.rb' - - 'ee/db/fixtures/development/20_vulnerabilities.rb' - - 'ee/lib/ee/audit/protected_branches_changes_auditor.rb' - - 'ee/lib/gitlab/elastic/search_results.rb' - - 'ee/lib/system_check/geo/authorized_keys_check.rb' - - 'lib/backup/manager.rb' - - 'lib/banzai/filter/external_link_filter.rb' - - 'lib/banzai/filter/footnote_filter.rb' - - 'lib/banzai/filter/kroki_filter.rb' - - 'lib/banzai/filter/math_filter.rb' - - 'lib/banzai/filter/plantuml_filter.rb' - - 'lib/banzai/filter/table_of_contents_filter.rb' - - 'lib/gitlab/background_migration/encrypt_static_object_token.rb' - - 'lib/gitlab/database.rb' - 'lib/gitlab/fogbugz_import/importer.rb' - - 'lib/gitlab/gitaly_client/repository_service.rb' - - 'lib/gitlab/import_export/attributes_permitter.rb' - - 'lib/gitlab/import_export/base/relation_object_saver.rb' - - 'lib/gitlab/metrics/samplers/base_sampler.rb' - - 'lib/gitlab/pagination/keyset/in_operator_optimization/strategies/record_loader_strategy.rb' - - 'lib/gitlab/reference_extractor.rb' - - 'lib/tasks/gitlab/assets.rake' - - 'lib/tasks/gitlab/db/validate_config.rake' - - 'scripts/perf/query_limiting_report.rb' - - 'scripts/qa/quarantine-types-check' - - 'spec/lib/gitlab/import_export/import_test_coverage_spec.rb' - - 'spec/presenters/packages/npm/package_presenter_spec.rb' diff --git a/app/assets/javascripts/ci_variable_list/components/ci_admin_variables.vue b/app/assets/javascripts/ci_variable_list/components/ci_admin_variables.vue index afdac28cbd6..719696f682e 100644 --- a/app/assets/javascripts/ci_variable_list/components/ci_admin_variables.vue +++ b/app/assets/javascripts/ci_variable_list/components/ci_admin_variables.vue @@ -28,6 +28,7 @@ export default { <ci-variable-shared :are-scoped-variables-available="false" component-name="InstanceVariables" + :hide-environment-scope="true" :mutation-data="$options.mutationData" :refetch-after-mutation="true" :query-data="$options.queryData" diff --git a/app/assets/javascripts/ci_variable_list/components/ci_variable_modal.vue b/app/assets/javascripts/ci_variable_list/components/ci_variable_modal.vue index 346a038000e..94f8cb9e906 100644 --- a/app/assets/javascripts/ci_variable_list/components/ci_variable_modal.vue +++ b/app/assets/javascripts/ci_variable_list/components/ci_variable_modal.vue @@ -86,6 +86,11 @@ export default { required: false, default: () => [], }, + hideEnvironmentScope: { + type: Boolean, + required: false, + default: false, + }, mode: { type: String, required: true, @@ -310,33 +315,35 @@ export default { /> </gl-form-group> - <gl-form-group - label-for="ci-variable-env" - class="gl-w-half" - data-testid="environment-scope" - > - <template #label> - {{ __('Environment scope') }} - <gl-link - :title="$options.environmentScopeLinkTitle" - :href="environmentScopeLink" - target="_blank" - data-testid="environment-scope-link" - > - <gl-icon name="question" :size="12" /> - </gl-link> - </template> - <ci-environments-dropdown - v-if="areScopedVariablesAvailable" - class="gl-w-full" - :selected-environment-scope="variable.environmentScope" - :environments="joinedEnvironments" - @select-environment="setEnvironmentScope" - @create-environment-scope="createEnvironmentScope" - /> + <template v-if="!hideEnvironmentScope"> + <gl-form-group + label-for="ci-variable-env" + class="gl-w-half" + data-testid="environment-scope" + > + <template #label> + {{ __('Environment scope') }} + <gl-link + :title="$options.environmentScopeLinkTitle" + :href="environmentScopeLink" + target="_blank" + data-testid="environment-scope-link" + > + <gl-icon name="question" :size="12" /> + </gl-link> + </template> + <ci-environments-dropdown + v-if="areScopedVariablesAvailable" + class="gl-w-full" + :selected-environment-scope="variable.environmentScope" + :environments="joinedEnvironments" + @select-environment="setEnvironmentScope" + @create-environment-scope="createEnvironmentScope" + /> - <gl-form-input v-else :value="$options.defaultScope" class="gl-w-full" readonly /> - </gl-form-group> + <gl-form-input v-else :value="$options.defaultScope" class="gl-w-full" readonly /> + </gl-form-group> + </template> </div> <gl-form-group :label="__('Flags')" label-for="ci-variable-flags"> diff --git a/app/assets/javascripts/ci_variable_list/components/ci_variable_settings.vue b/app/assets/javascripts/ci_variable_list/components/ci_variable_settings.vue index 81e3a983ea3..94fd6c3892c 100644 --- a/app/assets/javascripts/ci_variable_list/components/ci_variable_settings.vue +++ b/app/assets/javascripts/ci_variable_list/components/ci_variable_settings.vue @@ -19,6 +19,11 @@ export default { required: false, default: () => [], }, + hideEnvironmentScope: { + type: Boolean, + required: false, + default: false, + }, isLoading: { type: Boolean, required: false, @@ -78,6 +83,7 @@ export default { v-if="showModal" :are-scoped-variables-available="areScopedVariablesAvailable" :environments="environments" + :hide-environment-scope="hideEnvironmentScope" :variables="variables" :mode="mode" :selected-variable="selectedVariable" diff --git a/app/assets/javascripts/ci_variable_list/components/ci_variable_shared.vue b/app/assets/javascripts/ci_variable_list/components/ci_variable_shared.vue index 48081fe28f1..7ee250cea98 100644 --- a/app/assets/javascripts/ci_variable_list/components/ci_variable_shared.vue +++ b/app/assets/javascripts/ci_variable_list/components/ci_variable_shared.vue @@ -31,6 +31,11 @@ export default { type: String, default: null, }, + hideEnvironmentScope: { + type: Boolean, + required: false, + default: false, + }, id: { required: false, type: String, @@ -216,6 +221,7 @@ export default { <template> <ci-variable-settings :are-scoped-variables-available="areScopedVariablesAvailable" + :hide-environment-scope="hideEnvironmentScope" :is-loading="isLoading" :variables="ciVariables" :environments="environments" diff --git a/app/assets/javascripts/content_editor/content_editor.stories.js b/app/assets/javascripts/content_editor/content_editor.stories.js index 2d4226ccd33..9e1a4bfe361 100644 --- a/app/assets/javascripts/content_editor/content_editor.stories.js +++ b/app/assets/javascripts/content_editor/content_editor.stories.js @@ -11,7 +11,6 @@ const Template = (_, { argTypes }) => ({ template: '<content-editor v-bind="$props" @initialized="loadContent" />', methods: { loadContent(contentEditor) { - // eslint-disable-next-line @gitlab/require-i18n-strings contentEditor.setSerializedContent('Hello content editor'); }, }, diff --git a/app/assets/javascripts/vue_shared/components/code_block.stories.js b/app/assets/javascripts/vue_shared/components/code_block.stories.js index e02a346c1de..994913dc1a8 100644 --- a/app/assets/javascripts/vue_shared/components/code_block.stories.js +++ b/app/assets/javascripts/vue_shared/components/code_block.stories.js @@ -13,6 +13,5 @@ const Template = (args, { argTypes }) => ({ export const Default = Template.bind({}); Default.args = { - // eslint-disable-next-line @gitlab/require-i18n-strings code: `git commit -a "Message"\ngit push`, }; diff --git a/app/assets/javascripts/vue_shared/components/confirm_danger/confirm_danger_modal.stories.js b/app/assets/javascripts/vue_shared/components/confirm_danger/confirm_danger_modal.stories.js index 7ecc309db52..b56434f746e 100644 --- a/app/assets/javascripts/vue_shared/components/confirm_danger/confirm_danger_modal.stories.js +++ b/app/assets/javascripts/vue_shared/components/confirm_danger/confirm_danger_modal.stories.js @@ -1,4 +1,3 @@ -/* eslint-disable @gitlab/require-i18n-strings */ import ConfirmDanger from './confirm_danger.vue'; export default { diff --git a/app/assets/javascripts/vue_shared/components/dropdown/dropdown_widget/dropdown_widget.stories.js b/app/assets/javascripts/vue_shared/components/dropdown/dropdown_widget/dropdown_widget.stories.js index 8256d953466..a48b8bcfa8e 100644 --- a/app/assets/javascripts/vue_shared/components/dropdown/dropdown_widget/dropdown_widget.stories.js +++ b/app/assets/javascripts/vue_shared/components/dropdown/dropdown_widget/dropdown_widget.stories.js @@ -1,5 +1,3 @@ -/* eslint-disable @gitlab/require-i18n-strings */ - import { __ } from '~/locale'; import DropdownWidget from './dropdown_widget.vue'; diff --git a/app/assets/javascripts/vue_shared/components/pagination_bar/pagination_bar.stories.js b/app/assets/javascripts/vue_shared/components/pagination_bar/pagination_bar.stories.js index f16afc77164..fd9d69bae22 100644 --- a/app/assets/javascripts/vue_shared/components/pagination_bar/pagination_bar.stories.js +++ b/app/assets/javascripts/vue_shared/components/pagination_bar/pagination_bar.stories.js @@ -1,4 +1,3 @@ -/* eslint-disable @gitlab/require-i18n-strings */ import PaginationBar from './pagination_bar.vue'; export default { diff --git a/app/assets/javascripts/vue_shared/components/sidebar/todo_toggle/todo_button.stories.js b/app/assets/javascripts/vue_shared/components/sidebar/todo_toggle/todo_button.stories.js index 8a2bab4cb9a..465ee9aa0d4 100644 --- a/app/assets/javascripts/vue_shared/components/sidebar/todo_toggle/todo_button.stories.js +++ b/app/assets/javascripts/vue_shared/components/sidebar/todo_toggle/todo_button.stories.js @@ -1,5 +1,3 @@ -/* eslint-disable @gitlab/require-i18n-strings */ - import TodoButton from './todo_button.vue'; export default { diff --git a/app/assets/javascripts/vue_shared/components/tooltip_on_truncate/tooltip_on_truncate.stories.js b/app/assets/javascripts/vue_shared/components/tooltip_on_truncate/tooltip_on_truncate.stories.js index e621442e601..84615386fe2 100644 --- a/app/assets/javascripts/vue_shared/components/tooltip_on_truncate/tooltip_on_truncate.stories.js +++ b/app/assets/javascripts/vue_shared/components/tooltip_on_truncate/tooltip_on_truncate.stories.js @@ -1,4 +1,3 @@ -/* eslint-disable @gitlab/require-i18n-strings */ import TooltipOnTruncate from './tooltip_on_truncate.vue'; const defaultWidth = '250px'; diff --git a/app/assets/javascripts/vue_shared/components/user_deletion_obstacles/user_deletion_obstacles_list.stories.js b/app/assets/javascripts/vue_shared/components/user_deletion_obstacles/user_deletion_obstacles_list.stories.js index 1f0f4cde234..0815fdd9aac 100644 --- a/app/assets/javascripts/vue_shared/components/user_deletion_obstacles/user_deletion_obstacles_list.stories.js +++ b/app/assets/javascripts/vue_shared/components/user_deletion_obstacles/user_deletion_obstacles_list.stories.js @@ -1,5 +1,3 @@ -/* eslint-disable @gitlab/require-i18n-strings */ - import { OBSTACLE_TYPES } from './constants'; import UserDeletionObstaclesList from './user_deletion_obstacles_list.vue'; diff --git a/app/assets/stylesheets/framework/variables.scss b/app/assets/stylesheets/framework/variables.scss index 504fae466bb..99284ea0a64 100644 --- a/app/assets/stylesheets/framework/variables.scss +++ b/app/assets/stylesheets/framework/variables.scss @@ -88,14 +88,6 @@ $white-normal: #f0f0f0 !default; $white-dark: #eaeaea !default; $white-transparent: rgba($white, 0.8) !default; -$gray-lightest: #fdfdfd !default; -$gray-light: #fafafa !default; -$gray-lighter: #f9f9f9 !default; -$gray-normal: #f5f5f5 !default; -$gray-dark: darken($gray-light, $darken-dark-factor) !default; -$gray-darker: #eee !default; -$gray-darkest: #c4c4c4 !default; - $purple: #6d49cb !default; $purple-light: #ede8fb !default; @@ -103,11 +95,6 @@ $black: #000 !default; $black-transparent: rgba(0, 0, 0, 0.3) !default; $almost-black: #242424 !default; -$t-gray-a-02: rgba($black, 0.02) !default; -$t-gray-a-04: rgba($black, 0.04) !default; -$t-gray-a-06: rgba($black, 0.06) !default; -$t-gray-a-08: rgba($black, 0.08) !default; - $green-50: #ecf4ee !default; $green-100: #c3e6cd !default; $green-200: #91d4a8 !default; @@ -168,18 +155,33 @@ $purple-800: #453894 !default; $purple-900: #2f2a6b !default; $purple-950: #232150 !default; -$gray-10: #f5f5f5 !default; -$gray-50: #f0f0f0 !default; -$gray-100: #dbdbdb !default; -$gray-200: #bfbfbf !default; -$gray-300: #999 !default; -$gray-400: #868686 !default; -$gray-500: #666 !default; -$gray-600: #5e5e5e !default; -$gray-700: #525252 !default; -$gray-800: #404040 !default; -$gray-900: #303030 !default; -$gray-950: #1f1f1f !default; +$gray-10: #fbfafd !default; +$gray-50: #ececef !default; +$gray-100: #dcdcde !default; +$gray-200: #bfbfc3 !default; +$gray-300: #a4a3a8 !default; +$gray-400: #89888d !default; +$gray-500: #737278 !default; +$gray-600: #626168 !default; +$gray-700: #535158 !default; +$gray-800: #434248 !default; +$gray-900: #333238 !default; +$gray-950: #1f1e24 !default; + +$gray-lightest: lighten($gray-10, 1) !default; +$gray-light: $gray-10 !default; +$gray-lighter: lighten($gray-50, 4) !default; +$gray-normal: lighten($gray-50, 2) !default; +$gray-dark: darken($gray-light, $darken-dark-factor) !default; +$gray-darker: $gray-50 !default; +$gray-darkest: $gray-200 !default; + +$t-gray-a-02: rgba($gray-950, 0.02) !default; +$t-gray-a-04: rgba($gray-950, 0.04) !default; +$t-gray-a-06: rgba($gray-950, 0.06) !default; +$t-gray-a-08: rgba($gray-950, 0.08) !default; +$t-gray-a-16: rgba($gray-950, 0.16) !default; +$t-gray-a-24: rgba($gray-950, 0.24) !default; $greens: ( '50': $green-50, @@ -370,7 +372,7 @@ $border-gray-normal-dashed: darken($gray-normal, $darken-border-dashed-factor); /* * UI elements */ -$contextual-sidebar-bg-color: #f5f5f5; +$contextual-sidebar-bg-color: $gray-10; $contextual-sidebar-border-color: #e9e9e9; $border-color: $gray-100; $shadow-color: $t-gray-a-08; diff --git a/app/assets/stylesheets/startup/startup-dark.scss b/app/assets/stylesheets/startup/startup-dark.scss index 3a81eec34e3..11131cc1a4b 100644 --- a/app/assets/stylesheets/startup/startup-dark.scss +++ b/app/assets/stylesheets/startup/startup-dark.scss @@ -6,15 +6,15 @@ color-scheme: dark; } body.gl-dark { - --gray-10: #1f1f1f; - --gray-50: #303030; - --gray-100: #404040; - --gray-200: #525252; - --gray-700: #dbdbdb; - --gray-900: #fafafa; + --gray-10: #1f1e24; + --gray-50: #333238; + --gray-100: #434248; + --gray-200: #535158; + --gray-700: #bfbfc3; + --gray-900: #ececef; --green-100: #0d532a; --green-700: #91d4a8; - --gl-text-color: #fafafa; + --gl-text-color: #ececef; --border-color: #4f4f4f; --black: #fff; } @@ -42,9 +42,9 @@ body { font-size: 1rem; font-weight: 400; line-height: 1.5; - color: #fafafa; + color: #ececef; text-align: left; - background-color: #1f1f1f; + background-color: #1f1e24; } ul { margin-top: 0; @@ -118,7 +118,7 @@ kbd { padding: 0.2rem 0.4rem; font-size: 90%; color: #333; - background-color: #fafafa; + background-color: #ececef; border-radius: 0.2rem; } kbd kbd { @@ -141,24 +141,24 @@ kbd kbd { font-size: 0.875rem; font-weight: 400; line-height: 1.5; - color: #fafafa; + color: #ececef; background-color: #333; background-clip: padding-box; - border: 1px solid #868686; + border: 1px solid #737278; border-radius: 0.25rem; } @media (prefers-reduced-motion: reduce) { } .form-control:-moz-focusring { color: transparent; - text-shadow: 0 0 0 #fafafa; + text-shadow: 0 0 0 #ececef; } .form-control::placeholder { - color: #bfbfbf; + color: #a4a3a8; opacity: 1; } .form-control:disabled { - background-color: #303030; + background-color: #333238; opacity: 1; } .form-inline { @@ -176,7 +176,7 @@ kbd kbd { .btn { display: inline-block; font-weight: 400; - color: #fafafa; + color: #ececef; text-align: center; vertical-align: middle; user-select: none; @@ -212,7 +212,7 @@ kbd kbd { padding: 0.5rem 0; margin: 0.125rem 0 0; font-size: 1rem; - color: #fafafa; + color: #ececef; text-align: left; list-style: none; background-color: #333; @@ -319,15 +319,15 @@ kbd kbd { border-radius: 10rem; } .badge-success { - color: #fff; + color: #fbfafd; background-color: #2da160; } .badge-info { - color: #fff; + color: #fbfafd; background-color: #428fdc; } .badge-warning { - color: #fff; + color: #fbfafd; background-color: #c17d10; } .rounded-circle { @@ -371,7 +371,7 @@ kbd kbd { .gl-avatar { border-width: 1px; border-style: solid; - border-color: rgba(0, 0, 0, 0.08); + border-color: rgba(251, 250, 253, 0.08); overflow: hidden; flex-shrink: 0; } @@ -455,8 +455,8 @@ a.gl-badge.badge-warning:active { padding-left: 0.75rem; padding-right: 0.75rem; height: auto; - color: #fafafa; - box-shadow: inset 0 0 0 1px #868686; + color: #ececef; + box-shadow: inset 0 0 0 1px #737278; border-style: none; appearance: none; -moz-appearance: none; @@ -465,17 +465,17 @@ a.gl-badge.badge-warning:active { .gl-form-input:not(.form-control-plaintext):not([type="color"]):read-only, .gl-form-input.form-control:disabled, .gl-form-input.form-control:not(.form-control-plaintext):not([type="color"]):read-only { - background-color: #1f1f1f; - box-shadow: inset 0 0 0 1px #404040; + background-color: #1f1e24; + box-shadow: inset 0 0 0 1px #434248; } .gl-form-input:disabled, .gl-form-input.form-control:disabled { cursor: not-allowed; - color: #999; + color: #89888d; } .gl-form-input::placeholder, .gl-form-input.form-control::placeholder { - color: #868686; + color: #737278; } .gl-icon { fill: currentColor; @@ -518,9 +518,9 @@ a.gl-badge.badge-warning:active { padding-right: 0.75rem; background-color: transparent; line-height: 1rem; - color: #fafafa; + color: #ececef; fill: currentColor; - box-shadow: inset 0 0 0 1px #525252; + box-shadow: inset 0 0 0 1px #535158; justify-content: center; align-items: center; font-size: 0.875rem; @@ -531,20 +531,20 @@ a.gl-badge.badge-warning:active { } .gl-button.gl-button.btn-default:active, .gl-button.gl-button.btn-default.active { - box-shadow: inset 0 0 0 1px #bfbfbf, 0 0 0 1px #333, 0 0 0 3px #1f75cb; + box-shadow: inset 0 0 0 1px #a4a3a8, 0 0 0 1px #333, 0 0 0 3px #1f75cb; outline: none; - background-color: #404040; + background-color: #434248; } .gl-button.gl-button.btn-default:active .gl-icon, .gl-button.gl-button.btn-default.active .gl-icon { - color: #fafafa; + color: #ececef; } .gl-button.gl-button.btn-default .gl-icon { - color: #999; + color: #89888d; } .gl-search-box-by-type-search-icon { margin: 0.5rem; - color: #999; + color: #89888d; width: 1rem; position: absolute; } @@ -594,11 +594,11 @@ svg { height: 0; margin: 4px 0; overflow: hidden; - border-top: 1px solid #404040; + border-top: 1px solid #434248; } .toggle-sidebar-button .collapse-text, .toggle-sidebar-button .icon-chevron-double-lg-left { - color: #999; + color: #89888d; } html { overflow-y: scroll; @@ -614,20 +614,20 @@ html { font-weight: 400; padding: 6px 10px; background-color: #333; - border-color: #404040; - color: #fafafa; - color: #fafafa; + border-color: #434248; + color: #ececef; + color: #ececef; white-space: nowrap; } .btn:active { - background-color: #303030; + background-color: #333238; box-shadow: none; } .btn:active, .btn.active { background-color: #444; border-color: #4f4f4f; - color: #fafafa; + color: #ececef; } .btn svg { height: 15px; @@ -639,7 +639,7 @@ html { .badge.badge-pill:not(.gl-badge) { font-weight: 400; background-color: rgba(255, 255, 255, 0.07); - color: #dbdbdb; + color: #bfbfc3; vertical-align: baseline; } .gl-font-sm { @@ -658,10 +658,10 @@ html { .dropdown-menu-toggle { padding: 6px 8px 6px 10px; background-color: #333; - color: #fafafa; + color: #ececef; font-size: 14px; text-align: left; - border: 1px solid #404040; + border: 1px solid #434248; border-radius: 0.25rem; white-space: nowrap; } @@ -690,7 +690,7 @@ html { font-weight: 400; padding: 8px 0; background-color: #333; - border: 1px solid #404040; + border: 1px solid #434248; border-radius: 0.25rem; box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); } @@ -713,7 +713,7 @@ html { font-weight: 400; position: relative; padding: 8px 12px; - color: #fafafa; + color: #ececef; line-height: 16px; white-space: normal; overflow: hidden; @@ -723,7 +723,7 @@ html { .dropdown-menu li > a:active, .dropdown-menu li button:active { background-color: #4f4f4f; - color: #fafafa; + color: #ececef; outline: 0; text-decoration: none; } @@ -737,7 +737,7 @@ html { height: 1px; margin: 0.25rem 0; padding: 0; - background-color: #404040; + background-color: #434248; } .dropdown-menu .badge.badge-pill + span:not(.badge):not(.badge-pill) { margin-right: 40px; @@ -764,7 +764,7 @@ html { } input { border-radius: 0.25rem; - color: #fafafa; + color: #ececef; background-color: #333; } .form-control { @@ -772,23 +772,23 @@ input { padding: 6px 10px; } .form-control::placeholder { - color: #868686; + color: #737278; } kbd { display: inline-block; padding: 3px 5px; font-size: 0.6875rem; line-height: 10px; - color: var(--gray-700, #dbdbdb); + color: var(--gray-700, #bfbfc3); vertical-align: middle; - background-color: var(--gray-10, #1f1f1f); + background-color: var(--gray-10, #1f1e24); border-width: 1px; border-style: solid; - border-color: var(--gray-100, #404040) var(--gray-100, #404040) - var(--gray-200, #525252); + border-color: var(--gray-100, #434248) var(--gray-100, #434248) + var(--gray-200, #535158); border-image: none; border-radius: 3px; - box-shadow: 0 -1px 0 var(--gray-200, #525252) inset; + box-shadow: 0 -1px 0 var(--gray-200, #535158) inset; } .navbar-gitlab { padding: 0 16px; @@ -1042,7 +1042,7 @@ kbd { width: 100%; align-items: center; padding: 10px 16px 10px 10px; - color: #fafafa; + color: #ececef; background-color: transparent; border: 0; text-align: left; @@ -1054,7 +1054,7 @@ kbd { .context-header .sidebar-context-title { overflow: hidden; text-overflow: ellipsis; - color: #fafafa; + color: #ececef; } @media (min-width: 768px) { .page-with-contextual-sidebar { @@ -1078,7 +1078,7 @@ kbd { z-index: 600; width: 256px; top: var(--header-height, 48px); - background-color: #f5f5f5; + background-color: #1f1e24; border-right: 1px solid #e9e9e9; transform: translate3d(0, 0, 0); } @@ -1115,7 +1115,7 @@ kbd { } .nav-sidebar a { text-decoration: none; - color: #fafafa; + color: #ececef; } .nav-sidebar li { white-space: nowrap; @@ -1400,7 +1400,7 @@ kbd { display: block; } .sidebar-top-level-items li > a.gl-link { - color: #fafafa; + color: #ececef; } .sidebar-top-level-items li > a.gl-link:active { text-decoration: none; @@ -1417,12 +1417,12 @@ kbd { .close-nav-button { height: 48px; padding: 0 16px; - background-color: #303030; + background-color: #333238; border: 0; - color: #999; + color: #89888d; display: flex; align-items: center; - background-color: #f5f5f5; + background-color: #1f1e24; position: fixed; bottom: 0; width: 255px; @@ -1493,14 +1493,14 @@ kbd { } } input::-moz-placeholder { - color: #868686; + color: #737278; opacity: 1; } input::-ms-input-placeholder { - color: #868686; + color: #737278; } input:-ms-input-placeholder { - color: #868686; + color: #737278; } svg { fill: currentColor; @@ -1629,7 +1629,7 @@ svg.s16 { padding: 0; background: #222; overflow: hidden; - box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.1); + box-shadow: inset 0 0 0 1px rgba(251, 250, 253, 0.1); } .avatar.avatar-tile { border-radius: 0; @@ -1638,8 +1638,8 @@ svg.s16 { .identicon { text-align: center; vertical-align: top; - color: #fafafa; - background-color: #303030; + color: #ececef; + background-color: #333238; } .identicon.s16 { font-size: 10px; @@ -1668,7 +1668,7 @@ svg.s16 { background-color: #5c2900; } .identicon.bg7 { - background-color: #303030; + background-color: #333238; } .avatar-container { overflow: hidden; @@ -1707,18 +1707,18 @@ svg.s16 { color-scheme: dark; } body.gl-dark { - --gray-10: #1f1f1f; - --gray-50: #303030; - --gray-100: #404040; - --gray-200: #525252; - --gray-300: #5e5e5e; - --gray-400: #868686; - --gray-500: #999; - --gray-600: #bfbfbf; - --gray-700: #dbdbdb; - --gray-800: #f0f0f0; - --gray-900: #fafafa; - --gray-950: #fff; + --gray-10: #1f1e24; + --gray-50: #333238; + --gray-100: #434248; + --gray-200: #535158; + --gray-300: #626168; + --gray-400: #737278; + --gray-500: #89888d; + --gray-600: #a4a3a8; + --gray-700: #bfbfc3; + --gray-800: #dcdcde; + --gray-900: #ececef; + --gray-950: #fbfafd; --green-50: #0a4020; --green-100: #0d532a; --green-200: #24663b; @@ -1790,59 +1790,59 @@ body.gl-dark { --dark-icon-color-purple-3: #9a79f7; --dark-icon-color-orange-1: #665349; --dark-icon-color-orange-2: #b37a5d; - --gl-text-color: #fafafa; + --gl-text-color: #ececef; --border-color: #4f4f4f; --white: #333; --black: #fff; - --gray-light: #303030; + --gray-light: #333238; --svg-status-bg: #333; } .nav-sidebar, .toggle-sidebar-button, .close-nav-button { - background-color: #262626; - border-right: 1px solid #303030; + background-color: #29282d; + border-right: 1px solid #333238; } .gl-avatar:not(.gl-avatar-identicon), .avatar-container, .avatar { - background: rgba(255, 255, 255, 0.04); + background: rgba(251, 250, 253, 0.04); } .gl-avatar { border-style: none; - box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.1); + box-shadow: inset 0 0 0 1px rgba(251, 250, 253, 0.1); } body.gl-dark { - --gl-theme-accent: #868686; + --gl-theme-accent: #737278; } body.gl-dark .navbar-gitlab { - background-color: #fafafa; + background-color: #ececef; } body.gl-dark .navbar-gitlab .navbar-collapse { - color: #fafafa; + color: #ececef; } body.gl-dark .navbar-gitlab .container-fluid .navbar-toggler { - border-left: 1px solid #b3b3b3; - color: #fafafa; + border-left: 1px solid #a3a2a6; + color: #ececef; } body.gl-dark .navbar-gitlab .navbar-sub-nav > li.active > a, body.gl-dark .navbar-gitlab .navbar-sub-nav > li.active > button, body.gl-dark .navbar-gitlab .navbar-nav > li.active > a, body.gl-dark .navbar-gitlab .navbar-nav > li.active > button { - color: #fafafa; + color: #ececef; background-color: #333; } body.gl-dark .navbar-gitlab .navbar-sub-nav { - color: #fafafa; + color: #ececef; } body.gl-dark .navbar-gitlab .nav > li { - color: #fafafa; + color: #ececef; } body.gl-dark .navbar-gitlab .nav > li.header-search-new { - color: #fafafa; + color: #ececef; } body.gl-dark .navbar-gitlab .nav > li > a .notification-dot { - border: 2px solid #fafafa; + border: 2px solid #ececef; } body.gl-dark .navbar-gitlab @@ -1850,7 +1850,7 @@ body.gl-dark > li > a.header-help-dropdown-toggle .notification-dot { - background-color: #fafafa; + background-color: #ececef; } body.gl-dark .navbar-gitlab @@ -1858,10 +1858,10 @@ body.gl-dark > li > a.header-user-dropdown-toggle .header-user-avatar { - border-color: #fafafa; + border-color: #ececef; } body.gl-dark .navbar-gitlab .nav > li.active > a { - color: #fafafa; + color: #ececef; background-color: #333; } body.gl-dark .navbar-gitlab .nav > li.active > a .notification-dot { @@ -1873,48 +1873,48 @@ body.gl-dark > li.active > a.header-help-dropdown-toggle .notification-dot { - background-color: #fafafa; + background-color: #ececef; } body.gl-dark .header-search { - background-color: rgba(250, 250, 250, 0.2) !important; + background-color: rgba(236, 236, 239, 0.2) !important; border-radius: 4px; } body.gl-dark .header-search svg.gl-search-box-by-type-search-icon { - color: rgba(250, 250, 250, 0.8); + color: rgba(236, 236, 239, 0.8); } body.gl-dark .header-search input { background-color: transparent; - color: rgba(250, 250, 250, 0.8); - box-shadow: inset 0 0 0 1px rgba(250, 250, 250, 0.4); + color: rgba(236, 236, 239, 0.8); + box-shadow: inset 0 0 0 1px rgba(236, 236, 239, 0.4); } body.gl-dark .header-search input::placeholder { - color: rgba(250, 250, 250, 0.8); + color: rgba(236, 236, 239, 0.8); } body.gl-dark .header-search input:active::placeholder { - color: #868686; + color: #737278; } body.gl-dark .header-search .keyboard-shortcut-helper { - color: #fafafa; - background-color: rgba(250, 250, 250, 0.2); + color: #ececef; + background-color: rgba(236, 236, 239, 0.2); } body.gl-dark .search form { - background-color: rgba(250, 250, 250, 0.2); + background-color: rgba(236, 236, 239, 0.2); } body.gl-dark .search .search-input::placeholder { - color: rgba(250, 250, 250, 0.8); + color: rgba(236, 236, 239, 0.8); } body.gl-dark .search .search-input-wrap .search-icon, body.gl-dark .search .search-input-wrap .clear-icon { - fill: rgba(250, 250, 250, 0.8); + fill: rgba(236, 236, 239, 0.8); } body.gl-dark .nav-sidebar li.active > a { - color: #fafafa; + color: #ececef; } body.gl-dark .nav-sidebar .fly-out-top-item a, body.gl-dark .nav-sidebar .fly-out-top-item.active a, body.gl-dark .nav-sidebar .fly-out-top-item .fly-out-top-item-container { - background-color: var(--gray-100, #303030); - color: var(--gray-900, #fafafa); + background-color: var(--gray-100, #333238); + color: var(--gray-900, #ececef); } body.gl-dark .navbar-gitlab { background-color: var(--gray-50); @@ -1951,18 +1951,18 @@ body.gl-dark .navbar-gitlab .search form .search-input { color-scheme: dark; } body.gl-dark { - --gray-10: #1f1f1f; - --gray-50: #303030; - --gray-100: #404040; - --gray-200: #525252; - --gray-300: #5e5e5e; - --gray-400: #868686; - --gray-500: #999; - --gray-600: #bfbfbf; - --gray-700: #dbdbdb; - --gray-800: #f0f0f0; - --gray-900: #fafafa; - --gray-950: #fff; + --gray-10: #1f1e24; + --gray-50: #333238; + --gray-100: #434248; + --gray-200: #535158; + --gray-300: #626168; + --gray-400: #737278; + --gray-500: #89888d; + --gray-600: #a4a3a8; + --gray-700: #bfbfc3; + --gray-800: #dcdcde; + --gray-900: #ececef; + --gray-950: #fbfafd; --green-50: #0a4020; --green-100: #0d532a; --green-200: #24663b; @@ -2034,11 +2034,11 @@ body.gl-dark { --dark-icon-color-purple-3: #9a79f7; --dark-icon-color-orange-1: #665349; --dark-icon-color-orange-2: #b37a5d; - --gl-text-color: #fafafa; + --gl-text-color: #ececef; --border-color: #4f4f4f; --white: #333; --black: #fff; - --gray-light: #303030; + --gray-light: #333238; --svg-status-bg: #333; } .tab-width-8 { diff --git a/app/assets/stylesheets/startup/startup-general.scss b/app/assets/stylesheets/startup/startup-general.scss index 802262ba346..7fb373bb6f4 100644 --- a/app/assets/stylesheets/startup/startup-general.scss +++ b/app/assets/stylesheets/startup/startup-general.scss @@ -23,7 +23,7 @@ body { font-size: 1rem; font-weight: 400; line-height: 1.5; - color: #303030; + color: #333238; text-align: left; background-color: #fff; } @@ -99,7 +99,7 @@ kbd { padding: 0.2rem 0.4rem; font-size: 90%; color: #fff; - background-color: #303030; + background-color: #333238; border-radius: 0.2rem; } kbd kbd { @@ -122,24 +122,24 @@ kbd kbd { font-size: 0.875rem; font-weight: 400; line-height: 1.5; - color: #303030; + color: #333238; background-color: #fff; background-clip: padding-box; - border: 1px solid #868686; + border: 1px solid #89888d; border-radius: 0.25rem; } @media (prefers-reduced-motion: reduce) { } .form-control:-moz-focusring { color: transparent; - text-shadow: 0 0 0 #303030; + text-shadow: 0 0 0 #333238; } .form-control::placeholder { - color: #5e5e5e; + color: #626168; opacity: 1; } .form-control:disabled { - background-color: #fafafa; + background-color: #fbfafd; opacity: 1; } .form-inline { @@ -157,7 +157,7 @@ kbd kbd { .btn { display: inline-block; font-weight: 400; - color: #303030; + color: #333238; text-align: center; vertical-align: middle; user-select: none; @@ -193,7 +193,7 @@ kbd kbd { padding: 0.5rem 0; margin: 0.125rem 0 0; font-size: 1rem; - color: #303030; + color: #333238; text-align: left; list-style: none; background-color: #fff; @@ -352,7 +352,7 @@ kbd kbd { .gl-avatar { border-width: 1px; border-style: solid; - border-color: rgba(0, 0, 0, 0.08); + border-color: rgba(31, 30, 36, 0.08); overflow: hidden; flex-shrink: 0; } @@ -436,8 +436,8 @@ a.gl-badge.badge-warning:active { padding-left: 0.75rem; padding-right: 0.75rem; height: auto; - color: #303030; - box-shadow: inset 0 0 0 1px #868686; + color: #333238; + box-shadow: inset 0 0 0 1px #89888d; border-style: none; appearance: none; -moz-appearance: none; @@ -446,17 +446,17 @@ a.gl-badge.badge-warning:active { .gl-form-input:not(.form-control-plaintext):not([type="color"]):read-only, .gl-form-input.form-control:disabled, .gl-form-input.form-control:not(.form-control-plaintext):not([type="color"]):read-only { - background-color: #f5f5f5; - box-shadow: inset 0 0 0 1px #dbdbdb; + background-color: #fbfafd; + box-shadow: inset 0 0 0 1px #dcdcde; } .gl-form-input:disabled, .gl-form-input.form-control:disabled { cursor: not-allowed; - color: #666; + color: #737278; } .gl-form-input::placeholder, .gl-form-input.form-control::placeholder { - color: #868686; + color: #89888d; } .gl-icon { fill: currentColor; @@ -499,9 +499,9 @@ a.gl-badge.badge-warning:active { padding-right: 0.75rem; background-color: transparent; line-height: 1rem; - color: #303030; + color: #333238; fill: currentColor; - box-shadow: inset 0 0 0 1px #bfbfbf; + box-shadow: inset 0 0 0 1px #bfbfc3; justify-content: center; align-items: center; font-size: 0.875rem; @@ -512,20 +512,20 @@ a.gl-badge.badge-warning:active { } .gl-button.gl-button.btn-default:active, .gl-button.gl-button.btn-default.active { - box-shadow: inset 0 0 0 1px #5e5e5e, 0 0 0 1px #fff, 0 0 0 3px #428fdc; + box-shadow: inset 0 0 0 1px #626168, 0 0 0 1px #fff, 0 0 0 3px #428fdc; outline: none; - background-color: #dbdbdb; + background-color: #dcdcde; } .gl-button.gl-button.btn-default:active .gl-icon, .gl-button.gl-button.btn-default.active .gl-icon { - color: #303030; + color: #333238; } .gl-button.gl-button.btn-default .gl-icon { - color: #666; + color: #737278; } .gl-search-box-by-type-search-icon { margin: 0.5rem; - color: #666; + color: #737278; width: 1rem; position: absolute; } @@ -575,11 +575,11 @@ svg { height: 0; margin: 4px 0; overflow: hidden; - border-top: 1px solid #dbdbdb; + border-top: 1px solid #dcdcde; } .toggle-sidebar-button .collapse-text, .toggle-sidebar-button .icon-chevron-double-lg-left { - color: #666; + color: #737278; } html { overflow-y: scroll; @@ -595,20 +595,20 @@ html { font-weight: 400; padding: 6px 10px; background-color: #fff; - border-color: #dbdbdb; - color: #303030; - color: #303030; + border-color: #dcdcde; + color: #333238; + color: #333238; white-space: nowrap; } .btn:active { - background-color: #f0f0f0; + background-color: #ececef; box-shadow: none; } .btn:active, .btn.active { background-color: #eaeaea; border-color: #e3e3e3; - color: #303030; + color: #333238; } .btn svg { height: 15px; @@ -620,7 +620,7 @@ html { .badge.badge-pill:not(.gl-badge) { font-weight: 400; background-color: rgba(0, 0, 0, 0.07); - color: #525252; + color: #535158; vertical-align: baseline; } .gl-font-sm { @@ -639,10 +639,10 @@ html { .dropdown-menu-toggle { padding: 6px 8px 6px 10px; background-color: #fff; - color: #303030; + color: #333238; font-size: 14px; text-align: left; - border: 1px solid #dbdbdb; + border: 1px solid #dcdcde; border-radius: 0.25rem; white-space: nowrap; } @@ -671,7 +671,7 @@ html { font-weight: 400; padding: 8px 0; background-color: #fff; - border: 1px solid #dbdbdb; + border: 1px solid #dcdcde; border-radius: 0.25rem; box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); } @@ -694,7 +694,7 @@ html { font-weight: 400; position: relative; padding: 8px 12px; - color: #303030; + color: #333238; line-height: 16px; white-space: normal; overflow: hidden; @@ -703,8 +703,8 @@ html { } .dropdown-menu li > a:active, .dropdown-menu li button:active { - background-color: #eee; - color: #303030; + background-color: #ececef; + color: #333238; outline: 0; text-decoration: none; } @@ -718,7 +718,7 @@ html { height: 1px; margin: 0.25rem 0; padding: 0; - background-color: #dbdbdb; + background-color: #dcdcde; } .dropdown-menu .badge.badge-pill + span:not(.badge):not(.badge-pill) { margin-right: 40px; @@ -745,7 +745,7 @@ html { } input { border-radius: 0.25rem; - color: #303030; + color: #333238; background-color: #fff; } .form-control { @@ -753,23 +753,23 @@ input { padding: 6px 10px; } .form-control::placeholder { - color: #868686; + color: #89888d; } kbd { display: inline-block; padding: 3px 5px; font-size: 0.6875rem; line-height: 10px; - color: var(--gray-700, #525252); + color: var(--gray-700, #535158); vertical-align: middle; - background-color: var(--gray-10, #f5f5f5); + background-color: var(--gray-10, #fbfafd); border-width: 1px; border-style: solid; - border-color: var(--gray-100, #dbdbdb) var(--gray-100, #dbdbdb) - var(--gray-200, #bfbfbf); + border-color: var(--gray-100, #dcdcde) var(--gray-100, #dcdcde) + var(--gray-200, #bfbfc3); border-image: none; border-radius: 3px; - box-shadow: 0 -1px 0 var(--gray-200, #bfbfbf) inset; + box-shadow: 0 -1px 0 var(--gray-200, #bfbfc3) inset; } .navbar-gitlab { padding: 0 16px; @@ -991,7 +991,7 @@ kbd { float: left; margin-right: 5px; border-radius: 50%; - border: 1px solid #f5f5f5; + border: 1px solid #f2f2f4; } .notification-dot { background-color: #d99530; @@ -1023,7 +1023,7 @@ kbd { width: 100%; align-items: center; padding: 10px 16px 10px 10px; - color: #303030; + color: #333238; background-color: transparent; border: 0; text-align: left; @@ -1035,7 +1035,7 @@ kbd { .context-header .sidebar-context-title { overflow: hidden; text-overflow: ellipsis; - color: #303030; + color: #333238; } @media (min-width: 768px) { .page-with-contextual-sidebar { @@ -1059,7 +1059,7 @@ kbd { z-index: 600; width: 256px; top: var(--header-height, 48px); - background-color: #f5f5f5; + background-color: #fbfafd; border-right: 1px solid #e9e9e9; transform: translate3d(0, 0, 0); } @@ -1096,7 +1096,7 @@ kbd { } .nav-sidebar a { text-decoration: none; - color: #303030; + color: #333238; } .nav-sidebar li { white-space: nowrap; @@ -1381,7 +1381,7 @@ kbd { display: block; } .sidebar-top-level-items li > a.gl-link { - color: #303030; + color: #333238; } .sidebar-top-level-items li > a.gl-link:active { text-decoration: none; @@ -1398,12 +1398,12 @@ kbd { .close-nav-button { height: 48px; padding: 0 16px; - background-color: #fafafa; + background-color: #fbfafd; border: 0; - color: #666; + color: #737278; display: flex; align-items: center; - background-color: #f5f5f5; + background-color: #fbfafd; position: fixed; bottom: 0; width: 255px; @@ -1474,14 +1474,14 @@ kbd { } } input::-moz-placeholder { - color: #868686; + color: #89888d; opacity: 1; } input::-ms-input-placeholder { - color: #868686; + color: #89888d; } input:-ms-input-placeholder { - color: #868686; + color: #89888d; } svg { fill: currentColor; @@ -1608,9 +1608,9 @@ svg.s16 { width: 40px; height: 40px; padding: 0; - background: #fdfdfd; + background: #fefefe; overflow: hidden; - box-shadow: inset 0 0 0 1px rgba(31, 31, 31, 0.1); + box-shadow: inset 0 0 0 1px rgba(31, 30, 36, 0.1); } .avatar.avatar-tile { border-radius: 0; @@ -1619,8 +1619,8 @@ svg.s16 { .identicon { text-align: center; vertical-align: top; - color: #303030; - background-color: #f0f0f0; + color: #333238; + background-color: #ececef; } .identicon.s16 { font-size: 10px; @@ -1649,7 +1649,7 @@ svg.s16 { background-color: #fdf1dd; } .identicon.bg7 { - background-color: #f0f0f0; + background-color: #ececef; } .avatar-container { overflow: hidden; diff --git a/app/assets/stylesheets/startup/startup-signin.scss b/app/assets/stylesheets/startup/startup-signin.scss index 33e10b9bd62..7ae158b3930 100644 --- a/app/assets/stylesheets/startup/startup-signin.scss +++ b/app/assets/stylesheets/startup/startup-signin.scss @@ -22,7 +22,7 @@ body { font-size: 1rem; font-weight: 400; line-height: 1.5; - color: #303030; + color: #333238; text-align: left; background-color: #fff; } @@ -110,7 +110,7 @@ h3 { margin-bottom: 0.25rem; font-weight: 600; line-height: 1.2; - color: #303030; + color: #333238; } h1 { font-size: 2.1875rem; @@ -196,24 +196,24 @@ hr { font-size: 0.875rem; font-weight: 400; line-height: 1.5; - color: #303030; + color: #333238; background-color: #fff; background-clip: padding-box; - border: 1px solid #868686; + border: 1px solid #89888d; border-radius: 0.25rem; } @media (prefers-reduced-motion: reduce) { } .form-control:-moz-focusring { color: transparent; - text-shadow: 0 0 0 #303030; + text-shadow: 0 0 0 #333238; } .form-control::placeholder { - color: #5e5e5e; + color: #626168; opacity: 1; } .form-control:disabled { - background-color: #fafafa; + background-color: #fbfafd; opacity: 1; } .form-group { @@ -222,7 +222,7 @@ hr { .btn { display: inline-block; font-weight: 400; - color: #303030; + color: #333238; text-align: center; vertical-align: middle; user-select: none; @@ -282,10 +282,10 @@ input.btn-block[type="button"] { border-color: #b3d7ff; } .custom-control-input:disabled ~ .custom-control-label { - color: #5e5e5e; + color: #626168; } .custom-control-input:disabled ~ .custom-control-label::before { - background-color: #fafafa; + background-color: #fbfafd; } .custom-control-label { position: relative; @@ -302,7 +302,7 @@ input.btn-block[type="button"] { pointer-events: none; content: ""; background-color: #fff; - border: #666 solid 1px; + border: #737278 solid 1px; } .custom-control-label::after { position: absolute; @@ -400,8 +400,8 @@ input.btn-block[type="button"] { padding-left: 0.75rem; padding-right: 0.75rem; height: auto; - color: #303030; - box-shadow: inset 0 0 0 1px #868686; + color: #333238; + box-shadow: inset 0 0 0 1px #89888d; border-style: none; appearance: none; -moz-appearance: none; @@ -410,27 +410,27 @@ input.btn-block[type="button"] { .gl-form-input:not(.form-control-plaintext):not([type="color"]):read-only, .gl-form-input.form-control:disabled, .gl-form-input.form-control:not(.form-control-plaintext):not([type="color"]):read-only { - background-color: #f5f5f5; - box-shadow: inset 0 0 0 1px #dbdbdb; + background-color: #fbfafd; + box-shadow: inset 0 0 0 1px #dcdcde; } .gl-form-input:disabled, .gl-form-input.form-control:disabled { cursor: not-allowed; - color: #666; + color: #737278; } .gl-form-input::placeholder, .gl-form-input.form-control::placeholder { - color: #868686; + color: #89888d; } .gl-form-checkbox { font-size: 0.875rem; line-height: 1rem; - color: #303030; + color: #333238; } .gl-form-checkbox .custom-control-input:disabled, .gl-form-checkbox .custom-control-input:disabled ~ .custom-control-label { cursor: not-allowed; - color: #868686; + color: #89888d; } .gl-form-checkbox.custom-control .custom-control-input ~ .custom-control-label { cursor: pointer; @@ -447,7 +447,7 @@ input.btn-block[type="button"] { .custom-control-input ~ .custom-control-label::before { background-color: #fff; - border-color: #868686; + border-color: #89888d; } .gl-form-checkbox.custom-control .custom-control-input:checked @@ -490,8 +490,8 @@ input.btn-block[type="button"] { .gl-form-checkbox.custom-control .custom-control-input:disabled ~ .custom-control-label::before { - background-color: #f0f0f0; - border-color: #dbdbdb; + background-color: #ececef; + border-color: #dcdcde; pointer-events: auto; } .gl-form-checkbox.custom-control @@ -500,8 +500,8 @@ input.btn-block[type="button"] { .gl-form-checkbox.custom-control .custom-control-input[type="checkbox"]:indeterminate:disabled ~ .custom-control-label::before { - background-color: #dbdbdb; - border-color: #dbdbdb; + background-color: #dcdcde; + border-color: #dcdcde; } .gl-form-checkbox.custom-control .custom-control-input:checked:disabled @@ -509,7 +509,7 @@ input.btn-block[type="button"] { .gl-form-checkbox.custom-control .custom-control-input[type="checkbox"]:indeterminate:disabled ~ .custom-control-label::after { - background-color: #5e5e5e; + background-color: #626168; } .gl-button { display: inline-flex; @@ -526,9 +526,9 @@ input.btn-block[type="button"] { padding-right: 0.75rem; background-color: transparent; line-height: 1rem; - color: #303030; + color: #333238; fill: currentColor; - box-shadow: inset 0 0 0 1px #bfbfbf; + box-shadow: inset 0 0 0 1px #bfbfc3; justify-content: center; align-items: center; font-size: 0.875rem; @@ -560,9 +560,9 @@ input.btn-block[type="button"] { .gl-button.gl-button.btn-default.active, .gl-button.gl-button.btn-block.btn-default:active, .gl-button.gl-button.btn-block.btn-default.active { - box-shadow: inset 0 0 0 1px #5e5e5e, 0 0 0 1px #fff, 0 0 0 3px #428fdc; + box-shadow: inset 0 0 0 1px #626168, 0 0 0 1px #fff, 0 0 0 3px #428fdc; outline: none; - background-color: #dbdbdb; + background-color: #dcdcde; } .gl-button.gl-button.btn-confirm, .gl-button.gl-button.btn-block.btn-confirm { @@ -636,20 +636,20 @@ body.navless { font-weight: 400; padding: 6px 10px; background-color: #fff; - border-color: #dbdbdb; - color: #303030; - color: #303030; + border-color: #dcdcde; + color: #333238; + color: #333238; white-space: nowrap; } .btn:active { - background-color: #f0f0f0; + background-color: #ececef; box-shadow: none; } .btn:active, .btn.active { background-color: #eaeaea; border-color: #e3e3e3; - color: #303030; + color: #333238; } .btn svg { height: 15px; @@ -676,7 +676,7 @@ body.navless { } hr { margin: 1.5rem 0; - border-top: 1px solid #eee; + border-top: 1px solid #ececef; } .footer-links { margin-bottom: 20px; @@ -704,7 +704,7 @@ hr { } input { border-radius: 0.25rem; - color: #303030; + color: #333238; background-color: #fff; } label { @@ -721,7 +721,7 @@ label.label-bold { padding: 6px 10px; } .form-control::placeholder { - color: #868686; + color: #89888d; } .gl-show-field-errors .form-control:not(textarea) { height: 34px; @@ -730,7 +730,7 @@ label.label-bold { justify-content: center; height: var(--header-height, 48px); background: #fff; - border-bottom: 1px solid #dbdbdb; + border-bottom: 1px solid #dcdcde; } .navbar-empty .tanuki-logo, .navbar-empty .brand-header-logo { @@ -747,14 +747,14 @@ label.label-bold { fill: #fca326; } input::-moz-placeholder { - color: #868686; + color: #89888d; opacity: 1; } input::-ms-input-placeholder { - color: #868686; + color: #89888d; } input:-ms-input-placeholder { - color: #868686; + color: #89888d; } svg { fill: currentColor; @@ -805,7 +805,7 @@ svg { } .login-page .login-box, .login-page .omniauth-container { - box-shadow: 0 0 0 1px #dbdbdb; + box-shadow: 0 0 0 1px #dcdcde; border-radius: 0.25rem; } .login-page .login-box .login-heading h3, @@ -863,7 +863,7 @@ svg { } .login-page .new-session-tabs { display: flex; - box-shadow: 0 0 0 1px #dbdbdb; + box-shadow: 0 0 0 1px #dcdcde; border-top-right-radius: 4px; border-top-left-radius: 4px; } @@ -874,7 +874,7 @@ svg { .login-page .new-session-tabs.nav-links-unboxed .nav-item { border-left: 0; border-right: 0; - border-bottom: 1px solid #dbdbdb; + border-bottom: 1px solid #dcdcde; background-color: transparent; } .login-page .new-session-tabs.custom-provider-tabs { @@ -885,7 +885,7 @@ svg { flex-basis: auto; } .login-page .new-session-tabs.custom-provider-tabs li:nth-child(n + 5) { - border-top: 1px solid #dbdbdb; + border-top: 1px solid #dcdcde; } .login-page .new-session-tabs.custom-provider-tabs a { font-size: 16px; @@ -893,7 +893,7 @@ svg { .login-page .new-session-tabs li { flex: 1; text-align: center; - border-left: 1px solid #dbdbdb; + border-left: 1px solid #dcdcde; } .login-page .new-session-tabs li:first-of-type { border-left: 0; @@ -903,7 +903,7 @@ svg { border-top-right-radius: 4px; } .login-page .new-session-tabs li:not(.active) { - background-color: #fafafa; + background-color: #fbfafd; } .login-page .new-session-tabs li a { width: 100%; diff --git a/app/assets/stylesheets/themes/_dark.scss b/app/assets/stylesheets/themes/_dark.scss index 7126c99988c..a3474d2ed50 100644 --- a/app/assets/stylesheets/themes/_dark.scss +++ b/app/assets/stylesheets/themes/_dark.scss @@ -1,15 +1,15 @@ -$gray-10: #1f1f1f; -$gray-50: #303030; -$gray-100: #404040; -$gray-200: #525252; -$gray-300: #5e5e5e; -$gray-400: #868686; -$gray-500: #999; -$gray-600: #bfbfbf; -$gray-700: #dbdbdb; -$gray-800: #f0f0f0; -$gray-900: #fafafa; -$gray-950: #fff; +$gray-10: #1f1e24; +$gray-50: #333238; +$gray-100: #434248; +$gray-200: #535158; +$gray-300: #626168; +$gray-400: #737278; +$gray-500: #89888d; +$gray-600: #a4a3a8; +$gray-700: #bfbfc3; +$gray-800: #dcdcde; +$gray-900: #ececef; +$gray-950: #fbfafd; $green-50: #0a4020; $green-100: #0d532a; diff --git a/app/controllers/jira_connect/application_controller.rb b/app/controllers/jira_connect/application_controller.rb index a70c1ef4965..b9f0ea795e1 100644 --- a/app/controllers/jira_connect/application_controller.rb +++ b/app/controllers/jira_connect/application_controller.rb @@ -5,7 +5,6 @@ class JiraConnect::ApplicationController < ApplicationController CORS_ALLOWED_METHODS = { '/-/jira_connect/oauth_application_id' => %i[GET OPTIONS], - '/-/jira_connect/subscriptions' => %i[GET POST OPTIONS], '/-/jira_connect/subscriptions/*' => %i[DELETE OPTIONS] }.freeze diff --git a/app/graphql/types/metadata_type.rb b/app/graphql/types/metadata_type.rb index b00fcfd38ad..492cca365f3 100644 --- a/app/graphql/types/metadata_type.rb +++ b/app/graphql/types/metadata_type.rb @@ -6,6 +6,8 @@ module Types authorize :read_instance_metadata + field :enterprise, GraphQL::Types::Boolean, null: false, + description: 'Enterprise edition.' field :kas, ::Types::Metadata::KasType, null: false, description: 'Metadata about KAS.' field :revision, GraphQL::Types::String, null: false, diff --git a/app/models/active_session.rb b/app/models/active_session.rb index dbc590668a8..b16c4a2b353 100644 --- a/app/models/active_session.rb +++ b/app/models/active_session.rb @@ -83,24 +83,26 @@ class ActiveSession is_impersonated: request.session[:impersonator_id].present? ) - redis.pipelined do |pipeline| - pipeline.setex( - key_name(user.id, session_private_id), - expiry, - active_user_session.dump - ) - - # Deprecated legacy format - temporary to support mixed deployments - pipeline.setex( - key_name_v1(user.id, session_private_id), - expiry, - Marshal.dump(active_user_session) - ) - - pipeline.sadd?( - lookup_key_name(user.id), - session_private_id - ) + Gitlab::Instrumentation::RedisClusterValidator.allow_cross_slot_commands do + redis.pipelined do |pipeline| + pipeline.setex( + key_name(user.id, session_private_id), + expiry, + active_user_session.dump + ) + + # Deprecated legacy format - temporary to support mixed deployments + pipeline.setex( + key_name_v1(user.id, session_private_id), + expiry, + Marshal.dump(active_user_session) + ) + + pipeline.sadd?( + lookup_key_name(user.id), + session_private_id + ) + end end end end diff --git a/app/models/awareness_session.rb b/app/models/awareness_session.rb index cca69e38b6f..0b652984630 100644 --- a/app/models/awareness_session.rb +++ b/app/models/awareness_session.rb @@ -63,16 +63,18 @@ class AwarenessSession # rubocop:disable Gitlab/NamespacedClass user_key = user_sessions_key(user.id) with_redis do |redis| - redis.pipelined do |pipeline| - pipeline.sadd?(user_key, id_i) - pipeline.expire(user_key, USER_LIFETIME.to_i) + Gitlab::Instrumentation::RedisClusterValidator.allow_cross_slot_commands do + redis.pipelined do |pipeline| + pipeline.sadd?(user_key, id_i) + pipeline.expire(user_key, USER_LIFETIME.to_i) - pipeline.zadd(users_key, timestamp.to_f, user.id) + pipeline.zadd(users_key, timestamp.to_f, user.id) - # We also mark for expiry when a session key is created (first user joins), - # because some users might never actively leave a session and the key could - # therefore become stale, w/o us noticing. - reset_session_expiry(pipeline) + # We also mark for expiry when a session key is created (first user joins), + # because some users might never actively leave a session and the key could + # therefore become stale, w/o us noticing. + reset_session_expiry(pipeline) + end end end @@ -83,26 +85,33 @@ class AwarenessSession # rubocop:disable Gitlab/NamespacedClass user_key = user_sessions_key(user.id) with_redis do |redis| - redis.pipelined do |pipeline| - pipeline.srem?(user_key, id_i) - pipeline.zrem(users_key, user.id) + Gitlab::Instrumentation::RedisClusterValidator.allow_cross_slot_commands do + redis.pipelined do |pipeline| + pipeline.srem?(user_key, id_i) + pipeline.zrem(users_key, user.id) + end end # cleanup orphan sessions and users # # this needs to be a second pipeline due to the delete operations being # dependent on the result of the cardinality checks - user_sessions_count, session_users_count = redis.pipelined do |pipeline| - pipeline.scard(user_key) - pipeline.zcard(users_key) - end + user_sessions_count, session_users_count = + Gitlab::Instrumentation::RedisClusterValidator.allow_cross_slot_commands do + redis.pipelined do |pipeline| + pipeline.scard(user_key) + pipeline.zcard(users_key) + end + end - redis.pipelined do |pipeline| - pipeline.del(user_key) unless user_sessions_count > 0 + Gitlab::Instrumentation::RedisClusterValidator.allow_cross_slot_commands do + redis.pipelined do |pipeline| + pipeline.del(user_key) unless user_sessions_count > 0 - unless session_users_count > 0 - pipeline.del(users_key) - @id = nil + unless session_users_count > 0 + pipeline.del(users_key) + @id = nil + end end end end diff --git a/app/models/incident_management/timeline_event_tag.rb b/app/models/incident_management/timeline_event_tag.rb index 75d23f05e4e..d1e3fbc2a6a 100644 --- a/app/models/incident_management/timeline_event_tag.rb +++ b/app/models/incident_management/timeline_event_tag.rb @@ -20,7 +20,7 @@ module IncidentManagement validates :name, uniqueness: { scope: :project_id, case_sensitive: false } validates :name, length: { maximum: 255 } - scope :by_names, -> (tag_names) { where(name: tag_names) } + scope :by_names, -> (tag_names) { where('lower(name) in (?)', tag_names.map(&:downcase)) } def self.pluck_names pluck(:name) diff --git a/app/models/instance_metadata.rb b/app/models/instance_metadata.rb index 6cac78178e0..47460c85671 100644 --- a/app/models/instance_metadata.rb +++ b/app/models/instance_metadata.rb @@ -1,11 +1,12 @@ # frozen_string_literal: true class InstanceMetadata - attr_reader :version, :revision, :kas + attr_reader :version, :revision, :kas, :enterprise - def initialize(version: Gitlab::VERSION, revision: Gitlab.revision) + def initialize(version: Gitlab::VERSION, revision: Gitlab.revision, enterprise: Gitlab.ee?) @version = version @revision = revision @kas = ::InstanceMetadata::Kas.new + @enterprise = enterprise end end diff --git a/app/services/incident_management/timeline_events/create_service.rb b/app/services/incident_management/timeline_events/create_service.rb index e8ec8aa6525..71ff5b64515 100644 --- a/app/services/incident_management/timeline_events/create_service.rb +++ b/app/services/incident_management/timeline_events/create_service.rb @@ -5,6 +5,7 @@ module IncidentManagement DEFAULT_ACTION = 'comment' DEFAULT_EDITABLE = false DEFAULT_AUTO_CREATED = false + AUTOCREATE_TAGS = [TimelineEventTag::START_TIME_TAG_NAME, TimelineEventTag::END_TIME_TAG_NAME].freeze class CreateService < TimelineEvents::BaseService def initialize(incident, user, params) @@ -94,6 +95,10 @@ module IncidentManagement editable: params.fetch(:editable, DEFAULT_EDITABLE) } + non_existing_tags = validate_tags(project, params[:timeline_event_tag_names]) + + return error("#{_("Following tags don't exist")}: #{non_existing_tags}") unless non_existing_tags.empty? + timeline_event = IncidentManagement::TimelineEvent.new(timeline_event_params) if timeline_event.save(context: validation_context) @@ -130,8 +135,11 @@ module IncidentManagement end def create_timeline_event_tag_links(timeline_event, tag_names) - return unless params[:timeline_event_tag_names] + return unless tag_names&.any? + auto_create_predefined_tags(tag_names) + + # Refetches the tag objects to consider predefined tags as well tags = project.incident_management_timeline_event_tags.by_names(tag_names) tag_links = tags.select(:id).map do |tag| @@ -144,6 +152,30 @@ module IncidentManagement IncidentManagement::TimelineEventTagLink.insert_all(tag_links) if tag_links.any? end + + def auto_create_predefined_tags(new_tags) + new_tags = new_tags.map(&:downcase) + + tags_to_create = AUTOCREATE_TAGS.select { |tag| tag.downcase.in?(new_tags) } + + tags_to_create.each do |name| + project.incident_management_timeline_event_tags.create(name: name) + end + end + + def validate_tags(project, tag_names) + return [] unless tag_names&.any? + + start_time_tag = AUTOCREATE_TAGS[0].downcase + end_time_tag = AUTOCREATE_TAGS[1].downcase + + tag_names_downcased = tag_names.map(&:downcase) + + tags = project.incident_management_timeline_event_tags.by_names(tag_names).pluck_names.map(&:downcase) + + # remove tags from given tag_names and also remove predefined tags which can be auto created + tag_names_downcased - tags - [start_time_tag, end_time_tag] + end end end end diff --git a/app/validators/nested_attributes_duplicates_validator.rb b/app/validators/nested_attributes_duplicates_validator.rb index b60350a6311..de219c300ba 100644 --- a/app/validators/nested_attributes_duplicates_validator.rb +++ b/app/validators/nested_attributes_duplicates_validator.rb @@ -25,11 +25,11 @@ class NestedAttributesDuplicatesValidator < ActiveModel::EachValidator def validate_duplicates(record, attribute, values) child_attributes.each do |child_attribute| duplicates = values.reject(&:marked_for_destruction?).group_by(&:"#{child_attribute}").select { |_, v| v.many? }.map(&:first) - if duplicates.any? - error_message = +"have duplicate values (#{duplicates.join(", ")})" - error_message << " for #{values.first.send(options[:scope])} scope" if options[:scope] # rubocop:disable GitlabSecurity/PublicSend - record.errors.add(attribute, error_message) - end + next unless duplicates.any? + + error_message = +"have duplicate values (#{duplicates.join(", ")})" + error_message << " for #{values.first.send(options[:scope])} scope" if options[:scope] # rubocop:disable GitlabSecurity/PublicSend + record.errors.add(attribute, error_message) end end # rubocop: enable CodeReuse/ActiveRecord diff --git a/app/views/devise/confirmations/almost_there.haml b/app/views/devise/confirmations/almost_there.haml index ef19ac33a15..01f9595f35c 100644 --- a/app/views/devise/confirmations/almost_there.haml +++ b/app/views/devise/confirmations/almost_there.haml @@ -1,4 +1,4 @@ -- user_email = "(#{params[:email]})" if params[:email].present? +- user_email = "(#{params[:email]})" if Devise.email_regexp.match?(params[:email]) - request_link_start = '<a href="%{new_user_confirmation_path}">'.html_safe % { new_user_confirmation_path: new_user_confirmation_path } - request_link_end = '</a>'.html_safe - content_for :page_specific_javascripts do diff --git a/config/feature_flags/development/board_grouped_by_epic_performance.yml b/config/feature_flags/development/board_grouped_by_epic_performance.yml new file mode 100644 index 00000000000..08519f3c328 --- /dev/null +++ b/config/feature_flags/development/board_grouped_by_epic_performance.yml @@ -0,0 +1,8 @@ +--- +name: board_grouped_by_epic_performance +introduced_by_url: 'https://gitlab.com/gitlab-org/gitlab/-/merge_requests/101640' +rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/381664 +milestone: '15.6' +type: development +group: group::product planning +default_enabled: false diff --git a/config/open_api.yml b/config/open_api.yml index 7f02fd23484..ddf3d23dfc9 100644 --- a/config/open_api.yml +++ b/config/open_api.yml @@ -29,6 +29,8 @@ metadata: description: Operations related to clusters - name: container_registry description: Operations related to container registry + - name: dashboard_annotations + description: Operations related to dashboard annotations - name: dependency_proxy description: Operations to manage dependency proxy for a groups - name: deploy_keys diff --git a/config/routes.rb b/config/routes.rb index 5ebd7246e5a..27313854233 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -56,7 +56,6 @@ InitializerConnections.with_disabled_database_connections do match '/oauth/revoke' => 'oauth/tokens#revoke', via: :options match '/-/jira_connect/oauth_application_id' => 'jira_connect/cors_preflight_checks#index', via: :options - match '/-/jira_connect/subscriptions' => 'jira_connect/cors_preflight_checks#index', via: :options match '/-/jira_connect/subscriptions/:id' => 'jira_connect/cors_preflight_checks#index', via: :options match '/-/jira_connect/installations' => 'jira_connect/cors_preflight_checks#index', via: :options diff --git a/doc/api/graphql/reference/index.md b/doc/api/graphql/reference/index.md index a8e48a952c0..b1f9d6ceae1 100644 --- a/doc/api/graphql/reference/index.md +++ b/doc/api/graphql/reference/index.md @@ -15783,6 +15783,7 @@ four standard [pagination arguments](#connection-pagination-arguments): | Name | Type | Description | | ---- | ---- | ----------- | +| <a id="metadataenterprise"></a>`enterprise` | [`Boolean!`](#boolean) | Enterprise edition. | | <a id="metadatakas"></a>`kas` | [`Kas!`](#kas) | Metadata about KAS. | | <a id="metadatarevision"></a>`revision` | [`String!`](#string) | Revision. | | <a id="metadataversion"></a>`version` | [`String!`](#string) | Version. | diff --git a/doc/api/metadata.md b/doc/api/metadata.md index 3803173b0b8..c3cbae70a54 100644 --- a/doc/api/metadata.md +++ b/doc/api/metadata.md @@ -24,6 +24,7 @@ Response body attributes: | `kas.enabled` | boolean | Indicates whether KAS is enabled. | | `kas.externalUrl` | string or null | URL used by the agents to communicate with KAS. It's `null` if `kas.enabled` is `false`. | | `kas.version` | string or null | Version of KAS. It's `null` if `kas.enabled` is `false`. | +| `enterprise` | boolean | Indicates whether GitLab instance is Enterprise Edition. | Example request: @@ -41,6 +42,7 @@ Example response: "enabled": true, "externalUrl": "grpc://gitlab.example.com:8150", "version": "15.0.0" - } + }, + "enterprise": true } ``` diff --git a/doc/api/suggestions.md b/doc/api/suggestions.md index 0b6fa25c5c7..1e1f226481c 100644 --- a/doc/api/suggestions.md +++ b/doc/api/suggestions.md @@ -51,7 +51,7 @@ PUT /suggestions/batch_apply | Attribute | Type | Required | Description | | --------- | ---- | -------- | ----------- | -| `ids` | integer | yes | The ID of a suggestion | +| `ids` | integer | yes | The IDs of suggestions | | `commit_message` | string | no | A custom commit message to use instead of the default generated message or the project's default message | ```shell diff --git a/doc/development/pipelines/index.md b/doc/development/pipelines/index.md index e29d38bb22f..01bb813e794 100644 --- a/doc/development/pipelines/index.md +++ b/doc/development/pipelines/index.md @@ -458,10 +458,13 @@ We also run our test suite against PG11 upon specific database library changes i | `maintenance` scheduled pipelines for the `ruby3` branch (every odd-numbered hour), see below. | 12 (default version), 11 for DB library changes | 3.0 (coded in the branch) | | `nightly` scheduled pipelines for the `master` branch | 12 (default version), 11, 13 | 2.7 (default version) | -The pipeline configuration for the scheduled pipeline testing Ruby 3 is -stored in the `ruby3-sync` branch. The pipeline updates the `ruby3` branch -with latest `master`, and then it triggers a regular branch pipeline for -`ruby3`. Any changes in `ruby3` are only for running the pipeline. It should +There are 2 pipeline schedules used for testing Ruby 3. One is triggering a +pipeline in `ruby3-sync` branch, which updates the `ruby3` branch with latest +`master`, and no pipelines will be triggered by this push. The other schedule +is triggering a pipeline in `ruby3` 5 minutes after it, which is considered +the maintenance schedule to run test suites and update cache. + +Any changes in `ruby3` are only for running the pipeline. It should never be merged back to `master`. Any other Ruby 3 changes should go into `master` directly, which should be compatible with Ruby 2.7. diff --git a/doc/user/product_analytics/index.md b/doc/user/product_analytics/index.md new file mode 100644 index 00000000000..8e340fff32a --- /dev/null +++ b/doc/user/product_analytics/index.md @@ -0,0 +1,48 @@ +--- +stage: Analyze +group: Product Analytics +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 +--- + +# Product analytics **(ULTIMATE)** **Alpha** + +> Introduced in GitLab 15.4 [with a flag](../../administration/feature_flags.md) named `cube_api_proxy`. Disabled by default. + +FLAG: +On self-managed GitLab, by default this feature is not available. To make it available per project or for your entire instance, ask an administrator to [enable the feature flag](../../administration/feature_flags.md) named `cube_api_proxy`. +On GitLab.com, this feature is not available. +This feature is not ready for production use. + +## Overview + +You can view the [product category](https://about.gitlab.com/direction/analytics/product-analytics/) page for more information about our direction. This page is a work in progress and will be updated as we add more features. + +## Product analytics dashboards + +Each project can define an unlimited number of dashboards. These dashboards are defined using our YAML schema and stored +in the `.gitlab/product_analytics/dashboards/` directory. The name of the file is the name of the dashboard, and visualizations are shared across dashboards.. + +Project maintainers can enforce approval rules on dashboard changes, and dashboards can be versioned in source control. + +### Define a dashboard + +To define a dashboard: + +1. In `.gitlab/product_analytics/dashboards/`, create a directory named like the dashboard. Each dashboard should have its own directory. +1. In the new directory, create a `.yaml` file with the same name as the directory. This file contains the dashboard definition, and must conform to the JSON schema defined in `ee/app/validators/json_schemas/product_analytics_dashboard.json`. +1. In the `.gitlab/product_analytics/dashboards/visualizations/` directory, create a `yaml` file. This file defines the visualization type for the dashboard, and must conform to the schema in +`ee/app/validators/json_schemas/product_analytics_visualization.json`. + +The example below includes three dashboards and one visualization that applies to all dashboards. + +```plaintext +.gitlab/product_analytics/dashboards +├── conversion_funnels +│ └── conversion_funnels.yaml +├── demographic_breakdown +│ └── demographic_breakdown.yaml +├── north_star_metrics +| └── north_star_metrics.yaml +├── visualizations +│ └── example_line_chart.yaml +``` diff --git a/lib/api/api.rb b/lib/api/api.rb index c0d5e84f5b1..94dfb7f598c 100644 --- a/lib/api/api.rb +++ b/lib/api/api.rb @@ -224,6 +224,7 @@ module API mount ::API::MergeRequestApprovals mount ::API::MergeRequestDiffs mount ::API::Metadata + mount ::API::Metrics::Dashboard::Annotations mount ::API::Metrics::UserStarredDashboards mount ::API::PackageFiles mount ::API::PersonalAccessTokens::SelfInformation @@ -296,7 +297,6 @@ module API mount ::API::MavenPackages mount ::API::Members mount ::API::MergeRequests - mount ::API::Metrics::Dashboard::Annotations mount ::API::Namespaces mount ::API::Notes mount ::API::NotificationSettings diff --git a/lib/api/entities/metadata.rb b/lib/api/entities/metadata.rb index 1e04b5c5982..7dfcad2ccab 100644 --- a/lib/api/entities/metadata.rb +++ b/lib/api/entities/metadata.rb @@ -10,6 +10,7 @@ module API expose :externalUrl, documentation: { type: 'string', example: 'grpc://gitlab.example.com:8150' } expose :version, documentation: { type: 'string', example: '15.0.0' } end + expose :enterprise, documentation: { type: 'boolean' } end end end diff --git a/lib/api/entities/metrics/dashboard/annotation.rb b/lib/api/entities/metrics/dashboard/annotation.rb index 66bd09d84f9..08d1a333259 100644 --- a/lib/api/entities/metrics/dashboard/annotation.rb +++ b/lib/api/entities/metrics/dashboard/annotation.rb @@ -5,13 +5,13 @@ module API module Metrics module Dashboard class Annotation < Grape::Entity - expose :id - expose :starting_at - expose :ending_at - expose :dashboard_path - expose :description - expose :environment_id - expose :cluster_id + expose :id, documentation: { type: 'integer', example: 4 } + expose :starting_at, documentation: { type: 'dateTime', example: '2016-04-08T03:45:40.000Z' } + expose :ending_at, documentation: { type: 'dateTime', example: '2016-08-08T09:00:00.000Z' } + expose :dashboard_path, documentation: { type: 'string', example: '.gitlab/dashboards/custom_metrics.yml' } + expose :description, documentation: { type: 'string', example: 'annotation description' } + expose :environment_id, documentation: { type: 'integer', example: 1 } + expose :cluster_id, documentation: { type: 'integer', example: 2 } end end end diff --git a/lib/api/metadata.rb b/lib/api/metadata.rb index 2fdb97f98ef..788d9843c63 100644 --- a/lib/api/metadata.rb +++ b/lib/api/metadata.rb @@ -23,6 +23,7 @@ module API externalUrl version } + enterprise } } EOF diff --git a/lib/api/metrics/dashboard/annotations.rb b/lib/api/metrics/dashboard/annotations.rb index 478adcdce70..6ba154191be 100644 --- a/lib/api/metrics/dashboard/annotations.rb +++ b/lib/api/metrics/dashboard/annotations.rb @@ -7,8 +7,15 @@ module API feature_category :metrics urgency :low - desc 'Create a new monitoring dashboard annotation' do + desc 'Create a new annotation' do + detail 'Creates a new monitoring dashboard annotation' success Entities::Metrics::Dashboard::Annotation + failure [ + { code: 400, message: 'Bad Request' }, + { code: 401, message: 'Unauthorized' }, + { code: 404, message: 'Not Found' } + ] + tags %w[dashboard_annotations] end ANNOTATIONS_SOURCES = [ @@ -20,12 +27,16 @@ module API resource annotations_source[:resource] do params do requires :starting_at, type: DateTime, - desc: 'Date time indicating starting moment to which the annotation relates.' + desc: 'Date time string, ISO 8601 formatted, such as 2016-03-11T03:45:40Z.'\ + 'Timestamp marking start point of annotation.' optional :ending_at, type: DateTime, - desc: 'Date time indicating ending moment to which the annotation relates.' + desc: 'Date time string, ISO 8601 formatted, such as 2016-03-11T03:45:40Z.'\ + 'Timestamp marking end point of annotation.'\ + 'When not supplied, an annotation displays as a single event at the start point.' requires :dashboard_path, type: String, coerce_with: -> (val) { CGI.unescape(val) }, - desc: 'The path to a file defining the dashboard on which the annotation should be added' - requires :description, type: String, desc: 'The description of the annotation' + desc: 'ID of the dashboard which needs to be annotated.'\ + 'Treated as a CGI-escaped path, and automatically un-escaped.' + requires :description, type: String, desc: 'Description of the annotation.' end post ':id/metrics_dashboard/annotations' do @@ -33,7 +44,9 @@ module API forbidden! unless can?(current_user, :create_metrics_dashboard_annotation, annotations_source_object) - create_service_params = declared(params).merge(annotations_source[:create_service_param_key] => annotations_source_object) + create_service_params = declared(params).merge( + annotations_source[:create_service_param_key] => annotations_source_object + ) result = ::Metrics::Dashboard::Annotations::CreateService.new(current_user, create_service_params).execute diff --git a/lib/backup/manager.rb b/lib/backup/manager.rb index 902eb8f6659..a8b3c12a2a2 100644 --- a/lib/backup/manager.rb +++ b/lib/backup/manager.rb @@ -396,13 +396,13 @@ module Backup timestamp = matched[1].to_i - if Time.at(timestamp) < (Time.now - keep_time) - begin - FileUtils.rm(file) - removed += 1 - rescue StandardError => e - puts_time "Deleting #{file} failed: #{e.message}".color(:red) - end + next unless Time.at(timestamp) < (Time.now - keep_time) + + begin + FileUtils.rm(file) + removed += 1 + rescue StandardError => e + puts_time "Deleting #{file} failed: #{e.message}".color(:red) end end end diff --git a/lib/banzai/filter/external_link_filter.rb b/lib/banzai/filter/external_link_filter.rb index d1a0f8e5859..0a76c84efe5 100644 --- a/lib/banzai/filter/external_link_filter.rb +++ b/lib/banzai/filter/external_link_filter.rb @@ -22,12 +22,12 @@ module Banzai addressable_uri = nil end - unless internal_url?(addressable_uri) - punycode_autolink_node!(addressable_uri, node) - sanitize_link_text!(node) - add_malicious_tooltip!(addressable_uri, node) - add_nofollow!(addressable_uri, node) - end + next if internal_url?(addressable_uri) + + punycode_autolink_node!(addressable_uri, node) + sanitize_link_text!(node) + add_malicious_tooltip!(addressable_uri, node) + add_nofollow!(addressable_uri, node) end doc diff --git a/lib/banzai/filter/footnote_filter.rb b/lib/banzai/filter/footnote_filter.rb index f5c4b788ad8..f10efdccdf1 100644 --- a/lib/banzai/filter/footnote_filter.rb +++ b/lib/banzai/filter/footnote_filter.rb @@ -44,25 +44,25 @@ module Banzai node_xpath = Gitlab::Utils::Nokogiri.css_to_xpath(css) footnote_node = doc.at_xpath(node_xpath) - if footnote_node || modified_footnotes[ref_num] - link_node[:href] += rand_suffix - link_node[:id] += rand_suffix + next unless footnote_node || modified_footnotes[ref_num] - # Sanitization stripped off class - add it back in - link_node.parent.append_class('footnote-ref') + link_node[:href] += rand_suffix + link_node[:id] += rand_suffix - unless modified_footnotes[ref_num] - footnote_node[:id] += rand_suffix - backref_node = footnote_node.at_css("a[href=\"##{fnref_id(ref_num)}\"]") + # Sanitization stripped off class - add it back in + link_node.parent.append_class('footnote-ref') - if backref_node - backref_node[:href] += rand_suffix - backref_node.append_class('footnote-backref') - end + next if modified_footnotes[ref_num] - modified_footnotes[ref_num] = true - end + footnote_node[:id] += rand_suffix + backref_node = footnote_node.at_css("a[href=\"##{fnref_id(ref_num)}\"]") + + if backref_node + backref_node[:href] += rand_suffix + backref_node.append_class('footnote-backref') end + + modified_footnotes[ref_num] = true end doc diff --git a/lib/banzai/filter/kroki_filter.rb b/lib/banzai/filter/kroki_filter.rb index 0ce70843675..26f42c6b194 100644 --- a/lib/banzai/filter/kroki_filter.rb +++ b/lib/banzai/filter/kroki_filter.rb @@ -32,16 +32,16 @@ module Banzai img_tag = Nokogiri::HTML::DocumentFragment.parse(%(<img src="#{image_src}" />)) img_tag = img_tag.children.first - unless img_tag.nil? - lazy_load = diagram_src.length > MAX_CHARACTER_LIMIT - img_tag.set_attribute('hidden', '') if lazy_load - img_tag.set_attribute('class', 'js-render-kroki') + next if img_tag.nil? - img_tag.set_attribute('data-diagram', diagram_type) - img_tag.set_attribute('data-diagram-src', "data:text/plain;base64,#{Base64.strict_encode64(diagram_src)}") + lazy_load = diagram_src.length > MAX_CHARACTER_LIMIT + img_tag.set_attribute('hidden', '') if lazy_load + img_tag.set_attribute('class', 'js-render-kroki') - node.parent.replace(img_tag) - end + img_tag.set_attribute('data-diagram', diagram_type) + img_tag.set_attribute('data-diagram-src', "data:text/plain;base64,#{Base64.strict_encode64(diagram_src)}") + + node.parent.replace(img_tag) end doc diff --git a/lib/banzai/filter/math_filter.rb b/lib/banzai/filter/math_filter.rb index ac009008040..1d854d6599b 100644 --- a/lib/banzai/filter/math_filter.rb +++ b/lib/banzai/filter/math_filter.rb @@ -107,19 +107,18 @@ module Banzai # We need a sibling before and after. # They should end and start with $ respectively. - if closing && opening && - closing.text? && opening.text? && - closing.content.first == DOLLAR_SIGN && - opening.content.last == DOLLAR_SIGN - - code[:class] = MATH_CLASSES - code[STYLE_ATTRIBUTE] = 'inline' - closing.content = closing.content[1..] - opening.content = opening.content[0..-2] - - @nodes_count += 1 - break if @nodes_count >= RENDER_NODES_LIMIT - end + next unless closing && opening && + closing.text? && opening.text? && + closing.content.first == DOLLAR_SIGN && + opening.content.last == DOLLAR_SIGN + + code[:class] = MATH_CLASSES + code[STYLE_ATTRIBUTE] = 'inline' + closing.content = closing.content[1..] + opening.content = opening.content[0..-2] + + @nodes_count += 1 + break if @nodes_count >= RENDER_NODES_LIMIT end end diff --git a/lib/banzai/filter/plantuml_filter.rb b/lib/banzai/filter/plantuml_filter.rb index 82f6247cf03..6a1fa64fb76 100644 --- a/lib/banzai/filter/plantuml_filter.rb +++ b/lib/banzai/filter/plantuml_filter.rb @@ -17,12 +17,12 @@ module Banzai img_tag = Nokogiri::HTML::DocumentFragment.parse( Asciidoctor::PlantUml::Processor.plantuml_content(node.content, {})).css('img').first - unless img_tag.nil? - img_tag.set_attribute('data-diagram', 'plantuml') - img_tag.set_attribute('data-diagram-src', "data:text/plain;base64,#{Base64.strict_encode64(node.content)}") + next if img_tag.nil? - node.parent.replace(img_tag) - end + img_tag.set_attribute('data-diagram', 'plantuml') + img_tag.set_attribute('data-diagram-src', "data:text/plain;base64,#{Base64.strict_encode64(node.content)}") + + node.parent.replace(img_tag) end doc diff --git a/lib/banzai/filter/table_of_contents_filter.rb b/lib/banzai/filter/table_of_contents_filter.rb index 1c794a81d9d..d76009d08e1 100644 --- a/lib/banzai/filter/table_of_contents_filter.rb +++ b/lib/banzai/filter/table_of_contents_filter.rb @@ -33,17 +33,17 @@ module Banzai header_root = current_header = HeaderNode.new doc.xpath(XPATH).each do |node| - if header_content = node.children.first - id = string_to_anchor(node.text[0...255]) + next unless header_content = node.children.first - uniq = headers[id] > 0 ? "-#{headers[id]}" : '' - headers[id] += 1 - href = "#{id}#{uniq}" + id = string_to_anchor(node.text[0...255]) - current_header = HeaderNode.new(node: node, href: href, previous_header: current_header) + uniq = headers[id] > 0 ? "-#{headers[id]}" : '' + headers[id] += 1 + href = "#{id}#{uniq}" - header_content.add_previous_sibling(anchor_tag(href)) - end + current_header = HeaderNode.new(node: node, href: href, previous_header: current_header) + + header_content.add_previous_sibling(anchor_tag(href)) end push_toc(header_root.children, root: true) diff --git a/lib/gitlab/background_migration/encrypt_static_object_token.rb b/lib/gitlab/background_migration/encrypt_static_object_token.rb index e1805d40bab..961dea028c9 100644 --- a/lib/gitlab/background_migration/encrypt_static_object_token.rb +++ b/lib/gitlab/background_migration/encrypt_static_object_token.rb @@ -40,8 +40,9 @@ module Gitlab encrypted_tokens_sql = user_encrypted_tokens.compact.map { |(id, token)| "(#{id}, '#{token}')" }.join(',') - if user_encrypted_tokens.present? - User.connection.execute(<<~SQL) + next unless user_encrypted_tokens.present? + + User.connection.execute(<<~SQL) WITH cte(cte_id, cte_token) AS #{::Gitlab::Database::AsWithMaterialized.materialized_if_supported} ( SELECT * FROM (VALUES #{encrypted_tokens_sql}) AS t (id, token) @@ -50,8 +51,7 @@ module Gitlab SET static_object_token_encrypted = cte_token FROM cte WHERE cte_id = id - SQL - end + SQL end mark_job_as_succeeded(start_id, end_id) diff --git a/lib/gitlab/cache/import/caching.rb b/lib/gitlab/cache/import/caching.rb index 1646c9fbfef..7fec6584ba3 100644 --- a/lib/gitlab/cache/import/caching.rb +++ b/lib/gitlab/cache/import/caching.rb @@ -161,13 +161,15 @@ module Gitlab # timeout - The time after which the cache key should expire. def self.write_multiple(mapping, key_prefix: nil, timeout: TIMEOUT) with_redis do |redis| - redis.pipelined do |multi| - mapping.each do |raw_key, value| - key = cache_key_for("#{key_prefix}#{raw_key}") + Gitlab::Instrumentation::RedisClusterValidator.allow_cross_slot_commands do + redis.pipelined do |multi| + mapping.each do |raw_key, value| + key = cache_key_for("#{key_prefix}#{raw_key}") - validate_redis_value!(value) + validate_redis_value!(value) - multi.set(key, value, ex: timeout) + multi.set(key, value, ex: timeout) + end end end end diff --git a/lib/gitlab/database.rb b/lib/gitlab/database.rb index 2456a5dd68e..04cf056199c 100644 --- a/lib/gitlab/database.rb +++ b/lib/gitlab/database.rb @@ -217,13 +217,13 @@ module Gitlab Rails.application.config.paths['db'].each do |db_path| path = Rails.root.join(db_path, 'post_migrate').to_s - unless Rails.application.config.paths['db/migrate'].include? path - Rails.application.config.paths['db/migrate'] << path + next if Rails.application.config.paths['db/migrate'].include? path - # Rails memoizes migrations at certain points where it won't read the above - # path just yet. As such we must also update the following list of paths. - ActiveRecord::Migrator.migrations_paths << path - end + Rails.application.config.paths['db/migrate'] << path + + # Rails memoizes migrations at certain points where it won't read the above + # path just yet. As such we must also update the following list of paths. + ActiveRecord::Migrator.migrations_paths << path end end diff --git a/lib/gitlab/discussions_diff/highlight_cache.rb b/lib/gitlab/discussions_diff/highlight_cache.rb index 62f7f268f07..14cb773251b 100644 --- a/lib/gitlab/discussions_diff/highlight_cache.rb +++ b/lib/gitlab/discussions_diff/highlight_cache.rb @@ -15,11 +15,13 @@ module Gitlab # mapping - Write multiple cache values at once def write_multiple(mapping) with_redis do |redis| - redis.multi do |multi| - mapping.each do |raw_key, value| - key = cache_key_for(raw_key) + Gitlab::Instrumentation::RedisClusterValidator.allow_cross_slot_commands do + redis.multi do |multi| + mapping.each do |raw_key, value| + key = cache_key_for(raw_key) - multi.set(key, gzip_compress(value.to_json), ex: EXPIRATION) + multi.set(key, gzip_compress(value.to_json), ex: EXPIRATION) + end end end end diff --git a/lib/gitlab/etag_caching/store.rb b/lib/gitlab/etag_caching/store.rb index 437d577e70e..bc97c88ce85 100644 --- a/lib/gitlab/etag_caching/store.rb +++ b/lib/gitlab/etag_caching/store.rb @@ -15,10 +15,12 @@ module Gitlab def touch(*keys, only_if_missing: false) etags = keys.map { generate_etag } - Gitlab::Redis::SharedState.with do |redis| - redis.pipelined do |pipeline| - keys.each_with_index do |key, i| - pipeline.set(redis_shared_state_key(key), etags[i], ex: EXPIRY_TIME, nx: only_if_missing) + Gitlab::Instrumentation::RedisClusterValidator.allow_cross_slot_commands do + Gitlab::Redis::SharedState.with do |redis| + redis.pipelined do |pipeline| + keys.each_with_index do |key, i| + pipeline.set(redis_shared_state_key(key), etags[i], ex: EXPIRY_TIME, nx: only_if_missing) + end end end end diff --git a/lib/gitlab/gitaly_client/blob_service.rb b/lib/gitlab/gitaly_client/blob_service.rb index 3b08a833aeb..6d87c3329d7 100644 --- a/lib/gitlab/gitaly_client/blob_service.rb +++ b/lib/gitlab/gitaly_client/blob_service.rb @@ -4,9 +4,12 @@ module Gitlab module GitalyClient class BlobService include Gitlab::EncodingHelper + include WithFeatureFlagActors def initialize(repository) @gitaly_repo = repository.gitaly_repository + + self.repository_actor = repository end def get_blob(oid:, limit:) @@ -15,7 +18,7 @@ module Gitlab oid: oid, limit: limit ) - response = GitalyClient.call(@gitaly_repo.storage_name, :blob_service, :get_blob, request, timeout: GitalyClient.fast_timeout) + response = gitaly_client_call(@gitaly_repo.storage_name, :blob_service, :get_blob, request, timeout: GitalyClient.fast_timeout) consume_blob_response(response) end @@ -35,7 +38,7 @@ module Gitlab GitalyClient.medium_timeout end - response = GitalyClient.call(@gitaly_repo.storage_name, :blob_service, :list_blobs, request, timeout: timeout) + response = gitaly_client_call(@gitaly_repo.storage_name, :blob_service, :list_blobs, request, timeout: timeout) GitalyClient::BlobsStitcher.new(GitalyClient::ListBlobsAdapter.new(response)) end @@ -47,7 +50,7 @@ module Gitlab blob_ids: blob_ids ) - response = GitalyClient.call(@gitaly_repo.storage_name, :blob_service, :get_lfs_pointers, request, timeout: GitalyClient.medium_timeout) + response = gitaly_client_call(@gitaly_repo.storage_name, :blob_service, :get_lfs_pointers, request, timeout: GitalyClient.medium_timeout) map_lfs_pointers(response) end @@ -64,7 +67,7 @@ module Gitlab limit: limit ) - response = GitalyClient.call( + response = gitaly_client_call( @gitaly_repo.storage_name, :blob_service, :get_blobs, @@ -87,7 +90,7 @@ module Gitlab limit: limit ) - response = GitalyClient.call( + response = gitaly_client_call( @gitaly_repo.storage_name, :blob_service, :get_blobs, @@ -107,7 +110,7 @@ module Gitlab GitalyClient.medium_timeout end - response = GitalyClient.call( + response = gitaly_client_call( @gitaly_repo.storage_name, :blob_service, rpc, @@ -123,7 +126,7 @@ module Gitlab revisions: [encode_binary("--all")] ) - response = GitalyClient.call(@gitaly_repo.storage_name, :blob_service, :list_lfs_pointers, request, timeout: GitalyClient.medium_timeout) + response = gitaly_client_call(@gitaly_repo.storage_name, :blob_service, :list_lfs_pointers, request, timeout: GitalyClient.medium_timeout) map_lfs_pointers(response) end diff --git a/lib/gitlab/gitaly_client/cleanup_service.rb b/lib/gitlab/gitaly_client/cleanup_service.rb index 649aaa46362..3c2c41a244e 100644 --- a/lib/gitlab/gitaly_client/cleanup_service.rb +++ b/lib/gitlab/gitaly_client/cleanup_service.rb @@ -3,6 +3,8 @@ module Gitlab module GitalyClient class CleanupService + include WithFeatureFlagActors + attr_reader :repository, :gitaly_repo, :storage # 'repository' is a Gitlab::Git::Repository @@ -10,10 +12,12 @@ module Gitlab @repository = repository @gitaly_repo = repository.gitaly_repository @storage = repository.storage + + self.repository_actor = repository end def apply_bfg_object_map_stream(io, &blk) - response = GitalyClient.call( + response = gitaly_client_call( storage, :cleanup_service, :apply_bfg_object_map_stream, diff --git a/lib/gitlab/gitaly_client/conflicts_service.rb b/lib/gitlab/gitaly_client/conflicts_service.rb index 982454b117e..38f648ccc31 100644 --- a/lib/gitlab/gitaly_client/conflicts_service.rb +++ b/lib/gitlab/gitaly_client/conflicts_service.rb @@ -4,6 +4,7 @@ module Gitlab module GitalyClient class ConflictsService include Gitlab::EncodingHelper + include WithFeatureFlagActors MAX_MSG_SIZE = 128.kilobytes.freeze @@ -12,6 +13,8 @@ module Gitlab @repository = repository @our_commit_oid = our_commit_oid @their_commit_oid = their_commit_oid + + self.repository_actor = repository end def list_conflict_files(allow_tree_conflicts: false) @@ -21,7 +24,7 @@ module Gitlab their_commit_oid: @their_commit_oid, allow_tree_conflicts: allow_tree_conflicts ) - response = GitalyClient.call(@repository.storage, :conflicts_service, :list_conflict_files, request, timeout: GitalyClient.long_timeout) + response = gitaly_client_call(@repository.storage, :conflicts_service, :list_conflict_files, request, timeout: GitalyClient.long_timeout) GitalyClient::ConflictFilesStitcher.new(response, @gitaly_repo) end @@ -50,7 +53,7 @@ module Gitlab end end - response = GitalyClient.call(@repository.storage, :conflicts_service, :resolve_conflicts, req_enum, remote_storage: target_repository.storage, timeout: GitalyClient.long_timeout) + response = gitaly_client_call(@repository.storage, :conflicts_service, :resolve_conflicts, req_enum, remote_storage: target_repository.storage, timeout: GitalyClient.long_timeout) if response.resolution_error.present? raise Gitlab::Git::Conflict::Resolver::ResolutionError, response.resolution_error diff --git a/lib/gitlab/gitaly_client/object_pool_service.rb b/lib/gitlab/gitaly_client/object_pool_service.rb index 786ef0ebebe..e07bf3fbccc 100644 --- a/lib/gitlab/gitaly_client/object_pool_service.rb +++ b/lib/gitlab/gitaly_client/object_pool_service.rb @@ -3,6 +3,8 @@ module Gitlab module GitalyClient class ObjectPoolService + include WithFeatureFlagActors + attr_reader :object_pool, :storage def initialize(object_pool) @@ -15,8 +17,10 @@ module Gitlab object_pool: object_pool, origin: repository.gitaly_repository) - GitalyClient.call(storage, :object_pool_service, :create_object_pool, - request, timeout: GitalyClient.medium_timeout) + GitalyClient.with_feature_flag_actors(**gitaly_feature_flag_actors(repository)) do + GitalyClient.call(storage, :object_pool_service, :create_object_pool, + request, timeout: GitalyClient.medium_timeout) + end end def delete @@ -32,8 +36,10 @@ module Gitlab repository: repository.gitaly_repository ) - GitalyClient.call(storage, :object_pool_service, :link_repository_to_object_pool, - request, timeout: GitalyClient.fast_timeout) + GitalyClient.with_feature_flag_actors(**gitaly_feature_flag_actors(repository)) do + GitalyClient.call(storage, :object_pool_service, :link_repository_to_object_pool, + request, timeout: GitalyClient.fast_timeout) + end end def fetch(repository) @@ -42,8 +48,10 @@ module Gitlab origin: repository.gitaly_repository ) - GitalyClient.call(storage, :object_pool_service, :fetch_into_object_pool, - request, timeout: GitalyClient.long_timeout) + GitalyClient.with_feature_flag_actors(**gitaly_feature_flag_actors(repository)) do + GitalyClient.call(storage, :object_pool_service, :fetch_into_object_pool, + request, timeout: GitalyClient.long_timeout) + end end end end diff --git a/lib/gitlab/gitaly_client/operation_service.rb b/lib/gitlab/gitaly_client/operation_service.rb index 298162a5e2c..2312def5efc 100644 --- a/lib/gitlab/gitaly_client/operation_service.rb +++ b/lib/gitlab/gitaly_client/operation_service.rb @@ -4,12 +4,15 @@ module Gitlab module GitalyClient class OperationService include Gitlab::EncodingHelper + include WithFeatureFlagActors MAX_MSG_SIZE = 128.kilobytes.freeze def initialize(repository) @gitaly_repo = repository.gitaly_repository @repository = repository + + self.repository_actor = repository end def rm_tag(tag_name, user) @@ -19,7 +22,7 @@ module Gitlab user: Gitlab::Git::User.from_gitlab(user).to_gitaly ) - response = GitalyClient.call(@repository.storage, :operation_service, :user_delete_tag, request, timeout: GitalyClient.long_timeout) + response = gitaly_client_call(@repository.storage, :operation_service, :user_delete_tag, request, timeout: GitalyClient.long_timeout) if pre_receive_error = response.pre_receive_error.presence raise Gitlab::Git::PreReceiveError, pre_receive_error @@ -36,7 +39,7 @@ module Gitlab timestamp: Google::Protobuf::Timestamp.new(seconds: Time.now.utc.to_i) ) - response = GitalyClient.call(@repository.storage, :operation_service, :user_create_tag, request, timeout: GitalyClient.long_timeout) + response = gitaly_client_call(@repository.storage, :operation_service, :user_create_tag, request, timeout: GitalyClient.long_timeout) if pre_receive_error = response.pre_receive_error.presence raise Gitlab::Git::PreReceiveError, pre_receive_error elsif response.exists @@ -73,7 +76,7 @@ module Gitlab user: Gitlab::Git::User.from_gitlab(user).to_gitaly, start_point: encode_binary(start_point) ) - response = GitalyClient.call(@repository.storage, :operation_service, + response = gitaly_client_call(@repository.storage, :operation_service, :user_create_branch, request, timeout: GitalyClient.long_timeout) if response.pre_receive_error.present? @@ -110,7 +113,7 @@ module Gitlab oldrev: encode_binary(oldrev) ) - response = GitalyClient.call(@repository.storage, :operation_service, + response = gitaly_client_call(@repository.storage, :operation_service, :user_update_branch, request, timeout: GitalyClient.long_timeout) if pre_receive_error = response.pre_receive_error.presence @@ -125,7 +128,7 @@ module Gitlab user: Gitlab::Git::User.from_gitlab(user).to_gitaly ) - response = GitalyClient.call(@repository.storage, :operation_service, + response = gitaly_client_call(@repository.storage, :operation_service, :user_delete_branch, request, timeout: GitalyClient.long_timeout) if pre_receive_error = response.pre_receive_error.presence @@ -156,7 +159,7 @@ module Gitlab timestamp: Google::Protobuf::Timestamp.new(seconds: Time.now.utc.to_i) ) - response = GitalyClient.call(@repository.storage, :operation_service, + response = gitaly_client_call(@repository.storage, :operation_service, :user_merge_to_ref, request, timeout: GitalyClient.long_timeout) response.commit_id @@ -164,7 +167,7 @@ module Gitlab def user_merge_branch(user, source_sha, target_branch, message) request_enum = QueueEnumerator.new - response_enum = GitalyClient.call( + response_enum = gitaly_client_call( @repository.storage, :operation_service, :user_merge_branch, @@ -225,7 +228,7 @@ module Gitlab branch: encode_binary(target_branch) ) - response = GitalyClient.call( + response = gitaly_client_call( @repository.storage, :operation_service, :user_ff_branch, @@ -268,7 +271,7 @@ module Gitlab request_enum = QueueEnumerator.new rebase_sha = nil - response_enum = GitalyClient.call( + response_enum = gitaly_client_call( @repository.storage, :operation_service, :user_rebase_confirmable, @@ -334,7 +337,7 @@ module Gitlab timestamp: Google::Protobuf::Timestamp.new(seconds: time.to_i) ) - response = GitalyClient.call( + response = gitaly_client_call( @repository.storage, :operation_service, :user_squash, @@ -376,7 +379,7 @@ module Gitlab timestamp: Google::Protobuf::Timestamp.new(seconds: Time.now.utc.to_i) ) - response = GitalyClient.call( + response = gitaly_client_call( @repository.storage, :operation_service, :user_update_submodule, @@ -422,7 +425,7 @@ module Gitlab end end - response = GitalyClient.call( + response = gitaly_client_call( @repository.storage, :operation_service, :user_commit_files, req_enum, timeout: GitalyClient.long_timeout, remote_storage: start_repository&.storage) @@ -473,7 +476,7 @@ module Gitlab end end - response = GitalyClient.call(@repository.storage, :operation_service, + response = gitaly_client_call(@repository.storage, :operation_service, :user_apply_patch, chunks, timeout: GitalyClient.long_timeout) Gitlab::Git::OperationService::BranchUpdate.from_gitaly(response.branch_update) @@ -509,7 +512,7 @@ module Gitlab dry_run: dry_run ) - response = GitalyClient.call( + response = gitaly_client_call( @repository.storage, :operation_service, :"user_#{rpc}", diff --git a/lib/gitlab/gitaly_client/praefect_info_service.rb b/lib/gitlab/gitaly_client/praefect_info_service.rb index 127f8cfbdf6..b565898acf8 100644 --- a/lib/gitlab/gitaly_client/praefect_info_service.rb +++ b/lib/gitlab/gitaly_client/praefect_info_service.rb @@ -3,16 +3,20 @@ module Gitlab module GitalyClient class PraefectInfoService + include WithFeatureFlagActors + def initialize(repository) @repository = repository @gitaly_repo = repository.gitaly_repository @storage = repository.storage + + self.repository_actor = repository end def replicas request = Gitaly::RepositoryReplicasRequest.new(repository: @gitaly_repo) - GitalyClient.call(@storage, :praefect_info_service, :repository_replicas, request, timeout: GitalyClient.fast_timeout) + gitaly_client_call(@storage, :praefect_info_service, :repository_replicas, request, timeout: GitalyClient.fast_timeout) end end end diff --git a/lib/gitlab/gitaly_client/ref_service.rb b/lib/gitlab/gitaly_client/ref_service.rb index bd6ff30df98..de76ade76cb 100644 --- a/lib/gitlab/gitaly_client/ref_service.rb +++ b/lib/gitlab/gitaly_client/ref_service.rb @@ -4,6 +4,7 @@ module Gitlab module GitalyClient class RefService include Gitlab::EncodingHelper + include WithFeatureFlagActors TAGS_SORT_KEY = { 'name' => Gitaly::FindAllTagsRequest::SortBy::Key::REFNAME, @@ -21,17 +22,19 @@ module Gitlab @repository = repository @gitaly_repo = repository.gitaly_repository @storage = repository.storage + + self.repository_actor = repository end def branches request = Gitaly::FindAllBranchesRequest.new(repository: @gitaly_repo) - response = GitalyClient.call(@storage, :ref_service, :find_all_branches, request, timeout: GitalyClient.fast_timeout) + response = gitaly_client_call(@storage, :ref_service, :find_all_branches, request, timeout: GitalyClient.fast_timeout) consume_find_all_branches_response(response) end def remote_branches(remote_name) request = Gitaly::FindAllRemoteBranchesRequest.new(repository: @gitaly_repo, remote_name: remote_name) - response = GitalyClient.call(@storage, :ref_service, :find_all_remote_branches, request, timeout: GitalyClient.medium_timeout) + response = gitaly_client_call(@storage, :ref_service, :find_all_remote_branches, request, timeout: GitalyClient.medium_timeout) consume_find_all_remote_branches_response(remote_name, response) end @@ -41,25 +44,25 @@ module Gitlab merged_only: true, merged_branches: branch_names.map { |s| encode_binary(s) } ) - response = GitalyClient.call(@storage, :ref_service, :find_all_branches, request, timeout: GitalyClient.fast_timeout) + response = gitaly_client_call(@storage, :ref_service, :find_all_branches, request, timeout: GitalyClient.fast_timeout) consume_find_all_branches_response(response) end def default_branch_name request = Gitaly::FindDefaultBranchNameRequest.new(repository: @gitaly_repo) - response = GitalyClient.call(@storage, :ref_service, :find_default_branch_name, request, timeout: GitalyClient.fast_timeout) + response = gitaly_client_call(@storage, :ref_service, :find_default_branch_name, request, timeout: GitalyClient.fast_timeout) Gitlab::Git.branch_name(response.name) end def branch_names request = Gitaly::FindAllBranchNamesRequest.new(repository: @gitaly_repo) - response = GitalyClient.call(@storage, :ref_service, :find_all_branch_names, request, timeout: GitalyClient.fast_timeout) + response = gitaly_client_call(@storage, :ref_service, :find_all_branch_names, request, timeout: GitalyClient.fast_timeout) consume_refs_response(response) { |name| Gitlab::Git.branch_name(name) } end def tag_names request = Gitaly::FindAllTagNamesRequest.new(repository: @gitaly_repo) - response = GitalyClient.call(@storage, :ref_service, :find_all_tag_names, request, timeout: GitalyClient.fast_timeout) + response = gitaly_client_call(@storage, :ref_service, :find_all_tag_names, request, timeout: GitalyClient.fast_timeout) consume_refs_response(response) { |name| Gitlab::Git.tag_name(name) } end @@ -74,7 +77,7 @@ module Gitlab def local_branches(sort_by: nil, pagination_params: nil) request = Gitaly::FindLocalBranchesRequest.new(repository: @gitaly_repo, pagination_params: pagination_params) request.sort_by = sort_local_branches_by_param(sort_by) if sort_by - response = GitalyClient.call(@storage, :ref_service, :find_local_branches, request, timeout: GitalyClient.fast_timeout) + response = gitaly_client_call(@storage, :ref_service, :find_local_branches, request, timeout: GitalyClient.fast_timeout) consume_find_local_branches_response(response) end @@ -82,13 +85,13 @@ module Gitlab request = Gitaly::FindAllTagsRequest.new(repository: @gitaly_repo, pagination_params: pagination_params) request.sort_by = sort_tags_by_param(sort_by) if sort_by - response = GitalyClient.call(@storage, :ref_service, :find_all_tags, request, timeout: GitalyClient.medium_timeout) + response = gitaly_client_call(@storage, :ref_service, :find_all_tags, request, timeout: GitalyClient.medium_timeout) consume_tags_response(response) end def ref_exists?(ref_name) request = Gitaly::RefExistsRequest.new(repository: @gitaly_repo, ref: encode_binary(ref_name)) - response = GitalyClient.call(@storage, :ref_service, :ref_exists, request, timeout: GitalyClient.fast_timeout) + response = gitaly_client_call(@storage, :ref_service, :ref_exists, request, timeout: GitalyClient.fast_timeout) response.value rescue GRPC::InvalidArgument => e raise ArgumentError, e.message @@ -100,7 +103,7 @@ module Gitlab name: encode_binary(branch_name) ) - response = GitalyClient.call(@repository.storage, :ref_service, :find_branch, request, timeout: GitalyClient.medium_timeout) + response = gitaly_client_call(@repository.storage, :ref_service, :find_branch, request, timeout: GitalyClient.medium_timeout) branch = response.branch return unless branch @@ -116,7 +119,7 @@ module Gitlab tag_name: encode_binary(tag_name) ) - response = GitalyClient.call(@repository.storage, :ref_service, :find_tag, request, timeout: GitalyClient.medium_timeout) + response = gitaly_client_call(@repository.storage, :ref_service, :find_tag, request, timeout: GitalyClient.medium_timeout) tag = response.tag return unless tag @@ -140,7 +143,7 @@ module Gitlab except_with_prefix: except_with_prefixes.map { |r| encode_binary(r) } ) - response = GitalyClient.call(@repository.storage, :ref_service, :delete_refs, request, timeout: GitalyClient.medium_timeout) + response = gitaly_client_call(@repository.storage, :ref_service, :delete_refs, request, timeout: GitalyClient.medium_timeout) raise Gitlab::Git::Repository::GitError, response.git_error if response.git_error.present? rescue GRPC::BadStatus => e @@ -164,7 +167,7 @@ module Gitlab limit: limit ) - response = GitalyClient.call(@storage, :ref_service, :list_tag_names_containing_commit, request, timeout: GitalyClient.medium_timeout) + response = gitaly_client_call(@storage, :ref_service, :list_tag_names_containing_commit, request, timeout: GitalyClient.medium_timeout) consume_ref_contains_sha_response(response, :tag_names) end @@ -176,7 +179,7 @@ module Gitlab limit: limit ) - response = GitalyClient.call(@storage, :ref_service, :list_branch_names_containing_commit, request, timeout: GitalyClient.medium_timeout) + response = gitaly_client_call(@storage, :ref_service, :list_branch_names_containing_commit, request, timeout: GitalyClient.medium_timeout) consume_ref_contains_sha_response(response, :branch_names) end @@ -185,7 +188,7 @@ module Gitlab messages = Hash.new { |h, k| h[k] = +''.b } current_tag_id = nil - response = GitalyClient.call(@storage, :ref_service, :get_tag_messages, request, timeout: GitalyClient.fast_timeout) + response = gitaly_client_call(@storage, :ref_service, :get_tag_messages, request, timeout: GitalyClient.fast_timeout) response.each do |rpc_message| current_tag_id = rpc_message.tag_id if rpc_message.tag_id.present? @@ -197,7 +200,7 @@ module Gitlab def get_tag_signatures(tag_ids) request = Gitaly::GetTagSignaturesRequest.new(repository: @gitaly_repo, tag_revisions: tag_ids) - response = GitalyClient.call(@repository.storage, :ref_service, :get_tag_signatures, request, timeout: GitalyClient.fast_timeout) + response = gitaly_client_call(@repository.storage, :ref_service, :get_tag_signatures, request, timeout: GitalyClient.fast_timeout) signatures = Hash.new { |h, k| h[k] = [+''.b, +''.b] } current_tag_id = nil @@ -222,20 +225,20 @@ module Gitlab patterns: patterns ) - response = GitalyClient.call(@storage, :ref_service, :list_refs, request, timeout: GitalyClient.fast_timeout) + response = gitaly_client_call(@storage, :ref_service, :list_refs, request, timeout: GitalyClient.fast_timeout) consume_list_refs_response(response) end def pack_refs request = Gitaly::PackRefsRequest.new(repository: @gitaly_repo) - GitalyClient.call(@storage, :ref_service, :pack_refs, request, timeout: GitalyClient.long_timeout) + gitaly_client_call(@storage, :ref_service, :pack_refs, request, timeout: GitalyClient.long_timeout) end def find_refs_by_oid(oid:, limit:, ref_patterns: nil) request = Gitaly::FindRefsByOIDRequest.new(repository: @gitaly_repo, sort_field: :refname, oid: oid, limit: limit, ref_patterns: ref_patterns) - response = GitalyClient.call(@storage, :ref_service, :find_refs_by_oid, request, timeout: GitalyClient.medium_timeout) + response = gitaly_client_call(@storage, :ref_service, :find_refs_by_oid, request, timeout: GitalyClient.medium_timeout) response&.refs&.to_a end diff --git a/lib/gitlab/gitaly_client/remote_service.rb b/lib/gitlab/gitaly_client/remote_service.rb index 535b987f91c..9647cfad76e 100644 --- a/lib/gitlab/gitaly_client/remote_service.rb +++ b/lib/gitlab/gitaly_client/remote_service.rb @@ -4,6 +4,7 @@ module Gitlab module GitalyClient class RemoteService include Gitlab::EncodingHelper + include WithFeatureFlagActors MAX_MSG_SIZE = 128.kilobytes.freeze @@ -24,6 +25,8 @@ module Gitlab @repository = repository @gitaly_repo = repository.gitaly_repository @storage = repository.storage + + self.repository_actor = repository end def find_remote_root_ref(remote_url, authorization) @@ -31,7 +34,7 @@ module Gitlab remote_url: remote_url, http_authorization_header: authorization) - response = GitalyClient.call(@storage, :remote_service, + response = gitaly_client_call(@storage, :remote_service, :find_remote_root_ref, request, timeout: GitalyClient.medium_timeout) encode_utf8(response.ref) diff --git a/lib/gitlab/gitaly_client/repository_service.rb b/lib/gitlab/gitaly_client/repository_service.rb index 8934067551c..e6565bd33c2 100644 --- a/lib/gitlab/gitaly_client/repository_service.rb +++ b/lib/gitlab/gitaly_client/repository_service.rb @@ -4,6 +4,7 @@ module Gitlab module GitalyClient class RepositoryService include Gitlab::EncodingHelper + include WithFeatureFlagActors MAX_MSG_SIZE = 128.kilobytes @@ -11,57 +12,59 @@ module Gitlab @repository = repository @gitaly_repo = repository.gitaly_repository @storage = repository.storage + + self.repository_actor = repository end def exists? request = Gitaly::RepositoryExistsRequest.new(repository: @gitaly_repo) - response = GitalyClient.call(@storage, :repository_service, :repository_exists, request, timeout: GitalyClient.fast_timeout) + response = gitaly_client_call(@storage, :repository_service, :repository_exists, request, timeout: GitalyClient.fast_timeout) response.exists end def optimize_repository request = Gitaly::OptimizeRepositoryRequest.new(repository: @gitaly_repo) - GitalyClient.call(@storage, :repository_service, :optimize_repository, request, timeout: GitalyClient.long_timeout) + gitaly_client_call(@storage, :repository_service, :optimize_repository, request, timeout: GitalyClient.long_timeout) end def prune_unreachable_objects request = Gitaly::PruneUnreachableObjectsRequest.new(repository: @gitaly_repo) - GitalyClient.call(@storage, :repository_service, :prune_unreachable_objects, request, timeout: GitalyClient.long_timeout) + gitaly_client_call(@storage, :repository_service, :prune_unreachable_objects, request, timeout: GitalyClient.long_timeout) end def garbage_collect(create_bitmap, prune:) request = Gitaly::GarbageCollectRequest.new(repository: @gitaly_repo, create_bitmap: create_bitmap, prune: prune) - GitalyClient.call(@storage, :repository_service, :garbage_collect, request, timeout: GitalyClient.long_timeout) + gitaly_client_call(@storage, :repository_service, :garbage_collect, request, timeout: GitalyClient.long_timeout) end def repack_full(create_bitmap) request = Gitaly::RepackFullRequest.new(repository: @gitaly_repo, create_bitmap: create_bitmap) - GitalyClient.call(@storage, :repository_service, :repack_full, request, timeout: GitalyClient.long_timeout) + gitaly_client_call(@storage, :repository_service, :repack_full, request, timeout: GitalyClient.long_timeout) end def repack_incremental request = Gitaly::RepackIncrementalRequest.new(repository: @gitaly_repo) - GitalyClient.call(@storage, :repository_service, :repack_incremental, request, timeout: GitalyClient.long_timeout) + gitaly_client_call(@storage, :repository_service, :repack_incremental, request, timeout: GitalyClient.long_timeout) end def repository_size request = Gitaly::RepositorySizeRequest.new(repository: @gitaly_repo) - response = GitalyClient.call(@storage, :repository_service, :repository_size, request, timeout: GitalyClient.long_timeout) + response = gitaly_client_call(@storage, :repository_service, :repository_size, request, timeout: GitalyClient.long_timeout) response.size end def get_object_directory_size request = Gitaly::GetObjectDirectorySizeRequest.new(repository: @gitaly_repo) - response = GitalyClient.call(@storage, :repository_service, :get_object_directory_size, request, timeout: GitalyClient.medium_timeout) + response = gitaly_client_call(@storage, :repository_service, :get_object_directory_size, request, timeout: GitalyClient.medium_timeout) response.size end def apply_gitattributes(revision) request = Gitaly::ApplyGitattributesRequest.new(repository: @gitaly_repo, revision: encode_binary(revision)) - GitalyClient.call(@storage, :repository_service, :apply_gitattributes, request, timeout: GitalyClient.fast_timeout) + gitaly_client_call(@storage, :repository_service, :apply_gitattributes, request, timeout: GitalyClient.fast_timeout) rescue GRPC::InvalidArgument => ex raise Gitlab::Git::Repository::InvalidRef, ex end @@ -69,7 +72,7 @@ module Gitlab def info_attributes request = Gitaly::GetInfoAttributesRequest.new(repository: @gitaly_repo) - response = GitalyClient.call(@storage, :repository_service, :get_info_attributes, request, timeout: GitalyClient.fast_timeout) + response = gitaly_client_call(@storage, :repository_service, :get_info_attributes, request, timeout: GitalyClient.fast_timeout) response.each_with_object([]) do |message, attributes| attributes << message.attributes end.join @@ -103,18 +106,18 @@ module Gitlab end end - GitalyClient.call(@storage, :repository_service, :fetch_remote, request, timeout: GitalyClient.long_timeout) + gitaly_client_call(@storage, :repository_service, :fetch_remote, request, timeout: GitalyClient.long_timeout) end # rubocop: enable Metrics/ParameterLists def create_repository(default_branch = nil) request = Gitaly::CreateRepositoryRequest.new(repository: @gitaly_repo, default_branch: default_branch) - GitalyClient.call(@storage, :repository_service, :create_repository, request, timeout: GitalyClient.fast_timeout) + gitaly_client_call(@storage, :repository_service, :create_repository, request, timeout: GitalyClient.fast_timeout) end def has_local_branches? request = Gitaly::HasLocalBranchesRequest.new(repository: @gitaly_repo) - response = GitalyClient.call(@storage, :repository_service, :has_local_branches, request, timeout: GitalyClient.fast_timeout) + response = gitaly_client_call(@storage, :repository_service, :has_local_branches, request, timeout: GitalyClient.fast_timeout) response.value end @@ -125,7 +128,7 @@ module Gitlab revisions: revisions.map { |r| encode_binary(r) } ) - response = GitalyClient.call(@storage, :repository_service, :find_merge_base, request, timeout: GitalyClient.fast_timeout) + response = gitaly_client_call(@storage, :repository_service, :find_merge_base, request, timeout: GitalyClient.fast_timeout) response.base.presence end @@ -135,7 +138,7 @@ module Gitlab source_repository: source_repository.gitaly_repository ) - GitalyClient.call( + gitaly_client_call( @storage, :repository_service, :create_fork, @@ -153,7 +156,7 @@ module Gitlab mirror: mirror ) - GitalyClient.call( + gitaly_client_call( @storage, :repository_service, :create_repository_from_url, @@ -170,7 +173,7 @@ module Gitlab target_ref: local_ref.b ) - response = GitalyClient.call( + response = gitaly_client_call( @storage, :repository_service, :fetch_source_branch, @@ -184,7 +187,7 @@ module Gitlab def fsck request = Gitaly::FsckRequest.new(repository: @gitaly_repo) - response = GitalyClient.call(@storage, :repository_service, :fsck, request, timeout: GitalyClient.long_timeout) + response = gitaly_client_call(@storage, :repository_service, :fsck, request, timeout: GitalyClient.long_timeout) if response.error.empty? ["", 0] @@ -236,7 +239,7 @@ module Gitlab http_auth: http_auth ) - GitalyClient.call( + gitaly_client_call( @storage, :repository_service, :create_repository_from_snapshot, @@ -253,11 +256,11 @@ module Gitlab ) request.old_revision = old_ref.b unless old_ref.nil? - GitalyClient.call(@storage, :repository_service, :write_ref, request, timeout: GitalyClient.fast_timeout) + gitaly_client_call(@storage, :repository_service, :write_ref, request, timeout: GitalyClient.fast_timeout) end def set_full_path(path) - GitalyClient.call( + gitaly_client_call( @storage, :repository_service, :set_full_path, @@ -272,7 +275,7 @@ module Gitlab end def full_path - response = GitalyClient.call( + response = gitaly_client_call( @storage, :repository_service, :full_path, @@ -286,12 +289,12 @@ module Gitlab def find_license request = Gitaly::FindLicenseRequest.new(repository: @gitaly_repo) - GitalyClient.call(@storage, :repository_service, :find_license, request, timeout: GitalyClient.medium_timeout) + gitaly_client_call(@storage, :repository_service, :find_license, request, timeout: GitalyClient.medium_timeout) end def calculate_checksum request = Gitaly::CalculateChecksumRequest.new(repository: @gitaly_repo) - response = GitalyClient.call(@storage, :repository_service, :calculate_checksum, request, timeout: GitalyClient.fast_timeout) + response = gitaly_client_call(@storage, :repository_service, :calculate_checksum, request, timeout: GitalyClient.fast_timeout) response.checksum.presence rescue GRPC::DataLoss => e raise Gitlab::Git::Repository::InvalidRepository, e @@ -300,23 +303,23 @@ module Gitlab def raw_changes_between(from, to) request = Gitaly::GetRawChangesRequest.new(repository: @gitaly_repo, from_revision: from, to_revision: to) - GitalyClient.call(@storage, :repository_service, :get_raw_changes, request, timeout: GitalyClient.fast_timeout) + gitaly_client_call(@storage, :repository_service, :get_raw_changes, request, timeout: GitalyClient.fast_timeout) end def search_files_by_name(ref, query, limit: 0, offset: 0) request = Gitaly::SearchFilesByNameRequest.new(repository: @gitaly_repo, ref: ref, query: query, limit: limit, offset: offset) - GitalyClient.call(@storage, :repository_service, :search_files_by_name, request, timeout: GitalyClient.fast_timeout).flat_map(&:files) + gitaly_client_call(@storage, :repository_service, :search_files_by_name, request, timeout: GitalyClient.fast_timeout).flat_map(&:files) end def search_files_by_content(ref, query, options = {}) request = Gitaly::SearchFilesByContentRequest.new(repository: @gitaly_repo, ref: ref, query: query) - response = GitalyClient.call(@storage, :repository_service, :search_files_by_content, request, timeout: GitalyClient.default_timeout) + response = gitaly_client_call(@storage, :repository_service, :search_files_by_content, request, timeout: GitalyClient.default_timeout) search_results_from_response(response, options) end def search_files_by_regexp(ref, filter, limit: 0, offset: 0) request = Gitaly::SearchFilesByNameRequest.new(repository: @gitaly_repo, ref: ref, query: '.', filter: filter, limit: limit, offset: offset) - GitalyClient.call(@storage, :repository_service, :search_files_by_name, request, timeout: GitalyClient.fast_timeout).flat_map(&:files) + gitaly_client_call(@storage, :repository_service, :search_files_by_name, request, timeout: GitalyClient.fast_timeout).flat_map(&:files) end def disconnect_alternates @@ -324,19 +327,19 @@ module Gitlab repository: @gitaly_repo ) - GitalyClient.call(@storage, :object_pool_service, :disconnect_git_alternates, request, timeout: GitalyClient.long_timeout) + gitaly_client_call(@storage, :object_pool_service, :disconnect_git_alternates, request, timeout: GitalyClient.long_timeout) end def rename(relative_path) request = Gitaly::RenameRepositoryRequest.new(repository: @gitaly_repo, relative_path: relative_path) - GitalyClient.call(@storage, :repository_service, :rename_repository, request, timeout: GitalyClient.fast_timeout) + gitaly_client_call(@storage, :repository_service, :rename_repository, request, timeout: GitalyClient.fast_timeout) end def remove request = Gitaly::RemoveRepositoryRequest.new(repository: @gitaly_repo) - GitalyClient.call(@storage, :repository_service, :remove_repository, request, timeout: GitalyClient.long_timeout) + gitaly_client_call(@storage, :repository_service, :remove_repository, request, timeout: GitalyClient.long_timeout) end def replicate(source_repository) @@ -345,7 +348,7 @@ module Gitlab source: source_repository.gitaly_repository ) - GitalyClient.call( + gitaly_client_call( @storage, :repository_service, :replicate_repository, @@ -371,11 +374,11 @@ module Gitlab current_match << message.match_data - if message.end_of_match - matches << current_match - current_match = +"" - matches_count += 1 - end + next unless message.end_of_match + + matches << current_match + current_match = +"" + matches_count += 1 end matches @@ -383,7 +386,7 @@ module Gitlab def gitaly_fetch_stream_to_file(save_path, rpc_name, request_class, timeout) request = request_class.new(repository: @gitaly_repo) - response = GitalyClient.call( + response = gitaly_client_call( @storage, :repository_service, rpc_name, @@ -416,7 +419,7 @@ module Gitlab end end - GitalyClient.call( + gitaly_client_call( @storage, :repository_service, rpc_name, diff --git a/lib/gitlab/gitaly_client/with_feature_flag_actors.rb b/lib/gitlab/gitaly_client/with_feature_flag_actors.rb index f89de276c50..92fc524b724 100644 --- a/lib/gitlab/gitaly_client/with_feature_flag_actors.rb +++ b/lib/gitlab/gitaly_client/with_feature_flag_actors.rb @@ -13,6 +13,41 @@ module Gitlab attr_accessor :repository_actor + # gitaly_client_call performs Gitaly calls including collected feature flag actors. The actors are retrieved + # from repository actor and memoized. The service must set `self.repository_actor = a_repository` beforehand. + def gitaly_client_call(*args, **kargs) + return GitalyClient.call(*args, **kargs) unless actors_aware_gitaly_calls? + + unless repository_actor + Gitlab::ErrorTracking.track_and_raise_for_dev_exception( + Feature::InvalidFeatureFlagError.new("gitaly_client_call called without setting repository_actor") + ) + end + + GitalyClient.with_feature_flag_actors( + repository: repository_actor, + user: user_actor, + project: project_actor, + group: group_actor + ) do + GitalyClient.call(*args, **kargs) + end + end + + # gitaly_feature_flag_actors returns a hash of actors implied from input repository. If actors_aware_gitaly_calls + # flag is not on, this method returns an empty hash. + def gitaly_feature_flag_actors(repository) + return {} unless actors_aware_gitaly_calls? + + container = find_repository_container(repository) + { + repository: repository, + user: Feature::Gitaly.user_actor, + project: Feature::Gitaly.project_actor(container), + group: Feature::Gitaly.group_actor(container) + } + end + # Use actor here means the user who originally perform the action. It is collected from ApplicationContext. As # this information is widely propagated in all entry points, User actor should be available everywhere, even in # background jobs. @@ -35,35 +70,32 @@ module Gitlab end end - def gitaly_client_call(*args, **kargs) - if Feature.enabled?(:actors_aware_gitaly_calls) - # The order of actors here is significant. Percentage-based actor selection may not work as expected if this - # order changes. - GitalyClient.with_feature_flag_actors( - repository: repository_actor, - user: user_actor, - project: project_actor, - group: group_actor - ) do - GitalyClient.call(*args, **kargs) - end - else - GitalyClient.call(*args, **kargs) - end - end + private def repository_container strong_memoize(:repository_container) do - next if repository_actor&.gl_repository.blank? + find_repository_container(repository_actor) + end + end + + def find_repository_container(repository) + return if repository&.gl_repository.blank? - if repository_actor.container.nil? - identifier = Gitlab::GlRepository::Identifier.parse(repository_actor.gl_repository) + if repository.container.nil? + begin + identifier = Gitlab::GlRepository::Identifier.parse(repository.gl_repository) identifier.container - else - repository_actor.container + rescue Gitlab::GlRepository::Identifier::InvalidIdentifier + nil end + else + repository.container end end + + def actors_aware_gitaly_calls? + Feature.enabled?(:actors_aware_gitaly_calls) + end end end end diff --git a/lib/gitlab/import_export/attributes_permitter.rb b/lib/gitlab/import_export/attributes_permitter.rb index f6f65f85599..8c7a6c13246 100644 --- a/lib/gitlab/import_export/attributes_permitter.rb +++ b/lib/gitlab/import_export/attributes_permitter.rb @@ -85,11 +85,11 @@ module Gitlab while stack.any? model_name, relations = stack.pop - if relations.is_a?(Hash) - add_permitted_attributes(model_name, relations.keys) + next unless relations.is_a?(Hash) - stack.concat(relations.to_a) - end + add_permitted_attributes(model_name, relations.keys) + + stack.concat(relations.to_a) end @permitted_attributes diff --git a/lib/gitlab/import_export/base/relation_object_saver.rb b/lib/gitlab/import_export/base/relation_object_saver.rb index 3c473449ec0..ed3858d0bf4 100644 --- a/lib/gitlab/import_export/base/relation_object_saver.rb +++ b/lib/gitlab/import_export/base/relation_object_saver.rb @@ -81,11 +81,11 @@ module Gitlab subrelation = relation_object.public_send(definition) association = relation_object.class.reflect_on_association(definition) - if association&.collection? && subrelation.size > MIN_RECORDS_SIZE - collection_subrelations[definition] = subrelation.records + next unless association&.collection? && subrelation.size > MIN_RECORDS_SIZE - subrelation.clear - end + collection_subrelations[definition] = subrelation.records + + subrelation.clear end end end diff --git a/lib/gitlab/instrumentation/redis_base.rb b/lib/gitlab/instrumentation/redis_base.rb index 0bd10597f24..268c6cdf459 100644 --- a/lib/gitlab/instrumentation/redis_base.rb +++ b/lib/gitlab/instrumentation/redis_base.rb @@ -66,8 +66,8 @@ module Gitlab query_time.round(::Gitlab::InstrumentationHelper::DURATION_PRECISION) end - def redis_cluster_validate!(command) - ::Gitlab::Instrumentation::RedisClusterValidator.validate!(command) if @redis_cluster_validation + def redis_cluster_validate!(commands) + ::Gitlab::Instrumentation::RedisClusterValidator.validate!(commands) if @redis_cluster_validation end def enable_redis_cluster_validation diff --git a/lib/gitlab/instrumentation/redis_cluster_validator.rb b/lib/gitlab/instrumentation/redis_cluster_validator.rb index 005751fb0db..36d3e088956 100644 --- a/lib/gitlab/instrumentation/redis_cluster_validator.rb +++ b/lib/gitlab/instrumentation/redis_cluster_validator.rb @@ -10,57 +10,189 @@ module Gitlab # # Gitlab::Redis::Cache # .with { |redis| redis.call('COMMAND') } - # .select { |command| command[3] != command[4] } - # .map { |command| [command[0].upcase, { first: command[3], last: command[4], step: command[5] }] } + # .select { |cmd| cmd[3] != 0 } + # .map { |cmd| [ + # cmd[0].upcase, + # { first: cmd[3], last: cmd[4], step: cmd[5], single_key: cmd[3] == cmd[4] } + # ] + # } # .sort_by(&:first) # .to_h - # - MULTI_KEY_COMMANDS = { - "BITOP" => { first: 2, last: -1, step: 1 }, - "BLPOP" => { first: 1, last: -2, step: 1 }, - "BRPOP" => { first: 1, last: -2, step: 1 }, - "BRPOPLPUSH" => { first: 1, last: 2, step: 1 }, - "BZPOPMAX" => { first: 1, last: -2, step: 1 }, - "BZPOPMIN" => { first: 1, last: -2, step: 1 }, - "DEL" => { first: 1, last: -1, step: 1 }, - "EXISTS" => { first: 1, last: -1, step: 1 }, - "MGET" => { first: 1, last: -1, step: 1 }, - "MSET" => { first: 1, last: -1, step: 2 }, - "MSETNX" => { first: 1, last: -1, step: 2 }, - "PFCOUNT" => { first: 1, last: -1, step: 1 }, - "PFMERGE" => { first: 1, last: -1, step: 1 }, - "RENAME" => { first: 1, last: 2, step: 1 }, - "RENAMENX" => { first: 1, last: 2, step: 1 }, - "RPOPLPUSH" => { first: 1, last: 2, step: 1 }, - "SDIFF" => { first: 1, last: -1, step: 1 }, - "SDIFFSTORE" => { first: 1, last: -1, step: 1 }, - "SINTER" => { first: 1, last: -1, step: 1 }, - "SINTERSTORE" => { first: 1, last: -1, step: 1 }, - "SMOVE" => { first: 1, last: 2, step: 1 }, - "SUNION" => { first: 1, last: -1, step: 1 }, - "SUNIONSTORE" => { first: 1, last: -1, step: 1 }, - "UNLINK" => { first: 1, last: -1, step: 1 }, - "WATCH" => { first: 1, last: -1, step: 1 } + REDIS_COMMANDS = { + "APPEND" => { first: 1, last: 1, step: 1, single_key: true }, + "BITCOUNT" => { first: 1, last: 1, step: 1, single_key: true }, + "BITFIELD" => { first: 1, last: 1, step: 1, single_key: true }, + "BITFIELD_RO" => { first: 1, last: 1, step: 1, single_key: true }, + "BITOP" => { first: 2, last: -1, step: 1, single_key: false }, + "BITPOS" => { first: 1, last: 1, step: 1, single_key: true }, + "BLMOVE" => { first: 1, last: 2, step: 1, single_key: false }, + "BLPOP" => { first: 1, last: -2, step: 1, single_key: false }, + "BRPOP" => { first: 1, last: -2, step: 1, single_key: false }, + "BRPOPLPUSH" => { first: 1, last: 2, step: 1, single_key: false }, + "BZPOPMAX" => { first: 1, last: -2, step: 1, single_key: false }, + "BZPOPMIN" => { first: 1, last: -2, step: 1, single_key: false }, + "COPY" => { first: 1, last: 2, step: 1, single_key: false }, + "DECR" => { first: 1, last: 1, step: 1, single_key: true }, + "DECRBY" => { first: 1, last: 1, step: 1, single_key: true }, + "DEL" => { first: 1, last: -1, step: 1, single_key: false }, + "DUMP" => { first: 1, last: 1, step: 1, single_key: true }, + "EXISTS" => { first: 1, last: -1, step: 1, single_key: false }, + "EXPIRE" => { first: 1, last: 1, step: 1, single_key: true }, + "EXPIREAT" => { first: 1, last: 1, step: 1, single_key: true }, + "GEOADD" => { first: 1, last: 1, step: 1, single_key: true }, + "GEODIST" => { first: 1, last: 1, step: 1, single_key: true }, + "GEOHASH" => { first: 1, last: 1, step: 1, single_key: true }, + "GEOPOS" => { first: 1, last: 1, step: 1, single_key: true }, + "GEORADIUS" => { first: 1, last: 1, step: 1, single_key: true }, + "GEORADIUSBYMEMBER" => { first: 1, last: 1, step: 1, single_key: true }, + "GEORADIUSBYMEMBER_RO" => { first: 1, last: 1, step: 1, single_key: true }, + "GEORADIUS_RO" => { first: 1, last: 1, step: 1, single_key: true }, + "GEOSEARCH" => { first: 1, last: 1, step: 1, single_key: true }, + "GEOSEARCHSTORE" => { first: 1, last: 2, step: 1, single_key: false }, + "GET" => { first: 1, last: 1, step: 1, single_key: true }, + "GETBIT" => { first: 1, last: 1, step: 1, single_key: true }, + "GETDEL" => { first: 1, last: 1, step: 1, single_key: true }, + "GETEX" => { first: 1, last: 1, step: 1, single_key: true }, + "GETRANGE" => { first: 1, last: 1, step: 1, single_key: true }, + "GETSET" => { first: 1, last: 1, step: 1, single_key: true }, + "HDEL" => { first: 1, last: 1, step: 1, single_key: true }, + "HEXISTS" => { first: 1, last: 1, step: 1, single_key: true }, + "HGET" => { first: 1, last: 1, step: 1, single_key: true }, + "HGETALL" => { first: 1, last: 1, step: 1, single_key: true }, + "HINCRBY" => { first: 1, last: 1, step: 1, single_key: true }, + "HINCRBYFLOAT" => { first: 1, last: 1, step: 1, single_key: true }, + "HKEYS" => { first: 1, last: 1, step: 1, single_key: true }, + "HLEN" => { first: 1, last: 1, step: 1, single_key: true }, + "HMGET" => { first: 1, last: 1, step: 1, single_key: true }, + "HMSET" => { first: 1, last: 1, step: 1, single_key: true }, + "HRANDFIELD" => { first: 1, last: 1, step: 1, single_key: true }, + "HSCAN" => { first: 1, last: 1, step: 1, single_key: true }, + "HSET" => { first: 1, last: 1, step: 1, single_key: true }, + "HSETNX" => { first: 1, last: 1, step: 1, single_key: true }, + "HSTRLEN" => { first: 1, last: 1, step: 1, single_key: true }, + "HVALS" => { first: 1, last: 1, step: 1, single_key: true }, + "INCR" => { first: 1, last: 1, step: 1, single_key: true }, + "INCRBY" => { first: 1, last: 1, step: 1, single_key: true }, + "INCRBYFLOAT" => { first: 1, last: 1, step: 1, single_key: true }, + "LINDEX" => { first: 1, last: 1, step: 1, single_key: true }, + "LINSERT" => { first: 1, last: 1, step: 1, single_key: true }, + "LLEN" => { first: 1, last: 1, step: 1, single_key: true }, + "LMOVE" => { first: 1, last: 2, step: 1, single_key: false }, + "LPOP" => { first: 1, last: 1, step: 1, single_key: true }, + "LPOS" => { first: 1, last: 1, step: 1, single_key: true }, + "LPUSH" => { first: 1, last: 1, step: 1, single_key: true }, + "LPUSHX" => { first: 1, last: 1, step: 1, single_key: true }, + "LRANGE" => { first: 1, last: 1, step: 1, single_key: true }, + "LREM" => { first: 1, last: 1, step: 1, single_key: true }, + "LSET" => { first: 1, last: 1, step: 1, single_key: true }, + "LTRIM" => { first: 1, last: 1, step: 1, single_key: true }, + "MGET" => { first: 1, last: -1, step: 1, single_key: false }, + "MIGRATE" => { first: 3, last: 3, step: 1, single_key: true }, + "MOVE" => { first: 1, last: 1, step: 1, single_key: true }, + "MSET" => { first: 1, last: -1, step: 2, single_key: false }, + "MSETNX" => { first: 1, last: -1, step: 2, single_key: false }, + "OBJECT" => { first: 2, last: 2, step: 1, single_key: true }, + "PERSIST" => { first: 1, last: 1, step: 1, single_key: true }, + "PEXPIRE" => { first: 1, last: 1, step: 1, single_key: true }, + "PEXPIREAT" => { first: 1, last: 1, step: 1, single_key: true }, + "PFADD" => { first: 1, last: 1, step: 1, single_key: true }, + "PFCOUNT" => { first: 1, last: -1, step: 1, single_key: false }, + "PFDEBUG" => { first: 2, last: 2, step: 1, single_key: true }, + "PFMERGE" => { first: 1, last: -1, step: 1, single_key: false }, + "PSETEX" => { first: 1, last: 1, step: 1, single_key: true }, + "PTTL" => { first: 1, last: 1, step: 1, single_key: true }, + "RENAME" => { first: 1, last: 2, step: 1, single_key: false }, + "RENAMENX" => { first: 1, last: 2, step: 1, single_key: false }, + "RESTORE" => { first: 1, last: 1, step: 1, single_key: true }, + "RESTORE-ASKING" => { first: 1, last: 1, step: 1, single_key: true }, + "RPOP" => { first: 1, last: 1, step: 1, single_key: true }, + "RPOPLPUSH" => { first: 1, last: 2, step: 1, single_key: false }, + "RPUSH" => { first: 1, last: 1, step: 1, single_key: true }, + "RPUSHX" => { first: 1, last: 1, step: 1, single_key: true }, + "SADD" => { first: 1, last: 1, step: 1, single_key: true }, + "SCARD" => { first: 1, last: 1, step: 1, single_key: true }, + "SDIFF" => { first: 1, last: -1, step: 1, single_key: false }, + "SDIFFSTORE" => { first: 1, last: -1, step: 1, single_key: false }, + "SET" => { first: 1, last: 1, step: 1, single_key: true }, + "SETBIT" => { first: 1, last: 1, step: 1, single_key: true }, + "SETEX" => { first: 1, last: 1, step: 1, single_key: true }, + "SETNX" => { first: 1, last: 1, step: 1, single_key: true }, + "SETRANGE" => { first: 1, last: 1, step: 1, single_key: true }, + "SINTER" => { first: 1, last: -1, step: 1, single_key: false }, + "SINTERSTORE" => { first: 1, last: -1, step: 1, single_key: false }, + "SISMEMBER" => { first: 1, last: 1, step: 1, single_key: true }, + "SMEMBERS" => { first: 1, last: 1, step: 1, single_key: true }, + "SMISMEMBER" => { first: 1, last: 1, step: 1, single_key: true }, + "SMOVE" => { first: 1, last: 2, step: 1, single_key: false }, + "SORT" => { first: 1, last: 1, step: 1, single_key: true }, + "SPOP" => { first: 1, last: 1, step: 1, single_key: true }, + "SRANDMEMBER" => { first: 1, last: 1, step: 1, single_key: true }, + "SREM" => { first: 1, last: 1, step: 1, single_key: true }, + "SSCAN" => { first: 1, last: 1, step: 1, single_key: true }, + "STRLEN" => { first: 1, last: 1, step: 1, single_key: true }, + "SUBSTR" => { first: 1, last: 1, step: 1, single_key: true }, + "SUNION" => { first: 1, last: -1, step: 1, single_key: false }, + "SUNIONSTORE" => { first: 1, last: -1, step: 1, single_key: false }, + "TOUCH" => { first: 1, last: -1, step: 1, single_key: false }, + "TTL" => { first: 1, last: 1, step: 1, single_key: true }, + "TYPE" => { first: 1, last: 1, step: 1, single_key: true }, + "UNLINK" => { first: 1, last: -1, step: 1, single_key: false }, + "WATCH" => { first: 1, last: -1, step: 1, single_key: false }, + "XACK" => { first: 1, last: 1, step: 1, single_key: true }, + "XADD" => { first: 1, last: 1, step: 1, single_key: true }, + "XAUTOCLAIM" => { first: 1, last: 1, step: 1, single_key: true }, + "XCLAIM" => { first: 1, last: 1, step: 1, single_key: true }, + "XDEL" => { first: 1, last: 1, step: 1, single_key: true }, + "XGROUP" => { first: 2, last: 2, step: 1, single_key: true }, + "XINFO" => { first: 2, last: 2, step: 1, single_key: true }, + "XLEN" => { first: 1, last: 1, step: 1, single_key: true }, + "XPENDING" => { first: 1, last: 1, step: 1, single_key: true }, + "XRANGE" => { first: 1, last: 1, step: 1, single_key: true }, + "XREVRANGE" => { first: 1, last: 1, step: 1, single_key: true }, + "XSETID" => { first: 1, last: 1, step: 1, single_key: true }, + "XTRIM" => { first: 1, last: 1, step: 1, single_key: true }, + "ZADD" => { first: 1, last: 1, step: 1, single_key: true }, + "ZCARD" => { first: 1, last: 1, step: 1, single_key: true }, + "ZCOUNT" => { first: 1, last: 1, step: 1, single_key: true }, + "ZDIFFSTORE" => { first: 1, last: 1, step: 1, single_key: true }, + "ZINCRBY" => { first: 1, last: 1, step: 1, single_key: true }, + "ZINTERSTORE" => { first: 1, last: 1, step: 1, single_key: true }, + "ZLEXCOUNT" => { first: 1, last: 1, step: 1, single_key: true }, + "ZMSCORE" => { first: 1, last: 1, step: 1, single_key: true }, + "ZPOPMAX" => { first: 1, last: 1, step: 1, single_key: true }, + "ZPOPMIN" => { first: 1, last: 1, step: 1, single_key: true }, + "ZRANDMEMBER" => { first: 1, last: 1, step: 1, single_key: true }, + "ZRANGE" => { first: 1, last: 1, step: 1, single_key: true }, + "ZRANGEBYLEX" => { first: 1, last: 1, step: 1, single_key: true }, + "ZRANGEBYSCORE" => { first: 1, last: 1, step: 1, single_key: true }, + "ZRANGESTORE" => { first: 1, last: 2, step: 1, single_key: false }, + "ZRANK" => { first: 1, last: 1, step: 1, single_key: true }, + "ZREM" => { first: 1, last: 1, step: 1, single_key: true }, + "ZREMRANGEBYLEX" => { first: 1, last: 1, step: 1, single_key: true }, + "ZREMRANGEBYRANK" => { first: 1, last: 1, step: 1, single_key: true }, + "ZREMRANGEBYSCORE" => { first: 1, last: 1, step: 1, single_key: true }, + "ZREVRANGE" => { first: 1, last: 1, step: 1, single_key: true }, + "ZREVRANGEBYLEX" => { first: 1, last: 1, step: 1, single_key: true }, + "ZREVRANGEBYSCORE" => { first: 1, last: 1, step: 1, single_key: true }, + "ZREVRANK" => { first: 1, last: 1, step: 1, single_key: true }, + "ZSCAN" => { first: 1, last: 1, step: 1, single_key: true }, + "ZSCORE" => { first: 1, last: 1, step: 1, single_key: true }, + "ZUNIONSTORE" => { first: 1, last: 1, step: 1, single_key: true } }.freeze CrossSlotError = Class.new(StandardError) class << self - def validate!(command) + def validate!(commands) return unless Rails.env.development? || Rails.env.test? return if allow_cross_slot_commands? + return if commands.empty? - command_name = command.first.to_s.upcase - argument_positions = MULTI_KEY_COMMANDS[command_name] - - return unless argument_positions - - arguments = command.flatten[argument_positions[:first]..argument_positions[:last]] - - key_slots = arguments.each_slice(argument_positions[:step]).map do |args| - key_slot(args.first) - end + # early exit for single-command (non-pipelined) if it is a single-key-command + command_name = commands.size > 1 ? "PIPELINE/MULTI" : commands.first.first.to_s.upcase + return if commands.size == 1 && REDIS_COMMANDS.dig(command_name, :single_key) + key_slots = commands.map { |command| key_slots(command) }.flatten if key_slots.uniq.many? # rubocop: disable CodeReuse/ActiveRecord raise CrossSlotError, "Redis command #{command_name} arguments hash to different slots. See https://docs.gitlab.com/ee/development/redis.html#multi-key-commands" end @@ -78,6 +210,17 @@ module Gitlab private + def key_slots(command) + argument_positions = REDIS_COMMANDS[command.first.to_s.upcase] + + return [] unless argument_positions + + arguments = command.flatten[argument_positions[:first]..argument_positions[:last]] + arguments.each_slice(argument_positions[:step]).map do |args| + key_slot(args.first) + end + end + def allow_cross_slot_commands? Thread.current[:allow_cross_slot_commands].to_i > 0 end diff --git a/lib/gitlab/instrumentation/redis_interceptor.rb b/lib/gitlab/instrumentation/redis_interceptor.rb index 02c96d632b0..f19279df2fe 100644 --- a/lib/gitlab/instrumentation/redis_interceptor.rb +++ b/lib/gitlab/instrumentation/redis_interceptor.rb @@ -33,8 +33,7 @@ module Gitlab def instrument_call(commands) start = Gitlab::Metrics::System.monotonic_time # must come first so that 'start' is always defined instrumentation_class.instance_count_request(commands.size) - - commands.each { |c| instrumentation_class.redis_cluster_validate!(c) } + instrumentation_class.redis_cluster_validate!(commands) yield rescue ::Redis::BaseError => ex diff --git a/lib/gitlab/issues/rebalancing/state.rb b/lib/gitlab/issues/rebalancing/state.rb index 99f777edee5..36346564b39 100644 --- a/lib/gitlab/issues/rebalancing/state.rb +++ b/lib/gitlab/issues/rebalancing/state.rb @@ -99,11 +99,13 @@ module Gitlab def refresh_keys_expiration with_redis do |redis| - redis.multi do |multi| - multi.expire(issue_ids_key, REDIS_EXPIRY_TIME) - multi.expire(current_index_key, REDIS_EXPIRY_TIME) - multi.expire(current_project_key, REDIS_EXPIRY_TIME) - multi.expire(CONCURRENT_RUNNING_REBALANCES_KEY, REDIS_EXPIRY_TIME) + Gitlab::Instrumentation::RedisClusterValidator.allow_cross_slot_commands do + redis.multi do |multi| + multi.expire(issue_ids_key, REDIS_EXPIRY_TIME) + multi.expire(current_index_key, REDIS_EXPIRY_TIME) + multi.expire(current_project_key, REDIS_EXPIRY_TIME) + multi.expire(CONCURRENT_RUNNING_REBALANCES_KEY, REDIS_EXPIRY_TIME) + end end end end @@ -112,12 +114,14 @@ module Gitlab value = "#{rebalanced_container_type}/#{rebalanced_container_id}" with_redis do |redis| - redis.multi do |multi| - multi.del(issue_ids_key) - multi.del(current_index_key) - multi.del(current_project_key) - multi.srem?(CONCURRENT_RUNNING_REBALANCES_KEY, value) - multi.set(self.class.recently_finished_key(rebalanced_container_type, rebalanced_container_id), true, ex: 1.hour) + Gitlab::Instrumentation::RedisClusterValidator.allow_cross_slot_commands do + redis.multi do |multi| + multi.del(issue_ids_key) + multi.del(current_index_key) + multi.del(current_project_key) + multi.srem?(CONCURRENT_RUNNING_REBALANCES_KEY, value) + multi.set(self.class.recently_finished_key(rebalanced_container_type, rebalanced_container_id), true, ex: 1.hour) + end end end end diff --git a/lib/gitlab/manifest_import/metadata.rb b/lib/gitlab/manifest_import/metadata.rb index 6fe9bb10cdf..3747431c6a7 100644 --- a/lib/gitlab/manifest_import/metadata.rb +++ b/lib/gitlab/manifest_import/metadata.rb @@ -14,9 +14,11 @@ module Gitlab def save(repositories, group_id) Gitlab::Redis::SharedState.with do |redis| - redis.multi do |multi| - multi.set(key_for('repositories'), Gitlab::Json.dump(repositories), ex: EXPIRY_TIME) - multi.set(key_for('group_id'), group_id, ex: EXPIRY_TIME) + Gitlab::Instrumentation::RedisClusterValidator.allow_cross_slot_commands do + redis.multi do |multi| + multi.set(key_for('repositories'), Gitlab::Json.dump(repositories), ex: EXPIRY_TIME) + multi.set(key_for('group_id'), group_id, ex: EXPIRY_TIME) + end end end end diff --git a/lib/gitlab/markdown_cache/redis/store.rb b/lib/gitlab/markdown_cache/redis/store.rb index fd5870fa842..8cab069e1bf 100644 --- a/lib/gitlab/markdown_cache/redis/store.rb +++ b/lib/gitlab/markdown_cache/redis/store.rb @@ -10,9 +10,11 @@ module Gitlab results = {} Gitlab::Redis::Cache.with do |r| - r.pipelined do |pipeline| - subjects.each do |subject| - results[subject.cache_key] = new(subject).read(pipeline) + Gitlab::Instrumentation::RedisClusterValidator.allow_cross_slot_commands do + r.pipelined do |pipeline| + subjects.each do |subject| + results[subject.cache_key] = new(subject).read(pipeline) + end end end end diff --git a/lib/gitlab/metrics/samplers/base_sampler.rb b/lib/gitlab/metrics/samplers/base_sampler.rb index b2a9de21145..e62a62a935e 100644 --- a/lib/gitlab/metrics/samplers/base_sampler.rb +++ b/lib/gitlab/metrics/samplers/base_sampler.rb @@ -46,11 +46,11 @@ module Gitlab # 2. Don't sample data at the same interval two times in a row. def sleep_interval while step = @interval_steps.sample - if step != @last_step - @last_step = step + next if step == @last_step - return @interval + @last_step - end + @last_step = step + + return @interval + @last_step end end diff --git a/lib/gitlab/pagination/keyset/in_operator_optimization/strategies/record_loader_strategy.rb b/lib/gitlab/pagination/keyset/in_operator_optimization/strategies/record_loader_strategy.rb index 51f38c1da58..4f79a3593f4 100644 --- a/lib/gitlab/pagination/keyset/in_operator_optimization/strategies/record_loader_strategy.rb +++ b/lib/gitlab/pagination/keyset/in_operator_optimization/strategies/record_loader_strategy.rb @@ -39,15 +39,15 @@ module Gitlab def verify_order_by_attributes_on_model!(model, order_by_columns) order_by_columns.map(&:column).each do |column| - unless model.columns_hash[column.attribute_name.to_s] - text = <<~TEXT + next if model.columns_hash[column.attribute_name.to_s] + + text = <<~TEXT The "RecordLoaderStrategy" does not support the following ORDER BY column because it's not available on the \"#{model.table_name}\" table: #{column.attribute_name} Omit the "finder_query" parameter to use the "OrderValuesLoaderStrategy". - TEXT - raise text - end + TEXT + raise text end end end diff --git a/lib/gitlab/reference_extractor.rb b/lib/gitlab/reference_extractor.rb index f914123a94d..c5798bec0d7 100644 --- a/lib/gitlab/reference_extractor.rb +++ b/lib/gitlab/reference_extractor.rb @@ -42,10 +42,10 @@ module Gitlab @references[type] ||= references(type) end - if %w(mentioned_user mentioned_group mentioned_project).include?(type.to_s) - define_method("#{type}_ids") do - @references[type] ||= references(type, ids_only: true) - end + next unless %w(mentioned_user mentioned_group mentioned_project).include?(type.to_s) + + define_method("#{type}_ids") do + @references[type] ||= references(type, ids_only: true) end end diff --git a/lib/tasks/gitlab/assets.rake b/lib/tasks/gitlab/assets.rake index 3d3bdc560ac..12a8cb01e9e 100644 --- a/lib/tasks/gitlab/assets.rake +++ b/lib/tasks/gitlab/assets.rake @@ -127,20 +127,20 @@ namespace :gitlab do # rewrite the corresponding gzip file (if it exists) gzip = "#{file}.gz" - if File.exist?(gzip) - puts "Fixing #{gzip}" + next unless File.exist?(gzip) - FileUtils.rm(gzip) - mtime = File.stat(file).mtime + puts "Fixing #{gzip}" - File.open(gzip, 'wb+') do |f| - gz = Zlib::GzipWriter.new(f, Zlib::BEST_COMPRESSION) - gz.mtime = mtime - gz.write IO.binread(file) - gz.close + FileUtils.rm(gzip) + mtime = File.stat(file).mtime - File.utime(mtime, mtime, f.path) - end + File.open(gzip, 'wb+') do |f| + gz = Zlib::GzipWriter.new(f, Zlib::BEST_COMPRESSION) + gz.mtime = mtime + gz.write IO.binread(file) + gz.close + + File.utime(mtime, mtime, f.path) end end end diff --git a/lib/tasks/gitlab/db/validate_config.rake b/lib/tasks/gitlab/db/validate_config.rake index bf9ebc56486..b3c98e91d17 100644 --- a/lib/tasks/gitlab/db/validate_config.rake +++ b/lib/tasks/gitlab/db/validate_config.rake @@ -64,15 +64,15 @@ namespace :gitlab do next unless identifier connections_with_tasks = connections.select { |connection| connection[:database_tasks?] } - if connections_with_tasks.many? - names = connections_with_tasks.pluck(:name) - - warnings << "- Many configurations (#{names.join(', ')}) " \ - "share the same database (#{identifier}). " \ - "This will result in failures provisioning or migrating this database. " \ - "Ensure that additional databases are configured " \ - "with 'database_tasks: false' or are pointing to a dedicated database host." - end + next unless connections_with_tasks.many? + + names = connections_with_tasks.pluck(:name) + + warnings << "- Many configurations (#{names.join(', ')}) " \ + "share the same database (#{identifier}). " \ + "This will result in failures provisioning or migrating this database. " \ + "Ensure that additional databases are configured " \ + "with 'database_tasks: false' or are pointing to a dedicated database host." end # Each configuration with `database_tasks: false` should share the database with `main:` diff --git a/locale/gitlab.pot b/locale/gitlab.pot index a2b05faa74f..3fa779062d7 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -17232,6 +17232,9 @@ msgstr "" msgid "Followed users" msgstr "" +msgid "Following tags don't exist" +msgstr "" + msgid "Font Color" msgstr "" @@ -36653,6 +36656,9 @@ msgstr "" msgid "SecurityOrchestration|Use a scan execution policy to create rules which enforce security scans for particular branches at a certain time. Supported types are SAST, DAST, Secret detection, Container scanning, and Dependency scanning." msgstr "" +msgid "SecurityOrchestration|Use a scan result policy to create rules that check for security vulnerabilities and license compliance before merging a merge request." +msgstr "" + msgid "SecurityOrchestration|Use a scan result policy to create rules that ensure security issues are checked before merging a merge request." msgstr "" @@ -36716,6 +36722,9 @@ msgstr "" msgid "SecurityReports|%{firstProject}, %{secondProject}, and %{rest}" msgstr "" +msgid "SecurityReports|Activity" +msgstr "" + msgid "SecurityReports|Add or remove projects to monitor in the security area. Projects included in this list will have their results displayed in the security dashboard and vulnerability report." msgstr "" @@ -47867,10 +47876,10 @@ msgstr "" msgid "ciReport|%{scanner} detected %{strong_start}%{number}%{strong_end} new potential %{vulnStr}" msgstr "" -msgid "ciReport|%{scanner} detected no %{boldStart}new%{boldEnd} potential vulnerabilities" +msgid "ciReport|%{scanner} detected no new %{vulnStr}" msgstr "" -msgid "ciReport|%{scanner} detected no new %{vulnStr}" +msgid "ciReport|%{scanner} detected no new potential vulnerabilities" msgstr "" msgid "ciReport|: Loading resulted in an error" diff --git a/package.json b/package.json index 0fe20b96301..7b28a3d557b 100644 --- a/package.json +++ b/package.json @@ -53,8 +53,8 @@ "@codesandbox/sandpack-client": "^1.2.2", "@gitlab/at.js": "1.5.7", "@gitlab/favicon-overlay": "2.0.0", - "@gitlab/svgs": "3.7.0", - "@gitlab/ui": "49.3.0", + "@gitlab/svgs": "3.8.0", + "@gitlab/ui": "49.10.0", "@gitlab/visual-review-tools": "1.7.3", "@gitlab/web-ide": "0.0.1-dev-20221114183058", "@rails/actioncable": "6.1.4-7", diff --git a/scripts/perf/query_limiting_report.rb b/scripts/perf/query_limiting_report.rb index 364cd6fc5d4..6326b2590ae 100755 --- a/scripts/perf/query_limiting_report.rb +++ b/scripts/perf/query_limiting_report.rb @@ -124,19 +124,19 @@ class QueryLimitingReport file_lines.each_index do |index| line = file_lines[index] - if line =~ /#{CODE_LINES_SEARCH_STRING}/o - issue_iid = line.slice(%r{issues/(\d+)\D}, 1) - line_number = index + 1 - code_line = { - file_location: "#{filename}:#{line_number}", - filename: filename, - line_number: line_number, - line: line, - issue_iid: issue_iid.to_i, - has_issue_iid: !issue_iid.nil? - } - code_lines << code_line - end + next unless line =~ /#{CODE_LINES_SEARCH_STRING}/o + + issue_iid = line.slice(%r{issues/(\d+)\D}, 1) + line_number = index + 1 + code_line = { + file_location: "#{filename}:#{line_number}", + filename: filename, + line_number: line_number, + line: line, + issue_iid: issue_iid.to_i, + has_issue_iid: !issue_iid.nil? + } + code_lines << code_line end end diff --git a/scripts/qa/quarantine-types-check b/scripts/qa/quarantine-types-check index 44d329a3590..188348b949c 100755 --- a/scripts/qa/quarantine-types-check +++ b/scripts/qa/quarantine-types-check @@ -30,19 +30,19 @@ puts "\nAnalyzing quarantined test data...\n" tests = data_hash['examples'] tests.each do |test| - if test['quarantine'] - unless QUARANTINE_TYPES.include?(test['quarantine']['type']) - quarantine_type_errors.push( - <<~TYPE_ERRORS + next unless test['quarantine'] + + unless QUARANTINE_TYPES.include?(test['quarantine']['type']) + quarantine_type_errors.push( + <<~TYPE_ERRORS ==> #{test['full_description']} in file: #{test['id']} with type: "#{test['quarantine']['type']}" - TYPE_ERRORS - ) - end - - missing_issues.push(" ==> #{test['id']} - #{test['full_description']}\n") unless test['quarantine']['issue'] + TYPE_ERRORS + ) end + + missing_issues.push(" ==> #{test['id']} - #{test['full_description']}\n") unless test['quarantine']['issue'] end if quarantine_type_errors.empty? && missing_issues.empty? diff --git a/scripts/review_apps/automated_cleanup.rb b/scripts/review_apps/automated_cleanup.rb index 2440df6958d..f020283de52 100755 --- a/scripts/review_apps/automated_cleanup.rb +++ b/scripts/review_apps/automated_cleanup.rb @@ -88,7 +88,7 @@ module ReviewApps if deployed_at < delete_threshold deleted_environment = delete_environment(environment, deployment) if deleted_environment - release = Tooling::Helm3Client::Release.new(environment.slug, 1, deployed_at.to_s, nil, nil, review_apps_namespace) + release = Tooling::Helm3Client::Release.new(environment.slug, 1, deployed_at.to_s, nil, nil, environment.slug) releases_to_delete << release end else @@ -104,7 +104,7 @@ module ReviewApps end delete_stopped_environments(environment_type: :review_app, checked_environments: checked_environments, last_updated_threshold: delete_threshold) do |environment| - releases_to_delete << Tooling::Helm3Client::Release.new(environment.slug, 1, environment.updated_at, nil, nil, review_apps_namespace) + releases_to_delete << Tooling::Helm3Client::Release.new(environment.slug, 1, environment.updated_at, nil, nil, environment.slug) end delete_helm_releases(releases_to_delete) @@ -190,6 +190,8 @@ module ReviewApps rescue Gitlab::Error::Forbidden puts "Review app '#{environment.name}' / '#{environment.slug}' (##{environment.id}) is forbidden: skipping it" + rescue Gitlab::Error::InternalServerError + puts "Review app '#{environment.name}' / '#{environment.slug}' (##{environment.id}) 500 error - ignoring it" end def stop_environment(environment, deployment) diff --git a/scripts/review_apps/base-config.yaml b/scripts/review_apps/base-config.yaml index 132158d6e8c..43dc562c58a 100644 --- a/scripts/review_apps/base-config.yaml +++ b/scripts/review_apps/base-config.yaml @@ -18,6 +18,7 @@ global: preemptible: "true" certmanager: install: false + gitlab: gitaly: resources: @@ -34,18 +35,10 @@ gitlab: preemptible: "false" podAnnotations: <<: *safe-to-evict + gitlab-exporter: enabled: false - mailroom: - enabled: false - migrations: - resources: - requests: - cpu: 400m - memory: 920Mi - limits: - cpu: 600m - memory: 1100Mi + gitlab-shell: resources: requests: @@ -54,12 +47,30 @@ gitlab: limits: cpu: 750m memory: 150Mi - maxReplicas: 3 + minReplicas: 1 + maxReplicas: 1 hpa: targetAverageValue: 500m deployment: livenessProbe: timeoutSeconds: 5 + + kas: + minReplicas: 1 + maxReplicas: 1 + + mailroom: + enabled: false + + migrations: + resources: + requests: + cpu: 400m + memory: 920Mi + limits: + cpu: 600m + memory: 1100Mi + sidekiq: resources: requests: @@ -70,6 +81,7 @@ gitlab: memory: 2890Mi hpa: targetAverageValue: 650m + toolbox: resources: requests: @@ -78,6 +90,7 @@ gitlab: limits: cpu: 450m memory: 2890Mi + webservice: resources: requests: @@ -86,6 +99,8 @@ gitlab: limits: cpu: 1119m memory: 4214Mi + minReplicas: 1 + maxReplicas: 1 deployment: readinessProbe: initialDelaySeconds: 5 # Default is 0 @@ -103,6 +118,7 @@ gitlab: initialDelaySeconds: 5 # Default is 0 periodSeconds: 15 # Default is 10 timeoutSeconds: 5 # Default is 2 + gitlab-runner: resources: requests: @@ -115,6 +131,7 @@ gitlab-runner: preemptible: "true" podAnnotations: <<: *safe-to-evict + minio: resources: requests: @@ -127,6 +144,7 @@ minio: preemptible: "true" podAnnotations: <<: *safe-to-evict + nginx-ingress: controller: config: @@ -156,6 +174,7 @@ nginx-ingress: memory: 24Mi nodeSelector: preemptible: "true" + postgresql: metrics: enabled: false @@ -171,8 +190,10 @@ postgresql: preemptible: "false" podAnnotations: <<: *safe-to-evict + prometheus: install: false + redis: metrics: enabled: false @@ -188,9 +209,11 @@ redis: preemptible: "true" podAnnotations: <<: *safe-to-evict + registry: hpa: minReplicas: 1 + maxReplicas: 1 resources: requests: cpu: 100m diff --git a/scripts/review_apps/review-apps.sh b/scripts/review_apps/review-apps.sh index 7094bfcdf1c..5883141a943 100755 --- a/scripts/review_apps/review-apps.sh +++ b/scripts/review_apps/review-apps.sh @@ -62,7 +62,7 @@ function previous_deploy_failed() { return $status } -function delete_release() { +function delete_helm_release() { local namespace="${CI_ENVIRONMENT_SLUG}" local release="${CI_ENVIRONMENT_SLUG}" @@ -74,32 +74,6 @@ function delete_release() { if deploy_exists "${namespace}" "${release}"; then helm uninstall --namespace="${namespace}" "${release}" fi -} - -function delete_failed_release() { - local namespace="${CI_ENVIRONMENT_SLUG}" - local release="${CI_ENVIRONMENT_SLUG}" - - if [ -z "${release}" ]; then - echoerr "No release given, aborting the delete!" - return - fi - - if ! deploy_exists "${namespace}" "${release}"; then - echoinfo "No Review App with ${release} is currently deployed." - else - # Cleanup and previous installs, as FAILED and PENDING_UPGRADE will cause errors with `upgrade` - if previous_deploy_failed "${namespace}" "${release}" ; then - echoinfo "Review App deployment in bad state, cleaning up namespace ${release}" - delete_namespace - else - echoinfo "Review App deployment in good state" - fi - fi -} - -function delete_namespace() { - local namespace="${CI_ENVIRONMENT_SLUG}" if namespace_exists "${namespace}"; then echoinfo "Deleting namespace ${namespace}..." true diff --git a/spec/fixtures/api/schemas/public_api/v4/metadata.json b/spec/fixtures/api/schemas/public_api/v4/metadata.json index fd219b95df8..f5a6aa86890 100644 --- a/spec/fixtures/api/schemas/public_api/v4/metadata.json +++ b/spec/fixtures/api/schemas/public_api/v4/metadata.json @@ -3,11 +3,16 @@ "required": [ "version", "revision", - "kas" + "kas", + "enterprise" ], "properties": { - "version": { "type": "string" }, - "revision": { "type": "string" }, + "version": { + "type": "string" + }, + "revision": { + "type": "string" + }, "kas": { "type": "object", "required": [ @@ -16,11 +21,26 @@ "version" ], "properties": { - "enabled": { "type": "boolean" }, - "externalUrl": { "type": ["string", "null"] }, - "version": { "type": ["string", "null"] } + "enabled": { + "type": "boolean" + }, + "externalUrl": { + "type": [ + "string", + "null" + ] + }, + "version": { + "type": [ + "string", + "null" + ] + } } + }, + "enterprise": { + "type": "boolean" } }, "additionalProperties": false -} +}
\ No newline at end of file diff --git a/spec/frontend/access_tokens/components/__snapshots__/expires_at_field_spec.js.snap b/spec/frontend/access_tokens/components/__snapshots__/expires_at_field_spec.js.snap index 2bd2b17a12d..42818c14029 100644 --- a/spec/frontend/access_tokens/components/__snapshots__/expires_at_field_spec.js.snap +++ b/spec/frontend/access_tokens/components/__snapshots__/expires_at_field_spec.js.snap @@ -21,6 +21,7 @@ exports[`~/access_tokens/components/expires_at_field should render datepicker wi mindate="Mon Jul 06 2020 00:00:00 GMT+0000 (Greenwich Mean Time)" placeholder="YYYY-MM-DD" showclearbutton="true" + size="medium" theme="" /> </gl-form-group-stub> diff --git a/spec/frontend/ci_variable_list/components/ci_admin_variables_spec.js b/spec/frontend/ci_variable_list/components/ci_admin_variables_spec.js index c80e1184f02..c7375acd8e5 100644 --- a/spec/frontend/ci_variable_list/components/ci_admin_variables_spec.js +++ b/spec/frontend/ci_variable_list/components/ci_admin_variables_spec.js @@ -24,6 +24,7 @@ describe('Ci Project Variable wrapper', () => { expect(findCiShared().props()).toEqual({ areScopedVariablesAvailable: false, componentName: 'InstanceVariables', + hideEnvironmentScope: true, mutationData: wrapper.vm.$options.mutationData, queryData: wrapper.vm.$options.queryData, refetchAfterMutation: true, diff --git a/spec/frontend/ci_variable_list/components/ci_group_variables_spec.js b/spec/frontend/ci_variable_list/components/ci_group_variables_spec.js index 525cba3424b..ef5a86ccb61 100644 --- a/spec/frontend/ci_variable_list/components/ci_group_variables_spec.js +++ b/spec/frontend/ci_variable_list/components/ci_group_variables_spec.js @@ -40,6 +40,7 @@ describe('Ci Group Variable wrapper', () => { areScopedVariablesAvailable: false, componentName: 'GroupVariables', fullPath: mockProvide.groupPath, + hideEnvironmentScope: false, mutationData: wrapper.vm.$options.mutationData, queryData: wrapper.vm.$options.queryData, refetchAfterMutation: false, diff --git a/spec/frontend/ci_variable_list/components/ci_project_variables_spec.js b/spec/frontend/ci_variable_list/components/ci_project_variables_spec.js index 984baa45d91..97051325f59 100644 --- a/spec/frontend/ci_variable_list/components/ci_project_variables_spec.js +++ b/spec/frontend/ci_variable_list/components/ci_project_variables_spec.js @@ -36,6 +36,7 @@ describe('Ci Project Variable wrapper', () => { areScopedVariablesAvailable: true, componentName: 'ProjectVariables', fullPath: mockProvide.projectFullPath, + hideEnvironmentScope: false, mutationData: wrapper.vm.$options.mutationData, queryData: wrapper.vm.$options.queryData, refetchAfterMutation: false, diff --git a/spec/frontend/ci_variable_list/components/ci_variable_modal_spec.js b/spec/frontend/ci_variable_list/components/ci_variable_modal_spec.js index 1ea4e4f833b..e4771f040d1 100644 --- a/spec/frontend/ci_variable_list/components/ci_variable_modal_spec.js +++ b/spec/frontend/ci_variable_list/components/ci_variable_modal_spec.js @@ -39,6 +39,7 @@ describe('Ci variable modal', () => { const defaultProps = { areScopedVariablesAvailable: true, environments: [], + hideEnvironmentScope: false, mode: ADD_VARIABLE_ACTION, selectedVariable: {}, variable: [], @@ -75,6 +76,7 @@ describe('Ci variable modal', () => { const findEnvScopeInput = () => wrapper.findByTestId('environment-scope').findComponent(GlFormInput); const findVariableTypeDropdown = () => wrapper.find('#ci-variable-type'); + const findEnvironmentScopeText = () => wrapper.findByText('Environment scope'); afterEach(() => { wrapper.destroy(); @@ -250,39 +252,83 @@ describe('Ci variable modal', () => { describe('Environment scope', () => { describe('when feature is available', () => { - it('renders the environment dropdown', () => { - createComponent({ - mountFn: mountExtended, - props: { - areScopedVariablesAvailable: true, - }, + describe('and section is not hidden', () => { + beforeEach(() => { + createComponent({ + mountFn: mountExtended, + props: { + areScopedVariablesAvailable: true, + hideEnvironmentScope: false, + }, + }); }); - expect(findCiEnvironmentsDropdown().exists()).toBe(true); - expect(findCiEnvironmentsDropdown().isVisible()).toBe(true); - }); + it('renders the environment dropdown and section title', () => { + expect(findCiEnvironmentsDropdown().exists()).toBe(true); + expect(findCiEnvironmentsDropdown().isVisible()).toBe(true); + expect(findEnvironmentScopeText().exists()).toBe(true); + }); - it('renders a link to documentation on scopes', () => { - createComponent({ mountFn: mountExtended }); + it('renders a link to documentation on scopes', () => { + const link = findEnvScopeLink(); + + expect(link.attributes('title')).toBe(ENVIRONMENT_SCOPE_LINK_TITLE); + expect(link.attributes('href')).toBe(defaultProvide.environmentScopeLink); + }); + }); - const link = findEnvScopeLink(); + describe('and section is hidden', () => { + beforeEach(() => { + createComponent({ + mountFn: mountExtended, + props: { + areScopedVariablesAvailable: true, + hideEnvironmentScope: true, + }, + }); + }); - expect(link.attributes('title')).toBe(ENVIRONMENT_SCOPE_LINK_TITLE); - expect(link.attributes('href')).toBe(defaultProvide.environmentScopeLink); + it('does not renders the environment dropdown and section title', () => { + expect(findCiEnvironmentsDropdown().exists()).toBe(false); + expect(findEnvironmentScopeText().exists()).toBe(false); + }); }); }); describe('when feature is not available', () => { - it('disables the dropdown', () => { - createComponent({ - mountFn: mountExtended, - props: { - areScopedVariablesAvailable: false, - }, + describe('and section is not hidden', () => { + beforeEach(() => { + createComponent({ + mountFn: mountExtended, + props: { + areScopedVariablesAvailable: false, + hideEnvironmentScope: false, + }, + }); }); - expect(findCiEnvironmentsDropdown().exists()).toBe(false); - expect(findEnvScopeInput().attributes('readonly')).toBe('readonly'); + it('disables the dropdown', () => { + expect(findCiEnvironmentsDropdown().exists()).toBe(false); + expect(findEnvironmentScopeText().exists()).toBe(true); + expect(findEnvScopeInput().attributes('readonly')).toBe('readonly'); + }); + }); + + describe('and section is hidden', () => { + beforeEach(() => { + createComponent({ + mountFn: mountExtended, + props: { + areScopedVariablesAvailable: false, + hideEnvironmentScope: true, + }, + }); + }); + + it('hides the dropdown', () => { + expect(findEnvironmentScopeText().exists()).toBe(false); + expect(findCiEnvironmentsDropdown().exists()).toBe(false); + }); }); }); }); diff --git a/spec/frontend/ci_variable_list/components/ci_variable_settings_spec.js b/spec/frontend/ci_variable_list/components/ci_variable_settings_spec.js index 5c77ce71b41..8b5a0f7ae9d 100644 --- a/spec/frontend/ci_variable_list/components/ci_variable_settings_spec.js +++ b/spec/frontend/ci_variable_list/components/ci_variable_settings_spec.js @@ -18,6 +18,7 @@ describe('Ci variable table', () => { const defaultProps = { areScopedVariablesAvailable: true, environments: mapEnvironmentNames(mockEnvs), + hideEnvironmentScope: false, isLoading: false, variables: mockVariablesWithScopes(projectString), }; @@ -56,6 +57,7 @@ describe('Ci variable table', () => { expect(findCiVariableModal().props()).toEqual({ areScopedVariablesAvailable: defaultProps.areScopedVariablesAvailable, environments: defaultProps.environments, + hideEnvironmentScope: defaultProps.hideEnvironmentScope, variables: defaultProps.variables, mode: ADD_VARIABLE_ACTION, selectedVariable: {}, diff --git a/spec/frontend/ci_variable_list/components/ci_variable_shared_spec.js b/spec/frontend/ci_variable_list/components/ci_variable_shared_spec.js index 78c0bd2aa1f..0cc0ee7a9c7 100644 --- a/spec/frontend/ci_variable_list/components/ci_variable_shared_spec.js +++ b/spec/frontend/ci_variable_list/components/ci_variable_shared_spec.js @@ -48,6 +48,7 @@ const mockProvide = { const defaultProps = { areScopedVariablesAvailable: true, + hideEnvironmentScope: false, refetchAfterMutation: false, }; @@ -318,6 +319,7 @@ describe('Ci Variable Shared Component', () => { expect(findCiSettings().props()).toEqual({ areScopedVariablesAvailable: wrapper.props().areScopedVariablesAvailable, + hideEnvironmentScope: defaultProps.hideEnvironmentScope, isLoading: false, variables: wrapper.props().queryData.ciVariables.lookup(mockVariablesValue.data)?.nodes, environments: expectedEnvironments, diff --git a/spec/graphql/mutations/incident_management/timeline_event/create_spec.rb b/spec/graphql/mutations/incident_management/timeline_event/create_spec.rb index 61fb0d9458b..aab21776a99 100644 --- a/spec/graphql/mutations/incident_management/timeline_event/create_spec.rb +++ b/spec/graphql/mutations/incident_management/timeline_event/create_spec.rb @@ -7,7 +7,7 @@ RSpec.describe Mutations::IncidentManagement::TimelineEvent::Create do let_it_be(:project) { create(:project) } let_it_be(:incident) { create(:incident, project: project) } let_it_be(:timeline_event_tag) do - create(:incident_management_timeline_event_tag, project: project) + create(:incident_management_timeline_event_tag, project: project, name: 'Test tag 1') end let(:args) { { note: 'note', occurred_at: Time.current } } @@ -54,6 +54,92 @@ RSpec.describe Mutations::IncidentManagement::TimelineEvent::Create do it_behaves_like 'creating an incident timeline event' end + + context 'when predefined tags are passed' do + let(:args) do + { + note: 'note', + occurred_at: Time.current, + timeline_event_tag_names: ['Start time'] + } + end + + it_behaves_like 'creating an incident timeline event' + + it 'creates and sets the tag on the event' do + timeline_event = resolve[:timeline_event] + + expect(timeline_event.timeline_event_tags.by_names(['Start time']).count).to eq 1 + end + end + + context 'when predefined tags exist' do + let_it_be(:end_time_tag) do + create(:incident_management_timeline_event_tag, project: project, name: 'End time') + end + + let(:args) do + { + note: 'note', + occurred_at: Time.current, + timeline_event_tag_names: ['End time'] + } + end + + it 'does not create a new tag' do + expect { resolve }.not_to change(IncidentManagement::TimelineEventTag, :count) + end + end + + context 'when same tags are tried to be assigned to same timeline event' do + let(:args) do + { + note: 'note', + occurred_at: Time.current, + timeline_event_tag_names: ['Start time', 'Start time'] + } + end + + it 'only assigns the tag once on the event' do + timeline_event = resolve[:timeline_event] + + expect(timeline_event.timeline_event_tags.by_names(['Start time']).count).to eq(1) + expect(timeline_event.timeline_event_tags.count).to eq(1) + end + end + + context 'with case-insentive tags' do + let(:args) do + { + note: 'note', + occurred_at: Time.current, + timeline_event_tag_names: ['tESt tAg 1'] + } + end + + it 'sets the tag on the event' do + timeline_event = resolve[:timeline_event] + + expect(timeline_event.timeline_event_tags.by_names(['Test tag 1']).count).to eq(1) + end + end + + context 'when non-existing tags are passed' do + let(:args) do + { + note: 'note', + occurred_at: Time.current, + timeline_event_tag_names: ['other time'] + } + end + + it_behaves_like 'responding with an incident timeline errors', + errors: ["Following tags don't exist: [\"other time\"]"] + + it 'does not create the timeline event' do + expect { resolve }.not_to change(IncidentManagement::TimelineEvent, :count) + end + end end it_behaves_like 'failing to create an incident timeline event' diff --git a/spec/lib/gitlab/gitaly_client/with_feature_flag_actors_spec.rb b/spec/lib/gitlab/gitaly_client/with_feature_flag_actors_spec.rb index dd54ac0c6ac..41dce5d76dd 100644 --- a/spec/lib/gitlab/gitaly_client/with_feature_flag_actors_spec.rb +++ b/spec/lib/gitlab/gitaly_client/with_feature_flag_actors_spec.rb @@ -190,6 +190,23 @@ RSpec.describe Gitlab::GitalyClient::WithFeatureFlagActors do ) expect(result).to be(call_result) end + + context 'when call without repository_actor' do + before do + allow(service).to receive(:repository_actor).and_return(nil) + allow(Gitlab::ErrorTracking).to receive(:track_and_raise_for_dev_exception).and_call_original + end + + it 'calls error tracking track_and_raise_for_dev_exception' do + expect do + service.gitaly_client_call(call_arg_1, call_arg_2, karg: call_arg_3) + end.to raise_error /gitaly_client_call called without setting repository_actor/ + + expect(Gitlab::ErrorTracking).to have_received(:track_and_raise_for_dev_exception).with( + be_a(Feature::InvalidFeatureFlagError) + ) + end + end end context 'when actors_aware_gitaly_calls not enabled' do @@ -206,5 +223,53 @@ RSpec.describe Gitlab::GitalyClient::WithFeatureFlagActors do expect(result).to be(call_result) end end + + describe '#gitaly_feature_flag_actors' do + let_it_be(:project) { create(:project) } + let(:repository_actor) { project.repository } + + context 'when actors_aware_gitaly_calls flag is enabled' do + let(:user_actor) { instance_double(::User) } + let(:project_actor) { instance_double(Project) } + let(:group_actor) { instance_double(Group) } + + before do + stub_feature_flags(actors_aware_gitaly_calls: true) + + allow(Feature::Gitaly).to receive(:user_actor).and_return(user_actor) + allow(Feature::Gitaly).to receive(:project_actor).with(project).and_return(project_actor) + allow(Feature::Gitaly).to receive(:group_actor).with(project).and_return(group_actor) + end + + it 'returns a hash with collected feature flag actors' do + result = service.gitaly_feature_flag_actors(repository_actor) + expect(result).to eql( + repository: repository_actor, + user: user_actor, + project: project_actor, + group: group_actor + ) + + expect(Feature::Gitaly).to have_received(:user_actor).with(no_args) + expect(Feature::Gitaly).to have_received(:project_actor).with(project) + expect(Feature::Gitaly).to have_received(:group_actor).with(project) + end + end + + context 'when actors_aware_gitaly_calls not enabled' do + before do + stub_feature_flags(actors_aware_gitaly_calls: false) + end + + it 'returns an empty hash' do + expect(Feature::Gitaly).not_to receive(:user_actor) + expect(Feature::Gitaly).not_to receive(:project_actor) + expect(Feature::Gitaly).not_to receive(:group_actor) + + result = service.gitaly_feature_flag_actors(repository_actor) + expect(result).to eql({}) + end + end + end end end diff --git a/spec/lib/gitlab/import_export/import_test_coverage_spec.rb b/spec/lib/gitlab/import_export/import_test_coverage_spec.rb index 51c0008b2b4..b1f5574fba1 100644 --- a/spec/lib/gitlab/import_export/import_test_coverage_spec.rb +++ b/spec/lib/gitlab/import_export/import_test_coverage_spec.rb @@ -96,11 +96,11 @@ RSpec.describe 'Test coverage of the Project Import' do case item when Hash item.each do |k, v| - if (v.is_a?(Array) || v.is_a?(Hash)) && v.present? - new_path = path + [k] - res << new_path - gather_relations(v, res, new_path) - end + next unless (v.is_a?(Array) || v.is_a?(Hash)) && v.present? + + new_path = path + [k] + res << new_path + gather_relations(v, res, new_path) end when Array item.each { |i| gather_relations(i, res, path) } diff --git a/spec/lib/gitlab/instrumentation/redis_cluster_validator_spec.rb b/spec/lib/gitlab/instrumentation/redis_cluster_validator_spec.rb index e4af3f77d5d..58c75bff9dd 100644 --- a/spec/lib/gitlab/instrumentation/redis_cluster_validator_spec.rb +++ b/spec/lib/gitlab/instrumentation/redis_cluster_validator_spec.rb @@ -22,7 +22,7 @@ RSpec.describe Gitlab::Instrumentation::RedisClusterValidator do it do stub_rails_env(env) - args = [:mget, 'foo', 'bar'] + args = [[:mget, 'foo', 'bar']] if should_raise expect { described_class.validate!(args) } @@ -58,7 +58,7 @@ RSpec.describe Gitlab::Instrumentation::RedisClusterValidator do with_them do it do - args = [command] + arguments + args = [[command] + arguments] if should_raise expect { described_class.validate!(args) } @@ -68,13 +68,32 @@ RSpec.describe Gitlab::Instrumentation::RedisClusterValidator do end end end + + where(:arguments, :should_raise) do + [[:get, "foo"], [:get, "bar"]] | true + [[:get, "foo"], [:mget, "foo", "bar"]] | true # mix of single-key and multi-key cmds + [[:get, "{foo}:name"], [:get, "{foo}:profile"]] | false + [[:del, "foo"], [:del, "bar"]] | true + [] | false # pipeline or transaction opened and closed without ops + end + + with_them do + it do + if should_raise + expect { described_class.validate!(arguments) } + .to raise_error(described_class::CrossSlotError) + else + expect { described_class.validate!(arguments) }.not_to raise_error + end + end + end end describe '.allow_cross_slot_commands' do it 'does not raise for invalid arguments' do expect do described_class.allow_cross_slot_commands do - described_class.validate!([:mget, 'foo', 'bar']) + described_class.validate!([[:mget, 'foo', 'bar']]) end end.not_to raise_error end @@ -83,10 +102,10 @@ RSpec.describe Gitlab::Instrumentation::RedisClusterValidator do expect do described_class.allow_cross_slot_commands do described_class.allow_cross_slot_commands do - described_class.validate!([:mget, 'foo', 'bar']) + described_class.validate!([[:mget, 'foo', 'bar']]) end - described_class.validate!([:mget, 'foo', 'bar']) + described_class.validate!([[:mget, 'foo', 'bar']]) end end.not_to raise_error end diff --git a/spec/lib/gitlab/instrumentation/redis_interceptor_spec.rb b/spec/lib/gitlab/instrumentation/redis_interceptor_spec.rb index 5b5516f100b..02c5dfb7521 100644 --- a/spec/lib/gitlab/instrumentation/redis_interceptor_spec.rb +++ b/spec/lib/gitlab/instrumentation/redis_interceptor_spec.rb @@ -57,8 +57,8 @@ RSpec.describe Gitlab::Instrumentation::RedisInterceptor, :clean_gitlab_redis_sh Gitlab::Redis::SharedState.with do |redis| redis.pipelined do |pipeline| - pipeline.call(:get, 'foobar') - pipeline.call(:get, 'foobarbaz') + pipeline.call(:get, '{foobar}buz') + pipeline.call(:get, '{foobar}baz') end end end @@ -103,11 +103,22 @@ RSpec.describe Gitlab::Instrumentation::RedisInterceptor, :clean_gitlab_redis_sh Gitlab::Redis::SharedState.with do |redis| redis.pipelined do |pipeline| - pipeline.call(:get, 'foobar') - pipeline.call(:get, 'foobarbaz') + pipeline.call(:get, '{foobar}:buz') + pipeline.call(:get, '{foobar}baz') end end end + + it 'raises error when keys are not from the same slot' do + expect do + Gitlab::Redis::SharedState.with do |redis| + redis.pipelined do |pipeline| + pipeline.call(:get, 'foo') + pipeline.call(:get, 'bar') + end + end + end.to raise_error(instance_of(Gitlab::Instrumentation::RedisClusterValidator::CrossSlotError)) + end end end diff --git a/spec/lib/gitlab/redis/multi_store_spec.rb b/spec/lib/gitlab/redis/multi_store_spec.rb index 6e3001b38b7..207fe28e84e 100644 --- a/spec/lib/gitlab/redis/multi_store_spec.rb +++ b/spec/lib/gitlab/redis/multi_store_spec.rb @@ -127,17 +127,15 @@ RSpec.describe Gitlab::Redis::MultiStore do end before(:all) do - primary_store.multi do |multi| - multi.set(key1, value1) - multi.set(key2, value2) - multi.sadd(skey, [value1, value2]) - end + primary_store.set(key1, value1) + primary_store.set(key2, value2) + primary_store.sadd?(skey, value1) + primary_store.sadd?(skey, value2) - secondary_store.multi do |multi| - multi.set(key1, value1) - multi.set(key2, value2) - multi.sadd(skey, [value1, value2]) - end + secondary_store.set(key1, value1) + secondary_store.set(key2, value2) + secondary_store.sadd?(skey, value1) + secondary_store.sadd?(skey, value2) end RSpec.shared_examples_for 'reads correct value' do @@ -349,15 +347,11 @@ RSpec.describe Gitlab::Redis::MultiStore do primary_store.flushdb secondary_store.flushdb - primary_store.multi do |multi| - multi.set(key2, value1) - multi.sadd?(skey, value1) - end + primary_store.set(key2, value1) + primary_store.sadd?(skey, value1) - secondary_store.multi do |multi| - multi.set(key2, value1) - multi.sadd?(skey, value1) - end + secondary_store.set(key2, value1) + secondary_store.sadd?(skey, value1) end with_them do diff --git a/spec/lib/gitlab/usage_data_counters/hll_redis_counter_spec.rb b/spec/lib/gitlab/usage_data_counters/hll_redis_counter_spec.rb index d0b935d59dd..08c712889a8 100644 --- a/spec/lib/gitlab/usage_data_counters/hll_redis_counter_spec.rb +++ b/spec/lib/gitlab/usage_data_counters/hll_redis_counter_spec.rb @@ -17,6 +17,7 @@ RSpec.describe Gitlab::UsageDataCounters::HLLRedisCounter, :clean_gitlab_redis_s # Without freezing the time, the test may behave inconsistently # depending on which day of the week test is run. # Monday 6th of June + described_class.clear_memoization(:known_events) reference_time = Time.utc(2020, 6, 1) travel_to(reference_time) { example.run } described_class.clear_memoization(:known_events) diff --git a/spec/models/incident_management/timeline_event_tag_spec.rb b/spec/models/incident_management/timeline_event_tag_spec.rb index 3d3ce08a909..1ec4fa30fb5 100644 --- a/spec/models/incident_management/timeline_event_tag_spec.rb +++ b/spec/models/incident_management/timeline_event_tag_spec.rb @@ -55,5 +55,9 @@ RSpec.describe IncidentManagement::TimelineEventTag do expect(project2.incident_management_timeline_event_tags.by_names(['Test tag 1', 'Test tag 3'])).to contain_exactly(tag3) end + + it 'returns one matching tag with case insensitive' do + expect(described_class.by_names(['tESt tAg 2'])).to contain_exactly(tag2) + end end end diff --git a/spec/models/instance_metadata_spec.rb b/spec/models/instance_metadata_spec.rb index 5fc073c392d..46fd165e065 100644 --- a/spec/models/instance_metadata_spec.rb +++ b/spec/models/instance_metadata_spec.rb @@ -9,7 +9,8 @@ RSpec.describe InstanceMetadata do expect(subject).to have_attributes( version: Gitlab::VERSION, revision: Gitlab.revision, - kas: kind_of(::InstanceMetadata::Kas) + kas: kind_of(::InstanceMetadata::Kas), + enterprise: Gitlab.ee? ) end end diff --git a/spec/presenters/packages/npm/package_presenter_spec.rb b/spec/presenters/packages/npm/package_presenter_spec.rb index 8b99e6d8605..4fa469c7cd2 100644 --- a/spec/presenters/packages/npm/package_presenter_spec.rb +++ b/spec/presenters/packages/npm/package_presenter_spec.rb @@ -86,10 +86,10 @@ RSpec.describe ::Packages::Npm::PackagePresenter do it 'avoids N+1 database queries' do check_n_plus_one(:versions) do create_list(:npm_package, 5, project: project, name: package_name).each do |npm_package| - if has_dependencies - ::Packages::DependencyLink.dependency_types.keys.each do |dependency_type| - create(:packages_dependency_link, package: npm_package, dependency_type: dependency_type) - end + next unless has_dependencies + + ::Packages::DependencyLink.dependency_types.keys.each do |dependency_type| + create(:packages_dependency_link, package: npm_package, dependency_type: dependency_type) end end end diff --git a/spec/requests/api/graphql/metadata_query_spec.rb b/spec/requests/api/graphql/metadata_query_spec.rb index 840bd7c018c..435e1b5b596 100644 --- a/spec/requests/api/graphql/metadata_query_spec.rb +++ b/spec/requests/api/graphql/metadata_query_spec.rb @@ -17,7 +17,8 @@ RSpec.describe 'getting project information' do 'enabled' => Gitlab::Kas.enabled?, 'version' => expected_kas_version, 'externalUrl' => expected_kas_external_url - } + }, + 'enterprise' => Gitlab.ee? } } end diff --git a/spec/requests/jira_connect/cors_preflight_checks_controller_spec.rb b/spec/requests/jira_connect/cors_preflight_checks_controller_spec.rb index aeea77091b1..d441a8575d0 100644 --- a/spec/requests/jira_connect/cors_preflight_checks_controller_spec.rb +++ b/spec/requests/jira_connect/cors_preflight_checks_controller_spec.rb @@ -50,13 +50,6 @@ RSpec.describe JiraConnect::CorsPreflightChecksController do it_behaves_like 'allows cross-origin requests on self managed' end - describe 'OPTIONS /-/jira_connect/subscriptions' do - let(:allowed_methods) { 'GET, POST, OPTIONS' } - let(:path) { '/-/jira_connect/subscriptions' } - - it_behaves_like 'allows cross-origin requests on self managed' - end - describe 'OPTIONS /-/jira_connect/subscriptions/:id' do let(:allowed_methods) { 'DELETE, OPTIONS' } let(:path) { '/-/jira_connect/subscriptions/123' } diff --git a/spec/requests/jira_connect/subscriptions_controller_spec.rb b/spec/requests/jira_connect/subscriptions_controller_spec.rb index 2a0b73d4ab1..b5f3ab916a4 100644 --- a/spec/requests/jira_connect/subscriptions_controller_spec.rb +++ b/spec/requests/jira_connect/subscriptions_controller_spec.rb @@ -28,14 +28,6 @@ RSpec.describe JiraConnect::SubscriptionsController do it { is_expected.to include('http://self-managed-gitlab.com/api/') } it { is_expected.to include('http://self-managed-gitlab.com/oauth/') } - it 'allows cross-origin requests', :aggregate_failures do - get path, params: params, headers: cors_request_headers - - expect(response.headers['Access-Control-Allow-Origin']).to eq 'https://gitlab.com' - expect(response.headers['Access-Control-Allow-Methods']).to eq 'GET, POST, OPTIONS' - expect(response.headers['Access-Control-Allow-Credentials']).to be_nil - end - context 'with no self-managed instance configured' do let_it_be(:installation) { create(:jira_connect_installation, instance_url: '') } @@ -55,34 +47,6 @@ RSpec.describe JiraConnect::SubscriptionsController do end end - describe 'POST /-/jira_connect/subscriptions' do - let_it_be(:installation) { create(:jira_connect_installation, instance_url: 'http://self-managed-gitlab.com') } - let_it_be(:group) { create(:group) } - let_it_be(:user) { create(:user) } - - let(:qsh) do - Atlassian::Jwt.create_query_string_hash('https://gitlab.test/subscriptions', 'GET', 'https://gitlab.test') - end - - let(:jwt) { Atlassian::Jwt.encode({ iss: installation.client_key, qsh: qsh }, installation.shared_secret) } - let(:cors_request_headers) { { 'Origin' => 'http://notgitlab.com' } } - let(:params) { { jwt: jwt, namespace_path: group.path, format: :json } } - - before do - group.add_maintainer(user) - sign_in(user) - stub_application_setting(jira_connect_proxy_url: 'https://gitlab.com') - end - - it 'allows cross-origin requests', :aggregate_failures do - post '/-/jira_connect/subscriptions', params: params, headers: cors_request_headers - - expect(response.headers['Access-Control-Allow-Origin']).to eq 'https://gitlab.com' - expect(response.headers['Access-Control-Allow-Methods']).to eq 'GET, POST, OPTIONS' - expect(response.headers['Access-Control-Allow-Credentials']).to be_nil - end - end - describe 'DELETE /-/jira_connect/subscriptions/:id' do let_it_be(:installation) { create(:jira_connect_installation, instance_url: 'http://self-managed-gitlab.com') } let_it_be(:subscription) { create(:jira_connect_subscription, installation: installation) } diff --git a/spec/services/incident_management/timeline_events/create_service_spec.rb b/spec/services/incident_management/timeline_events/create_service_spec.rb index 877cd41bf83..b10862a78b5 100644 --- a/spec/services/incident_management/timeline_events/create_service_spec.rb +++ b/spec/services/incident_management/timeline_events/create_service_spec.rb @@ -154,6 +154,48 @@ RSpec.describe IncidentManagement::TimelineEvents::CreateService do result = execute.payload[:timeline_event] expect(result.timeline_event_tags.first).to eq(timeline_event_tag) end + + context 'when predefined tags are passed' do + let(:args) do + { + note: 'note', + occurred_at: Time.current, + action: 'new comment', + promoted_from_note: comment, + timeline_event_tag_names: ['start time', 'end time'] + } + end + + it_behaves_like 'success response' + + it 'matches the two tags on the event and creates on project' do + result = execute.payload[:timeline_event] + + expect(result.timeline_event_tags.count).to eq(2) + expect(result.timeline_event_tags.by_names(['Start time', 'End time']).pluck_names) + .to match_array(['Start time', 'End time']) + expect(project.incident_management_timeline_event_tags.pluck_names) + .to include('Start time', 'End time') + end + end + + context 'when invalid tag names are passed' do + let(:args) do + { + note: 'note', + occurred_at: Time.current, + action: 'new comment', + promoted_from_note: comment, + timeline_event_tag_names: ['some other time'] + } + end + + it_behaves_like 'error response', "Following tags don't exist: [\"some other time\"]" + + it 'does not create timeline event' do + expect { execute }.not_to change(IncidentManagement::TimelineEvent, :count) + end + end end context 'with editable param' do diff --git a/spec/tooling/lib/tooling/helm3_client_spec.rb b/spec/tooling/lib/tooling/helm3_client_spec.rb index 41c51ec5754..52d1b5a1567 100644 --- a/spec/tooling/lib/tooling/helm3_client_spec.rb +++ b/spec/tooling/lib/tooling/helm3_client_spec.rb @@ -35,7 +35,7 @@ RSpec.describe Tooling::Helm3Client do describe '#releases' do it 'raises an error if the Helm command fails' do expect(Gitlab::Popen).to receive(:popen_with_detail) - .with([%(helm list --namespace "#{namespace}" --max 256 --offset 0 --output json)]) + .with([%(helm list --max 256 --offset 0 --output json)]) .and_return(Gitlab::Popen::Result.new([], '', '', double(success?: false))) expect { subject.releases.to_a }.to raise_error(described_class::CommandFailedError) @@ -43,7 +43,7 @@ RSpec.describe Tooling::Helm3Client do it 'calls helm list with default arguments' do expect(Gitlab::Popen).to receive(:popen_with_detail) - .with([%(helm list --namespace "#{namespace}" --max 256 --offset 0 --output json)]) + .with([%(helm list --max 256 --offset 0 --output json)]) .and_return(Gitlab::Popen::Result.new([], '', '', double(success?: true))) subject.releases.to_a @@ -51,7 +51,7 @@ RSpec.describe Tooling::Helm3Client do it 'calls helm list with extra arguments' do expect(Gitlab::Popen).to receive(:popen_with_detail) - .with([%(helm list --namespace "#{namespace}" --max 256 --offset 0 --output json --deployed)]) + .with([%(helm list --max 256 --offset 0 --output json --deployed)]) .and_return(Gitlab::Popen::Result.new([], '', '', double(success?: true))) subject.releases(args: ['--deployed']).to_a @@ -59,7 +59,7 @@ RSpec.describe Tooling::Helm3Client do it 'returns a list of Release objects' do expect(Gitlab::Popen).to receive(:popen_with_detail) - .with([%(helm list --namespace "#{namespace}" --max 256 --offset 0 --output json --deployed)]) + .with([%(helm list --max 256 --offset 0 --output json --deployed)]) .and_return(Gitlab::Popen::Result.new([], raw_helm_list_page2, '', double(success?: true))) expect(Gitlab::Popen).to receive(:popen_with_detail).ordered .and_return(Gitlab::Popen::Result.new([], raw_helm_list_empty, '', double(success?: true))) @@ -80,13 +80,13 @@ RSpec.describe Tooling::Helm3Client do it 'automatically paginates releases' do expect(Gitlab::Popen).to receive(:popen_with_detail).ordered - .with([%(helm list --namespace "#{namespace}" --max 256 --offset 0 --output json)]) + .with([%(helm list --max 256 --offset 0 --output json)]) .and_return(Gitlab::Popen::Result.new([], raw_helm_list_page1, '', double(success?: true))) expect(Gitlab::Popen).to receive(:popen_with_detail).ordered - .with([%(helm list --namespace "#{namespace}" --max 256 --offset 256 --output json)]) + .with([%(helm list --max 256 --offset 256 --output json)]) .and_return(Gitlab::Popen::Result.new([], raw_helm_list_page2, '', double(success?: true))) expect(Gitlab::Popen).to receive(:popen_with_detail).ordered - .with([%(helm list --namespace "#{namespace}" --max 256 --offset 512 --output json)]) + .with([%(helm list --max 256 --offset 512 --output json)]) .and_return(Gitlab::Popen::Result.new([], raw_helm_list_empty, '', double(success?: true))) releases = subject.releases.to_a @@ -98,7 +98,7 @@ RSpec.describe Tooling::Helm3Client do describe '#delete' do it 'raises an error if the Helm command fails' do expect(Gitlab::Popen).to receive(:popen_with_detail) - .with([%(helm uninstall --namespace "#{namespace}" #{release_name})]) + .with([%(helm uninstall #{release_name})]) .and_return(Gitlab::Popen::Result.new([], '', '', double(success?: false))) expect { subject.delete(release_name: release_name) }.to raise_error(described_class::CommandFailedError) @@ -106,7 +106,7 @@ RSpec.describe Tooling::Helm3Client do it 'calls helm uninstall with default arguments' do expect(Gitlab::Popen).to receive(:popen_with_detail) - .with([%(helm uninstall --namespace "#{namespace}" #{release_name})]) + .with([%(helm uninstall #{release_name})]) .and_return(Gitlab::Popen::Result.new([], '', '', double(success?: true))) expect(subject.delete(release_name: release_name)).to eq('') @@ -117,16 +117,16 @@ RSpec.describe Tooling::Helm3Client do it 'raises an error if the Helm command fails' do expect(Gitlab::Popen).to receive(:popen_with_detail) - .with([%(helm uninstall --namespace "#{namespace}" #{release_name.join(' ')})]) - .and_return(Gitlab::Popen::Result.new([], '', '', double(success?: false))) + .with([%(helm uninstall #{release_name.join(' ')})]) + .and_return(Gitlab::Popen::Result.new([], '', '', double(success?: false))) expect { subject.delete(release_name: release_name) }.to raise_error(described_class::CommandFailedError) end it 'calls helm uninstall with multiple release names' do expect(Gitlab::Popen).to receive(:popen_with_detail) - .with([%(helm uninstall --namespace "#{namespace}" #{release_name.join(' ')})]) - .and_return(Gitlab::Popen::Result.new([], '', '', double(success?: true))) + .with([%(helm uninstall #{release_name.join(' ')})]) + .and_return(Gitlab::Popen::Result.new([], '', '', double(success?: true))) expect(subject.delete(release_name: release_name)).to eq('') end diff --git a/spec/views/devise/confirmations/almost_there.html.haml_spec.rb b/spec/views/devise/confirmations/almost_there.html.haml_spec.rb new file mode 100644 index 00000000000..c091efe9295 --- /dev/null +++ b/spec/views/devise/confirmations/almost_there.html.haml_spec.rb @@ -0,0 +1,37 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe 'devise/confirmations/almost_there' do + describe 'confirmations text' do + subject { render(template: 'devise/confirmations/almost_there') } + + before do + allow(view).to receive(:params).and_return(email: email) + end + + context 'when correct email' do + let(:email) { 'こんにちは@test' } + + specify do + subject + + expect(rendered).to have_content( + "Please check your email (#{email}) to confirm your account" + ) + end + end + + context 'when random text' do + let(:email) { 'random text' } + + specify do + subject + + expect(rendered).to have_content( + 'Please check your email to confirm your account' + ) + end + end + end +end diff --git a/tooling/lib/tooling/helm3_client.rb b/tooling/lib/tooling/helm3_client.rb index 82ebe3f51dc..d83dbeac76b 100644 --- a/tooling/lib/tooling/helm3_client.rb +++ b/tooling/lib/tooling/helm3_client.rb @@ -37,7 +37,6 @@ module Tooling def delete(release_name:) run_command([ 'uninstall', - %(--namespace "#{namespace}"), release_name ]) end @@ -60,7 +59,6 @@ module Tooling def raw_releases(page, args = []) command = [ 'list', - %(--namespace "#{namespace}"), %(--max #{PAGINATION_SIZE}), %(--offset #{PAGINATION_SIZE * page}), %(--output json), diff --git a/yarn.lock b/yarn.lock index 3e1e1b88281..1a2f200dd15 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1108,19 +1108,19 @@ stylelint-declaration-strict-value "1.8.0" stylelint-scss "4.2.0" -"@gitlab/svgs@3.7.0": - version "3.7.0" - resolved "https://registry.yarnpkg.com/@gitlab/svgs/-/svgs-3.7.0.tgz#1257b69fb9898ea5614f992aa6b6dc3619c3c38c" - integrity sha512-6vTqWZzY63ZUTUqk0dmMDcfU27qtkAu0WmlK4e3FMWmISvTxNhAk2j11c/YlLauf6okE4W2T2fnhvXp1mzcPgA== +"@gitlab/svgs@3.8.0": + version "3.8.0" + resolved "https://registry.yarnpkg.com/@gitlab/svgs/-/svgs-3.8.0.tgz#bc7fa51e345e26cff56fdff629ea439adfa1e0cb" + integrity sha512-DUWeG2Vx+1ntZ/1GT6S36ZOtXvM5Wm02MtDRrQS4GuOX4rkTeG9aoutSJuwQ2h9BNtxl0U/jkf5GVBxacj18XA== -"@gitlab/ui@49.3.0": - version "49.3.0" - resolved "https://registry.yarnpkg.com/@gitlab/ui/-/ui-49.3.0.tgz#63e6a375d66c6f6ae568f0d1a08fe0e9bd4e355b" - integrity sha512-c8GSajEdW2Q1ME7lYuQgImR493WaELKJOq/T+1zVs3i82cc1YDWbGEJyKZh6srJ6xNSLuIbn6d7oSqfM/jeSAQ== +"@gitlab/ui@49.10.0": + version "49.10.0" + resolved "https://registry.yarnpkg.com/@gitlab/ui/-/ui-49.10.0.tgz#54715c18d3e06f313b572c5b9b807622f7a35b19" + integrity sha512-hBkU5TIdc2bzqe4P2X/BZXgQQQa+Sp4A5eWiKK+FVuCx5l1To2q4EyHHPMn292AJx7Y23qM2jP4GMphbs2wtDg== dependencies: "@popperjs/core" "^2.11.2" bootstrap-vue "2.20.1" - dompurify "^2.4.0" + dompurify "^2.4.1" echarts "^5.3.2" iframe-resizer "^4.3.2" lodash "^4.17.20" @@ -4992,7 +4992,7 @@ dompurify@2.3.8: resolved "https://registry.yarnpkg.com/dompurify/-/dompurify-2.3.8.tgz#224fe9ae57d7ebd9a1ae1ac18c1c1ca3f532226f" integrity sha512-eVhaWoVibIzqdGYjwsBWodIQIaXFSB+cKDf4cfxLMsK0xiud6SE+/WCVx/Xw/UwQsa4cS3T2eITcdtmTg2UKcw== -dompurify@^2.4.0, dompurify@^2.4.1: +dompurify@^2.4.1: version "2.4.1" resolved "https://registry.yarnpkg.com/dompurify/-/dompurify-2.4.1.tgz#f9cb1a275fde9af6f2d0a2644ef648dd6847b631" integrity sha512-ewwFzHzrrneRjxzmK6oVz/rZn9VWspGFRDb4/rRtIsM1n36t9AKma/ye8syCpcw+XJ25kOK/hOG7t1j2I2yBqA== |