diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2022-12-02 18:07:23 +0000 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2022-12-02 18:07:23 +0000 |
commit | af833d9730dd367984b55ef02ccc3fe6eb83f0e4 (patch) | |
tree | f525dae9e43b8cb77d8eaad38f998ad06b8a769d | |
parent | 20ddcb963c756f5c7df26046adb01a8e325a26cd (diff) | |
download | gitlab-ce-af833d9730dd367984b55ef02ccc3fe6eb83f0e4.tar.gz |
Add latest changes from gitlab-org/gitlab@master
151 files changed, 1232 insertions, 622 deletions
diff --git a/.gitignore b/.gitignore index ad7595dc7f2..6441e798332 100644 --- a/.gitignore +++ b/.gitignore @@ -73,6 +73,7 @@ eslint-report.html /.gitlab_workhorse_secret /.gitlab_pages_secret /.gitlab_kas_secret +/.gitlab_suggested_reviewers_secret /webpack-report/ /crystalball/ /test_results/ diff --git a/.rubocop_todo/lint/constant_definition_in_block.yml b/.rubocop_todo/lint/constant_definition_in_block.yml index 813e0b22559..ff3f6b2afbc 100644 --- a/.rubocop_todo/lint/constant_definition_in_block.yml +++ b/.rubocop_todo/lint/constant_definition_in_block.yml @@ -1,8 +1,6 @@ --- Lint/ConstantDefinitionInBlock: - # Offense count: 111 - # Temporarily disabled due to too many offenses - Enabled: false + Details: grace period Exclude: - 'app/models/concerns/ignorable_columns.rb' - 'app/models/concerns/partitioned_table.rb' diff --git a/.rubocop_todo/performance/bind_call.yml b/.rubocop_todo/performance/bind_call.yml deleted file mode 100644 index 732aecc3910..00000000000 --- a/.rubocop_todo/performance/bind_call.yml +++ /dev/null @@ -1,10 +0,0 @@ ---- -# Cop supports --autocorrect. -Performance/BindCall: - Exclude: - - 'app/services/metrics/dashboard/grafana_metric_embed_service.rb' - - 'ee/spec/features/issues/form_spec.rb' - - 'lib/gitlab/patch/prependable.rb' - - 'spec/features/issues/form_spec.rb' - - 'spec/mailers/notify_spec.rb' - - 'spec/support/patches/rspec_mocks_prepended_methods.rb' diff --git a/.rubocop_todo/rspec/hooks_before_examples.yml b/.rubocop_todo/rspec/hooks_before_examples.yml index 01756f4d8c3..333687e1cc4 100644 --- a/.rubocop_todo/rspec/hooks_before_examples.yml +++ b/.rubocop_todo/rspec/hooks_before_examples.yml @@ -44,29 +44,3 @@ RSpec/HooksBeforeExamples: - 'qa/qa/specs/features/ee/browser_ui/1_manage/user/minimal_access_user_spec.rb' - 'qa/spec/resource/reusable_collection_spec.rb' - 'qa/spec/specs/runner_spec.rb' - - 'spec/controllers/import/github_controller_spec.rb' - - 'spec/features/projects/pages/user_configures_pages_pipeline_spec.rb' - - 'spec/graphql/resolvers/design_management/design_resolver_spec.rb' - - 'spec/graphql/resolvers/design_management/designs_resolver_spec.rb' - - 'spec/graphql/resolvers/project_pipeline_resolver_spec.rb' - - 'spec/lib/banzai/filter/inline_grafana_metrics_filter_spec.rb' - - 'spec/lib/banzai/filter/references/user_reference_filter_spec.rb' - - 'spec/lib/feature/definition_spec.rb' - - 'spec/lib/gitlab/auth/saml/user_spec.rb' - - 'spec/lib/gitlab/email/handler/create_issue_handler_spec.rb' - - 'spec/lib/gitlab/email/handler/create_merge_request_handler_spec.rb' - - 'spec/lib/gitlab/email/handler/create_note_handler_spec.rb' - - 'spec/lib/gitlab/email/hook/disable_email_interceptor_spec.rb' - - 'spec/lib/gitlab/tracking/event_definition_spec.rb' - - 'spec/lib/gitlab/usage/metric_definition_spec.rb' - - 'spec/lib/gitlab/usage_data_counters/hll_redis_counter_spec.rb' - - 'spec/lib/mattermost/session_spec.rb' - - 'spec/models/ci/build_trace_chunk_spec.rb' - - 'spec/models/clusters/applications/ingress_spec.rb' - - 'spec/models/clusters/applications/knative_spec.rb' - - 'spec/models/integrations/chat_message/pipeline_message_spec.rb' - - 'spec/models/repository_spec.rb' - - 'spec/serializers/pipeline_details_entity_spec.rb' - - 'spec/services/feature_flags/hook_service_spec.rb' - - 'spec/services/projects/container_repository/gitlab/delete_tags_service_spec.rb' - - 'spec/support/shared_examples/lib/gitlab/cycle_analytics/deployment_metrics.rb' diff --git a/app/assets/javascripts/boards/components/board_content.vue b/app/assets/javascripts/boards/components/board_content.vue index 5bc6ce105b9..ca86894ca40 100644 --- a/app/assets/javascripts/boards/components/board_content.vue +++ b/app/assets/javascripts/boards/components/board_content.vue @@ -116,6 +116,8 @@ export default { group: 'boards-list', tag: 'div', value: this.boardListsToUse, + delay: 100, + delayOnTouchOnly: true, }; return this.canDragColumns ? options : {}; diff --git a/app/assets/javascripts/boards/components/board_list.vue b/app/assets/javascripts/boards/components/board_list.vue index 6561958b9bf..215691c7ba2 100644 --- a/app/assets/javascripts/boards/components/board_list.vue +++ b/app/assets/javascripts/boards/components/board_list.vue @@ -133,6 +133,8 @@ export default { 'ghost-class': 'board-card-drag-active', 'data-list-id': this.list.id, value: this.boardItems, + delay: 100, + delayOnTouchOnly: true, }; return this.canMoveIssue ? options : {}; diff --git a/app/assets/javascripts/boards/components/boards_selector.vue b/app/assets/javascripts/boards/components/boards_selector.vue index eaf3facb450..4f90d77c0be 100644 --- a/app/assets/javascripts/boards/components/boards_selector.vue +++ b/app/assets/javascripts/boards/components/boards_selector.vue @@ -237,7 +237,7 @@ export default { :text="board.name" @show="loadBoards" > - <p class="gl-new-dropdown-header-top" @mousedown.prevent> + <p class="gl-dropdown-header-top" @mousedown.prevent> {{ s__('IssueBoards|Switch board') }} </p> <gl-search-box-by-type ref="searchBox" v-model="filterTerm" class="m-2" /> diff --git a/app/assets/javascripts/content_editor/components/bubble_menus/code_block_bubble_menu.vue b/app/assets/javascripts/content_editor/components/bubble_menus/code_block_bubble_menu.vue index a9668ebdb69..98b7203778f 100644 --- a/app/assets/javascripts/content_editor/components/bubble_menus/code_block_bubble_menu.vue +++ b/app/assets/javascripts/content_editor/components/bubble_menus/code_block_bubble_menu.vue @@ -166,9 +166,7 @@ export default { icon="arrow-left" @click.prevent.stop="showCustomLanguageInput = false" /> - <p - class="gl-text-center gl-new-dropdown-header-top gl-mb-0! gl-border-none! gl-pb-1!" - > + <p class="gl-text-center gl-dropdown-header-top gl-mb-0! gl-border-none! gl-pb-1!"> {{ __('Create custom type') }} </p> </div> diff --git a/app/assets/javascripts/content_editor/components/suggestions_dropdown.vue b/app/assets/javascripts/content_editor/components/suggestions_dropdown.vue index 001b34a00fa..37e6ef61d50 100644 --- a/app/assets/javascripts/content_editor/components/suggestions_dropdown.vue +++ b/app/assets/javascripts/content_editor/components/suggestions_dropdown.vue @@ -210,10 +210,10 @@ export default { <template> <ul :class="{ show: items.length > 0 }" - class="gl-new-dropdown dropdown-menu gl-relative" + class="gl-dropdown dropdown-menu gl-relative" data-testid="content-editor-suggestions-dropdown" > - <div class="gl-new-dropdown-inner gl-overflow-y-auto"> + <div class="gl-dropdown-inner gl-overflow-y-auto"> <gl-dropdown-item v-for="(item, index) in items" ref="dropdownItems" diff --git a/app/assets/javascripts/editor/schema/ci.json b/app/assets/javascripts/editor/schema/ci.json index 5facd858f62..21c50acbf5d 100644 --- a/app/assets/javascripts/editor/schema/ci.json +++ b/app/assets/javascripts/editor/schema/ci.json @@ -41,6 +41,9 @@ "before_script": { "$ref": "#/definitions/before_script" }, + "hooks": { + "$ref": "#/definitions/hooks" + }, "cache": { "$ref": "#/definitions/cache" }, @@ -1217,6 +1220,9 @@ "after_script": { "$ref": "#/definitions/after_script" }, + "hooks": { + "$ref": "#/definitions/hooks" + }, "rules": { "$ref": "#/definitions/rules" }, @@ -1881,6 +1887,39 @@ } ] } + }, + "hooks": { + "type": "object", + "markdownDescription": "Specifies lists of commands to execute on the runner at certain stages of job execution. [Learn More](https://docs.gitlab.com/ee/ci/yaml/#hooks).", + "properties": { + "pre_get_sources_script": { + "markdownDescription": "Specifies a list of commands to execute on the runner before updating the Git repository and any submodules. [Learn More](https://docs.gitlab.com/ee/ci/yaml/#hookspre_get_sources_script).", + "oneOf": [ + { + "type": "string", + "minLength": 1 + }, + { + "type": "array", + "items": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "minItems": 1 + } + ] + } + }, + "additionalProperties": false } } -}
\ No newline at end of file +} diff --git a/app/assets/javascripts/error_tracking/components/error_tracking_list.vue b/app/assets/javascripts/error_tracking/components/error_tracking_list.vue index de4b11699fc..122c7c005e9 100644 --- a/app/assets/javascripts/error_tracking/components/error_tracking_list.vue +++ b/app/assets/javascripts/error_tracking/components/error_tracking_list.vue @@ -357,7 +357,7 @@ export default { > <span class="d-flex"> <gl-icon - class="gl-new-dropdown-item-check-icon" + class="gl-dropdown-item-check-icon" :class="{ invisible: !isCurrentStatusFilter(status) }" name="mobile-issue-close" /> @@ -374,7 +374,7 @@ export default { > <span class="d-flex"> <gl-icon - class="gl-new-dropdown-item-check-icon" + class="gl-dropdown-item-check-icon" :class="{ invisible: !isCurrentSortField(field) }" name="mobile-issue-close" /> diff --git a/app/assets/javascripts/merge_request.js b/app/assets/javascripts/merge_request.js index 57b5e9809d2..a77f339f5a1 100644 --- a/app/assets/javascripts/merge_request.js +++ b/app/assets/javascripts/merge_request.js @@ -173,7 +173,7 @@ MergeRequest.toggleDraftStatus = function (title, isReady) { ); draftToggle.setAttribute('href', url); - draftToggle.querySelector('.gl-new-dropdown-item-text-wrapper').textContent = isReady + draftToggle.querySelector('.gl-dropdown-item-text-wrapper').textContent = isReady ? __('Mark as draft') : __('Mark as ready'); }); diff --git a/app/assets/javascripts/pipelines/components/pipeline_mini_graph/pipeline_stage.vue b/app/assets/javascripts/pipelines/components/pipeline_mini_graph/pipeline_stage.vue index 98f558c7df5..93110c8e3db 100644 --- a/app/assets/javascripts/pipelines/components/pipeline_mini_graph/pipeline_stage.vue +++ b/app/assets/javascripts/pipelines/components/pipeline_mini_graph/pipeline_stage.vue @@ -162,7 +162,7 @@ export default { /> </li> <template v-if="isMergeTrain"> - <li class="gl-new-dropdown-divider" role="presentation"> + <li class="gl-dropdown-divider" role="presentation"> <hr role="separator" aria-orientation="horizontal" class="dropdown-divider" /> </li> <li> diff --git a/app/assets/javascripts/sidebar/components/lock/issuable_lock_form.vue b/app/assets/javascripts/sidebar/components/lock/issuable_lock_form.vue index 1ccc31b6962..cdce6617591 100644 --- a/app/assets/javascripts/sidebar/components/lock/issuable_lock_form.vue +++ b/app/assets/javascripts/sidebar/components/lock/issuable_lock_form.vue @@ -111,9 +111,9 @@ export default { </script> <template> - <li v-if="isMergeRequest" class="gl-new-dropdown-item"> + <li v-if="isMergeRequest" class="gl-dropdown-item"> <button type="button" class="dropdown-item" @click="toggleLocked"> - <span class="gl-new-dropdown-item-text-wrapper"> + <span class="gl-dropdown-item-text-wrapper"> <template v-if="isLocked"> {{ __('Unlock merge request') }} </template> diff --git a/app/assets/javascripts/sidebar/components/subscriptions/sidebar_subscriptions_widget.vue b/app/assets/javascripts/sidebar/components/subscriptions/sidebar_subscriptions_widget.vue index eb38de73237..0fba1cb5e4e 100644 --- a/app/assets/javascripts/sidebar/components/subscriptions/sidebar_subscriptions_widget.vue +++ b/app/assets/javascripts/sidebar/components/subscriptions/sidebar_subscriptions_widget.vue @@ -182,7 +182,7 @@ export default { </script> <template> - <gl-dropdown-form v-if="isMergeRequest" class="gl-new-dropdown-item"> + <gl-dropdown-form v-if="isMergeRequest" class="gl-dropdown-item"> <div class="gl-px-5 gl-pb-2 gl-pt-1"> <gl-toggle :value="subscribed" diff --git a/app/assets/javascripts/vue_shared/alert_details/components/alert_status.vue b/app/assets/javascripts/vue_shared/alert_details/components/alert_status.vue index 672761af1cf..8d2ef20b381 100644 --- a/app/assets/javascripts/vue_shared/alert_details/components/alert_status.vue +++ b/app/assets/javascripts/vue_shared/alert_details/components/alert_status.vue @@ -106,7 +106,7 @@ export default { @keydown.esc.native="$emit('hide-dropdown')" @hide="$emit('hide-dropdown')" > - <p v-if="isSidebar" class="gl-new-dropdown-header-top" data-testid="dropdown-header"> + <p v-if="isSidebar" class="gl-dropdown-header-top" data-testid="dropdown-header"> {{ s__('AlertManagement|Assign status') }} </p> <div class="dropdown-content dropdown-body"> diff --git a/app/assets/javascripts/vue_shared/alert_details/components/sidebar/sidebar_assignees.vue b/app/assets/javascripts/vue_shared/alert_details/components/sidebar/sidebar_assignees.vue index 72dcc16b57a..4ec301b946b 100644 --- a/app/assets/javascripts/vue_shared/alert_details/components/sidebar/sidebar_assignees.vue +++ b/app/assets/javascripts/vue_shared/alert_details/components/sidebar/sidebar_assignees.vue @@ -242,7 +242,7 @@ export default { @keydown.esc.native="hideDropdown" @hide="hideDropdown" > - <p class="gl-new-dropdown-header-top"> + <p class="gl-dropdown-header-top"> {{ __('Assign To') }} </p> <gl-search-box-by-type v-model.trim="search" :placeholder="__('Search users')" /> diff --git a/app/assets/javascripts/vue_shared/components/actions_button.vue b/app/assets/javascripts/vue_shared/components/actions_button.vue index c6c22f9c61f..e3b22bcaf98 100644 --- a/app/assets/javascripts/vue_shared/components/actions_button.vue +++ b/app/assets/javascripts/vue_shared/components/actions_button.vue @@ -70,7 +70,7 @@ export default { @click="handleClick(selectedAction, $event)" > <template #button-content> - <span class="gl-new-dropdown-button-text" v-bind="selectedAction.attrs"> + <span class="gl-dropdown-button-text" v-bind="selectedAction.attrs"> {{ selectedAction.text }} </span> </template> diff --git a/app/assets/javascripts/vue_shared/components/listbox_input/listbox_input.stories.js b/app/assets/javascripts/vue_shared/components/listbox_input/listbox_input.stories.js new file mode 100644 index 00000000000..4106de371cb --- /dev/null +++ b/app/assets/javascripts/vue_shared/components/listbox_input/listbox_input.stories.js @@ -0,0 +1,26 @@ +import ListboxInput from './listbox_input.vue'; + +export default { + component: ListboxInput, + title: 'vue_shared/listbox_input', +}; + +const Template = (args, { argTypes }) => ({ + components: { ListboxInput }, + data() { + return { selected: null }; + }, + props: Object.keys(argTypes), + template: '<listbox-input v-model="selected" v-bind="$props" />', +}); + +export const Default = Template.bind({}); +Default.args = { + name: 'input_name', + defaultToggleText: 'Select an option', + items: [ + { text: 'Option 1', value: '1' }, + { text: 'Option 2', value: '2' }, + { text: 'Option 3', value: '3' }, + ], +}; diff --git a/app/assets/javascripts/vue_shared/components/listbox_input/listbox_input.vue b/app/assets/javascripts/vue_shared/components/listbox_input/listbox_input.vue new file mode 100644 index 00000000000..b1809e6a9f3 --- /dev/null +++ b/app/assets/javascripts/vue_shared/components/listbox_input/listbox_input.vue @@ -0,0 +1,110 @@ +<script> +import { GlListbox } from '@gitlab/ui'; +import { __ } from '~/locale'; + +const MIN_ITEMS_COUNT_FOR_SEARCHING = 20; + +export default { + i18n: { + noResultsText: __('No results found'), + }, + components: { + GlListbox, + }, + model: GlListbox.model, + props: { + name: { + type: String, + required: true, + }, + defaultToggleText: { + type: String, + required: true, + }, + selected: { + type: String, + required: false, + default: null, + }, + items: { + type: GlListbox.props.items.type, + required: true, + }, + }, + data() { + return { + searchString: '', + }; + }, + computed: { + allOptions() { + const allOptions = []; + + const getOptions = (options) => { + for (let i = 0; i < options.length; i += 1) { + const option = options[i]; + if (option.options) { + getOptions(option.options); + } else { + allOptions.push(option); + } + } + }; + getOptions(this.items); + + return allOptions; + }, + isGrouped() { + return this.items.some((item) => item.options !== undefined); + }, + isSearchable() { + return this.allOptions.length > MIN_ITEMS_COUNT_FOR_SEARCHING; + }, + filteredItems() { + const searchString = this.searchString.toLowerCase(); + + if (!searchString) { + return this.items; + } + + if (this.isGrouped) { + return this.items + .map(({ text, options }) => { + return { + text, + options: options.filter((option) => option.text.toLowerCase().includes(searchString)), + }; + }) + .filter(({ options }) => options.length); + } + + return this.items.filter((item) => item.text.toLowerCase().includes(searchString)); + }, + toggleText() { + return this.selected + ? this.allOptions.find((option) => option.value === this.selected).text + : this.defaultToggleText; + }, + }, + methods: { + search(searchString) { + this.searchString = searchString; + }, + }, +}; +</script> + +<template> + <div> + <gl-listbox + :selected="selected" + :toggle-text="toggleText" + :items="filteredItems" + :searchable="isSearchable" + :no-results-text="$options.i18n.noResultsText" + @search="search" + @select="$emit($options.model.event, $event)" + /> + <input ref="input" type="hidden" :name="name" :value="selected" /> + </div> +</template> diff --git a/app/assets/stylesheets/_page_specific_files.scss b/app/assets/stylesheets/_page_specific_files.scss index 6878e9a10d7..addb5f8b9c2 100644 --- a/app/assets/stylesheets/_page_specific_files.scss +++ b/app/assets/stylesheets/_page_specific_files.scss @@ -10,7 +10,6 @@ @import './pages/login'; @import './pages/ml_experiment_tracking'; @import './pages/merge_requests'; -@import './pages/monitor'; @import './pages/note_form'; @import './pages/notes'; @import './pages/pipelines'; diff --git a/app/assets/stylesheets/components/ref_selector.scss b/app/assets/stylesheets/components/ref_selector.scss index ded911c2492..f7a9367499e 100644 --- a/app/assets/stylesheets/components/ref_selector.scss +++ b/app/assets/stylesheets/components/ref_selector.scss @@ -6,7 +6,7 @@ width: 20rem; &, - .gl-new-dropdown-inner { + .gl-dropdown-inner { max-height: $dropdown-max-height-lg; } } diff --git a/app/assets/stylesheets/framework/dropdowns.scss b/app/assets/stylesheets/framework/dropdowns.scss index 3cd6db25d0e..0acda85f527 100644 --- a/app/assets/stylesheets/framework/dropdowns.scss +++ b/app/assets/stylesheets/framework/dropdowns.scss @@ -5,7 +5,7 @@ // for Snippets is introduced and Clone button is relocated, we won't // need this style. // Issue for the refactoring: https://gitlab.com/gitlab-org/gitlab/-/issues/213327 - &.gl-new-dropdown button.dropdown-toggle { + &.gl-dropdown button.dropdown-toggle { @include gl-display-inline-flex; } @@ -41,7 +41,7 @@ max-height: $extended-max-height; // See comment below for explanation - .gl-new-dropdown-inner { + .gl-dropdown-inner { max-height: $extended-max-height - 2px; } } @@ -54,12 +54,12 @@ width: 100%; } - // `GlDropdown` specifies the `max-height` of `.gl-new-dropdown-inner` + // `GlDropdown` specifies the `max-height` of `.gl-dropdown-inner` // as `$dropdown-max-height`, but the `max-height` rule above forces // the parent `.dropdown-menu` to be _slightly_ too small because of // the 1px borders. The workaround below overrides the @gitlab/ui style // to avoid a double scroll bar. - .gl-new-dropdown-inner { + .gl-dropdown-inner { max-height: $dropdown-max-height - 2px; } } @@ -1027,7 +1027,7 @@ // This class won't be needed once we can add a prop for this in the GitLab UI component // https://gitlab.com/gitlab-org/gitlab-ui/-/issues/966 -.gl-new-dropdown { +.gl-dropdown { .gl-dropdown-menu-wide { width: $gl-dropdown-width-wide; } @@ -1035,7 +1035,7 @@ // This class won't be needed once we can add a prop for this in the GitLab UI component // https://gitlab.com/gitlab-org/gitlab-ui/-/issues/966 -.gl-new-dropdown.gl-dropdown-menu-full-width { +.gl-dropdown.gl-dropdown-menu-full-width { .dropdown-menu { width: 100%; } diff --git a/app/assets/stylesheets/framework/emojis.scss b/app/assets/stylesheets/framework/emojis.scss index 68a3493670d..16ad6f62c64 100644 --- a/app/assets/stylesheets/framework/emojis.scss +++ b/app/assets/stylesheets/framework/emojis.scss @@ -36,7 +36,7 @@ gl-emoji { } } -.emoji-picker .gl-new-dropdown .dropdown-menu { +.emoji-picker .gl-dropdown .dropdown-menu { width: 350px; } @@ -48,6 +48,6 @@ gl-emoji { border-bottom-color: var(--gl-theme-accent, $theme-indigo-500); } -.emoji-picker .gl-new-dropdown-inner > :last-child { +.emoji-picker .gl-dropdown-inner > :last-child { padding-bottom: 0; } diff --git a/app/assets/stylesheets/framework/header.scss b/app/assets/stylesheets/framework/header.scss index ed41d10f3b2..9d518786eac 100644 --- a/app/assets/stylesheets/framework/header.scss +++ b/app/assets/stylesheets/framework/header.scss @@ -555,7 +555,7 @@ } .top-nav-container-view { - .gl-new-dropdown & .gl-search-box-by-type { + .gl-dropdown & .gl-search-box-by-type { @include gl-m-0; } diff --git a/app/assets/stylesheets/framework/typography.scss b/app/assets/stylesheets/framework/typography.scss index 2c2d8a2b592..d8f0375358c 100644 --- a/app/assets/stylesheets/framework/typography.scss +++ b/app/assets/stylesheets/framework/typography.scss @@ -587,7 +587,7 @@ } } - .gl-new-dropdown-item { + .gl-dropdown-item { margin: 0; padding: 0; line-height: 1rem; diff --git a/app/assets/stylesheets/framework/variables.scss b/app/assets/stylesheets/framework/variables.scss index 97ad342997b..6086405226e 100644 --- a/app/assets/stylesheets/framework/variables.scss +++ b/app/assets/stylesheets/framework/variables.scss @@ -789,11 +789,6 @@ $login-brand-holder-color: #888; $project-option-descr-color: #54565b; /* - * Monitor Charts - */ -$chart-tooltip-max-width: 512px; - -/* Stat Graph */ $stat-graph-common-bg: #f3f3f3; diff --git a/app/assets/stylesheets/page_bundles/alert_management_details.scss b/app/assets/stylesheets/page_bundles/alert_management_details.scss index 2eaf4517710..d67dadafa9e 100644 --- a/app/assets/stylesheets/page_bundles/alert_management_details.scss +++ b/app/assets/stylesheets/page_bundles/alert_management_details.scss @@ -28,7 +28,7 @@ @include gl-pt-8; } - .gl-new-dropdown-item-text-wrapper { + .gl-dropdown-item-text-wrapper { @include gl-py-0; } } diff --git a/app/assets/stylesheets/page_bundles/clusters.scss b/app/assets/stylesheets/page_bundles/clusters.scss index 4f29ff4b1ad..2f5c40de6e4 100644 --- a/app/assets/stylesheets/page_bundles/clusters.scss +++ b/app/assets/stylesheets/page_bundles/clusters.scss @@ -6,7 +6,7 @@ @include gl-w-full; order: -1; - .gl-new-dropdown, + .gl-dropdown, .split-content-button { @include gl-w-full; } diff --git a/app/assets/stylesheets/page_bundles/merge_requests.scss b/app/assets/stylesheets/page_bundles/merge_requests.scss index c2e6294f0c2..eb1d86b02df 100644 --- a/app/assets/stylesheets/page_bundles/merge_requests.scss +++ b/app/assets/stylesheets/page_bundles/merge_requests.scss @@ -999,7 +999,7 @@ $tabs-holder-z-index: 250; max-width: 650px; max-height: calc(100vh - 50px); - .gl-new-dropdown-inner { + .gl-dropdown-inner { max-height: none !important; } @@ -1034,7 +1034,7 @@ $tabs-holder-z-index: 250; } } - .gl-new-dropdown-contents { + .gl-dropdown-contents { padding: $gl-spacing-scale-4 !important; } diff --git a/app/assets/stylesheets/page_bundles/oncall_schedules.scss b/app/assets/stylesheets/page_bundles/oncall_schedules.scss index 91fd2d42657..b995724ec7c 100644 --- a/app/assets/stylesheets/page_bundles/oncall_schedules.scss +++ b/app/assets/stylesheets/page_bundles/oncall_schedules.scss @@ -9,7 +9,7 @@ @include gl-w-full; } - .gl-new-dropdown-item-text-primary { + .gl-dropdown-item-text-primary { @include gl-overflow-hidden; @include gl-text-overflow-ellipsis; } diff --git a/app/assets/stylesheets/pages/monitor.scss b/app/assets/stylesheets/pages/monitor.scss deleted file mode 100644 index 25ff5abd774..00000000000 --- a/app/assets/stylesheets/pages/monitor.scss +++ /dev/null @@ -1,5 +0,0 @@ -.chart-tooltip > .popover { - min-width: 0; - width: max-content; - max-width: $chart-tooltip-max-width; -} diff --git a/app/assets/stylesheets/pages/projects.scss b/app/assets/stylesheets/pages/projects.scss index bf20204cfd9..a119c815cf4 100644 --- a/app/assets/stylesheets/pages/projects.scss +++ b/app/assets/stylesheets/pages/projects.scss @@ -562,7 +562,7 @@ // Remove once gitlab/ui solution is implemented // https://gitlab.com/gitlab-org/gitlab-ui/-/issues/1158 // https://gitlab.com/gitlab-org/gitlab/-/issues/300405 - .gl-new-dropdown-button-text { + .gl-dropdown-button-text { @include str-truncated; } } diff --git a/app/assets/stylesheets/pages/search.scss b/app/assets/stylesheets/pages/search.scss index 1bca04e5eb1..bec9efe2bf4 100644 --- a/app/assets/stylesheets/pages/search.scss +++ b/app/assets/stylesheets/pages/search.scss @@ -283,7 +283,7 @@ input[type='checkbox']:hover { .btn-search, .btn-success, .dropdown-menu-toggle, - .gl-new-dropdown { + .gl-dropdown { width: 100%; margin-top: 5px; @@ -302,7 +302,7 @@ input[type='checkbox']:hover { } .dropdown-menu-toggle, - .gl-new-dropdown { + .gl-dropdown { @include media-breakpoint-up(sm) { width: 180px; margin-top: 0; @@ -317,7 +317,7 @@ input[type='checkbox']:hover { } .dropdown-menu-toggle, - .gl-new-dropdown { + .gl-dropdown { @include media-breakpoint-up(lg) { width: 240px; } diff --git a/app/helpers/listbox_helper.rb b/app/helpers/listbox_helper.rb index 16caf862c7b..0aaeb39c82d 100644 --- a/app/helpers/listbox_helper.rb +++ b/app/helpers/listbox_helper.rb @@ -1,9 +1,9 @@ # frozen_string_literal: true module ListboxHelper - DROPDOWN_CONTAINER_CLASSES = %w[dropdown b-dropdown gl-new-dropdown btn-group js-redirect-listbox].freeze + DROPDOWN_CONTAINER_CLASSES = %w[dropdown b-dropdown gl-dropdown btn-group js-redirect-listbox].freeze DROPDOWN_BUTTON_CLASSES = %w[btn dropdown-toggle btn-default btn-md gl-button gl-dropdown-toggle].freeze - DROPDOWN_INNER_CLASS = 'gl-new-dropdown-button-text' + DROPDOWN_INNER_CLASS = 'gl-dropdown-button-text' DROPDOWN_ICON_CLASS = 'gl-button-icon dropdown-chevron gl-icon' # Creates a listbox component with redirect behavior. diff --git a/app/models/concerns/has_user_type.rb b/app/models/concerns/has_user_type.rb index 8e371f2a9c1..158d7d1c9bb 100644 --- a/app/models/concerns/has_user_type.rb +++ b/app/models/concerns/has_user_type.rb @@ -14,10 +14,22 @@ module HasUserType migration_bot: 7, security_bot: 8, automation_bot: 9, - admin_bot: 11 + admin_bot: 11, + suggested_reviewers_bot: 12 }.with_indifferent_access.freeze - BOT_USER_TYPES = %w[alert_bot project_bot support_bot visual_review_bot migration_bot security_bot automation_bot admin_bot].freeze + BOT_USER_TYPES = %w[ + alert_bot + project_bot + support_bot + visual_review_bot + migration_bot + security_bot + automation_bot + admin_bot + suggested_reviewers_bot + ].freeze + NON_INTERNAL_USER_TYPES = %w[human project_bot service_user].freeze INTERNAL_USER_TYPES = (USER_TYPES.keys - NON_INTERNAL_USER_TYPES).freeze diff --git a/app/services/clusters/applications/check_progress_service.rb b/app/services/clusters/applications/check_progress_service.rb deleted file mode 100644 index 4a07b955f8e..00000000000 --- a/app/services/clusters/applications/check_progress_service.rb +++ /dev/null @@ -1,50 +0,0 @@ -# frozen_string_literal: true - -module Clusters - module Applications - class CheckProgressService < BaseHelmService - def execute - return unless operation_in_progress? - - case pod_phase - when Gitlab::Kubernetes::Pod::SUCCEEDED - on_success - when Gitlab::Kubernetes::Pod::FAILED - on_failed - else - check_timeout - end - rescue Kubeclient::HttpError => e - log_error(e) - - app.make_errored!(_('Kubernetes error: %{error_code}') % { error_code: e.error_code }) - end - - private - - def operation_in_progress? - raise NotImplementedError - end - - def on_success - raise NotImplementedError - end - - def pod_name - raise NotImplementedError - end - - def on_failed - app.make_errored!(_('Operation failed. Check pod logs for %{pod_name} for more details.') % { pod_name: pod_name }) - end - - def timed_out? - raise NotImplementedError - end - - def pod_phase - helm_api.status(pod_name) - end - end - end -end diff --git a/app/services/metrics/dashboard/grafana_metric_embed_service.rb b/app/services/metrics/dashboard/grafana_metric_embed_service.rb index e94c8d92c3a..26ccded45f8 100644 --- a/app/services/metrics/dashboard/grafana_metric_embed_service.rb +++ b/app/services/metrics/dashboard/grafana_metric_embed_service.rb @@ -51,8 +51,7 @@ module Metrics # being passed to #get_dashboard (which accepts none) ::Metrics::Dashboard::BaseService .instance_method(:get_dashboard) - .bind(self) - .call() # rubocop:disable Style/MethodCallWithoutArgsParentheses + .bind_call(self) end def cache_key(*args) diff --git a/app/views/admin/projects/index.html.haml b/app/views/admin/projects/index.html.haml index f23a688dd48..950642525db 100644 --- a/app/views/admin/projects/index.html.haml +++ b/app/views/admin/projects/index.html.haml @@ -20,7 +20,7 @@ - namespace = Namespace.find(params[:namespace_id]) - current_namespace = "#{namespace.kind}: #{namespace.full_path}" %button.dropdown-menu-toggle.btn.btn-default.btn-md.gl-button.js-namespace-select{ data: { show_any: 'true', field_name: 'namespace_id', placeholder: current_namespace, update_location: 'true' }, type: 'button' } - %span.gl-new-dropdown-button-text + %span.gl-dropdown-button-text = current_namespace = render 'shared/projects/dropdown' diff --git a/app/views/admin/projects/show.html.haml b/app/views/admin/projects/show.html.haml index a60c3996cf2..652b526ef64 100644 --- a/app/views/admin/projects/show.html.haml +++ b/app/views/admin/projects/show.html.haml @@ -143,7 +143,7 @@ .col-sm-9 - placeholder = _('Search for Namespace') %button.dropdown-menu-toggle.btn.btn-default.btn-md.gl-button.js-namespace-select{ data: { field_name: 'new_namespace_id', placeholder: placeholder }, type: 'button' } - %span.gl-new-dropdown-button-text + %span.gl-dropdown-button-text = placeholder .form-group.row diff --git a/app/views/projects/buttons/_clone.html.haml b/app/views/projects/buttons/_clone.html.haml index 34aecd31c57..a755cb9f5b0 100644 --- a/app/views/projects/buttons/_clone.html.haml +++ b/app/views/projects/buttons/_clone.html.haml @@ -27,28 +27,28 @@ = render_if_exists 'projects/buttons/geo' = render_if_exists 'projects/buttons/kerberos_clone_field' %li.divider.mt-2 - %li.pt-2.gl-new-dropdown-item + %li.pt-2.gl-dropdown-item %label.label-bold{ class: 'gl-px-4!' } = _('Open in your IDE') - if ssh_enabled? - escaped_ssh_url_to_repo = CGI.escape(project.ssh_url_to_repo) %a.dropdown-item.open-with-link{ href: 'vscode://vscode.git/clone?url=' + escaped_ssh_url_to_repo } - .gl-new-dropdown-item-text-wrapper + .gl-dropdown-item-text-wrapper = _('Visual Studio Code (SSH)') - if http_enabled? - escaped_http_url_to_repo = CGI.escape(project.http_url_to_repo) %a.dropdown-item.open-with-link{ href: 'vscode://vscode.git/clone?url=' + escaped_http_url_to_repo } - .gl-new-dropdown-item-text-wrapper + .gl-dropdown-item-text-wrapper = _('Visual Studio Code (HTTPS)') - if ssh_enabled? %a.dropdown-item.open-with-link{ href: 'jetbrains://idea/checkout/git?idea.required.plugins.id=Git4Idea&checkout.repo=' + escaped_ssh_url_to_repo } - .gl-new-dropdown-item-text-wrapper + .gl-dropdown-item-text-wrapper = _('IntelliJ IDEA (SSH)') - if http_enabled? %a.dropdown-item.open-with-link{ href: 'jetbrains://idea/checkout/git?idea.required.plugins.id=Git4Idea&checkout.repo=' + escaped_http_url_to_repo } - .gl-new-dropdown-item-text-wrapper + .gl-dropdown-item-text-wrapper = _('IntelliJ IDEA (HTTPS)') - if show_xcode_link?(@project) %a.dropdown-item.open-with-link{ href: xcode_uri_to_repo(@project) } - .gl-new-dropdown-item-text-wrapper + .gl-dropdown-item-text-wrapper = _("Xcode") diff --git a/app/views/projects/buttons/_download.html.haml b/app/views/projects/buttons/_download.html.haml index 23dcb7f41e1..1fbc399c3ff 100644 --- a/app/views/projects/buttons/_download.html.haml +++ b/app/views/projects/buttons/_download.html.haml @@ -4,7 +4,7 @@ - if !project.empty_repo? && can?(current_user, :download_code, project) - archive_prefix = "#{project.path}-#{ref.tr('/', '-')}" - .project-action-button.dropdown.gl-new-dropdown.inline> + .project-action-button.dropdown.gl-dropdown.inline> %button.gl-button.btn.btn-default.dropdown-toggle.gl-dropdown-toggle.dropdown-icon-only.has-tooltip{ title: s_('DownloadSource|Download'), 'data-toggle' => 'dropdown', 'aria-label' => s_('DownloadSource|Download'), 'data-display' => 'static', data: { qa_selector: 'download_source_code_button' } } = sprite_icon('download', css_class: 'gl-icon dropdown-icon') %span.sr-only= _('Select Archive Format') diff --git a/app/views/projects/merge_requests/_close_reopen_draft_report_toggle.html.haml b/app/views/projects/merge_requests/_close_reopen_draft_report_toggle.html.haml index 78fce3f7087..9b7d63797dd 100644 --- a/app/views/projects/merge_requests/_close_reopen_draft_report_toggle.html.haml +++ b/app/views/projects/merge_requests/_close_reopen_draft_report_toggle.html.haml @@ -1,53 +1,53 @@ - display_issuable_type = issuable_display_type(@merge_request) -.btn-group.gl-md-ml-3.gl-display-flex.dropdown.gl-new-dropdown.gl-md-w-auto.gl-w-full +.btn-group.gl-md-ml-3.gl-display-flex.dropdown.gl-dropdown.gl-md-w-auto.gl-w-full = button_tag type: 'button', class: "btn dropdown-toggle btn-default btn-md gl-button gl-dropdown-toggle btn-default-tertiary dropdown-icon-only dropdown-toggle-no-caret has-tooltip gl-display-none! gl-md-display-inline-flex!", data: { toggle: 'dropdown', title: _('Merge request actions'), testid: 'merge-request-actions', 'aria-label': _('Merge request actions') } do = sprite_icon "ellipsis_v", size: 16, css_class: "dropdown-icon gl-icon" = button_tag type: 'button', class: "btn dropdown-toggle btn-default btn-md btn-block gl-button gl-dropdown-toggle gl-md-display-none!", data: { 'toggle' => 'dropdown' } do - %span.gl-new-dropdown-button-text= _('Merge request actions') + %span.gl-dropdown-button-text= _('Merge request actions') = sprite_icon "chevron-down", size: 16, css_class: "dropdown-icon gl-icon" .dropdown-menu.dropdown-menu-right - .gl-new-dropdown-inner - .gl-new-dropdown-contents + .gl-dropdown-inner + .gl-dropdown-contents %ul - if current_user && moved_mr_sidebar_enabled? - %li.gl-new-dropdown-item.js-sidebar-subscriptions-widget-root - %li.gl-new-dropdown-divider + %li.gl-dropdown-item.js-sidebar-subscriptions-widget-root + %li.gl-dropdown-divider %hr.dropdown-divider - if can?(current_user, :update_merge_request, @merge_request) - %li.gl-new-dropdown-item{ class: "gl-md-display-none!" } + %li.gl-dropdown-item{ class: "gl-md-display-none!" } = link_to edit_project_merge_request_path(@project, @merge_request), class: 'dropdown-item' do - .gl-new-dropdown-item-text-wrapper + .gl-dropdown-item-text-wrapper = _('Edit') - if @merge_request.open? - %li.gl-new-dropdown-item + %li.gl-dropdown-item = link_to toggle_draft_merge_request_path(@merge_request), method: :put, class: 'dropdown-item js-draft-toggle-button' do - .gl-new-dropdown-item-text-wrapper + .gl-dropdown-item-text-wrapper = @merge_request.draft? ? _('Mark as ready') : _('Mark as draft') - %li.gl-new-dropdown-item.js-close-item + %li.gl-dropdown-item.js-close-item = link_to close_issuable_path(@merge_request), method: :put, class: 'dropdown-item' do - .gl-new-dropdown-item-text-wrapper + .gl-dropdown-item-text-wrapper = _('Close') = display_issuable_type - elsif !@merge_request.source_project_missing? && @merge_request.closed? - %li.gl-new-dropdown-item + %li.gl-dropdown-item = link_to reopen_issuable_path(@merge_request), method: :put, class: 'dropdown-item' do - .gl-new-dropdown-item-text-wrapper + .gl-dropdown-item-text-wrapper = _('Reopen') = display_issuable_type - if moved_mr_sidebar_enabled? - %li.gl-new-dropdown-item.js-sidebar-lock-root - %li.gl-new-dropdown-item + %li.gl-dropdown-item.js-sidebar-lock-root + %li.gl-dropdown-item %button.dropdown-item.js-copy-reference{ type: "button", data: { 'clipboard-text': @merge_request.to_reference(full: true) } } - .gl-new-dropdown-item-text-wrapper + .gl-dropdown-item-text-wrapper = _('Copy reference') - unless current_controller?('conflicts') - unless issuable_author_is_current_user(@merge_request) - if moved_mr_sidebar_enabled? - %li.gl-new-dropdown-divider + %li.gl-dropdown-divider %hr.dropdown-divider - %li.gl-new-dropdown-item + %li.gl-dropdown-item = link_to new_abuse_report_path(user_id: @merge_request.author.id, ref_url: merge_request_url(@merge_request)), class: 'dropdown-item' do - .gl-new-dropdown-item-text-wrapper + .gl-dropdown-item-text-wrapper = _('Report abuse') diff --git a/app/views/projects/merge_requests/_code_dropdown.html.haml b/app/views/projects/merge_requests/_code_dropdown.html.haml index 5c7fe56095c..2ef89a7bf04 100644 --- a/app/views/projects/merge_requests/_code_dropdown.html.haml +++ b/app/views/projects/merge_requests/_code_dropdown.html.haml @@ -1,39 +1,39 @@ -.gl-md-ml-3.dropdown.gl-new-dropdown{ class: "gl-display-none! gl-md-display-flex!" } +.gl-md-ml-3.dropdown.gl-dropdown{ class: "gl-display-none! gl-md-display-flex!" } #js-check-out-modal{ data: how_merge_modal_data(@merge_request) } = button_tag type: 'button', class: "btn dropdown-toggle btn-confirm gl-button gl-dropdown-toggle", data: { toggle: 'dropdown', qa_selector: 'mr_code_dropdown' } do - %span.gl-new-dropdown-button-text= _('Code') + %span.gl-dropdown-button-text= _('Code') = sprite_icon "chevron-down", size: 16, css_class: "dropdown-icon gl-icon gl-ml-2 gl-mr-0!" .dropdown-menu.dropdown-menu-right - .gl-new-dropdown-inner - .gl-new-dropdown-contents + .gl-dropdown-inner + .gl-dropdown-contents %ul - %li.gl-new-dropdown-section-header + %li.gl-dropdown-section-header %header.dropdown-header = _('Review changes') - %li.gl-new-dropdown-item + %li.gl-dropdown-item %button.dropdown-item.js-check-out-modal-trigger{ type: 'button' } - .gl-new-dropdown-item-text-wrapper + .gl-dropdown-item-text-wrapper = _('Check out branch') - if current_user - %li.gl-new-dropdown-item + %li.gl-dropdown-item = link_to ide_merge_request_path(@merge_request), class: 'dropdown-item', target: '_blank', data: { qa_selector: 'open_in_web_ide_button' } do - .gl-new-dropdown-item-text-wrapper + .gl-dropdown-item-text-wrapper = _('Open in Web IDE') - if Gitlab::CurrentSettings.gitpod_enabled && current_user&.gitpod_enabled - %li.gl-new-dropdown-item + %li.gl-dropdown-item = link_to "#{Gitlab::CurrentSettings.gitpod_url}##{merge_request_url(@merge_request)}", target: '_blank', class: 'dropdown-item' do - .gl-new-dropdown-item-text-wrapper + .gl-dropdown-item-text-wrapper = _('Open in Gitpod') - %li.gl-new-dropdown-divider + %li.gl-dropdown-divider %hr.dropdown-divider - %li.gl-new-dropdown-section-header + %li.gl-dropdown-section-header %header.dropdown-header = _('Download') - %li.gl-new-dropdown-item + %li.gl-dropdown-item = link_to merge_request_path(@merge_request, format: :patch), class: 'dropdown-item', download: '', data: { qa_selector: 'download_email_patches_menu_item' } do - .gl-new-dropdown-item-text-wrapper + .gl-dropdown-item-text-wrapper = _('Email patches') - %li.gl-new-dropdown-item + %li.gl-dropdown-item = link_to merge_request_path(@merge_request, format: :diff), class: 'dropdown-item', download: '', data: { qa_selector: 'download_plain_diff_menu_item' } do - .gl-new-dropdown-item-text-wrapper + .gl-dropdown-item-text-wrapper = _('Plain diff') diff --git a/app/views/shared/_new_project_item_select.html.haml b/app/views/shared/_new_project_item_select.html.haml index 0bd5d1795d0..d080d8be8fe 100644 --- a/app/views/shared/_new_project_item_select.html.haml +++ b/app/views/shared/_new_project_item_select.html.haml @@ -1,5 +1,5 @@ - if any_projects?(@projects) - .dropdown.b-dropdown.gl-new-dropdown.btn-group.project-item-select-holder{ class: 'gl-display-inline-flex!' } + .dropdown.b-dropdown.gl-dropdown.btn-group.project-item-select-holder{ class: 'gl-display-inline-flex!' } %a.btn.gl-button.btn-confirm.split-content-button.js-new-project-item-link.block-truncated{ href: '', data: { label: local_assigns[:label], type: local_assigns[:type] } } = gl_loading_icon(inline: true, color: 'light') = project_select_tag :project_path, class: "project-item-select gl-absolute! gl-visibility-hidden", data: { include_groups: local_assigns[:include_groups], order_by: 'last_activity_at', relative_path: local_assigns[:path], with_shared: local_assigns[:with_shared], include_projects_in_subgroups: local_assigns[:include_projects_in_subgroups] }, with_feature_enabled: local_assigns[:with_feature_enabled] diff --git a/app/views/shared/web_hooks/_test_button.html.haml b/app/views/shared/web_hooks/_test_button.html.haml index 3ffa45f01be..7a78a32fe87 100644 --- a/app/views/shared/web_hooks/_test_button.html.haml +++ b/app/views/shared/web_hooks/_test_button.html.haml @@ -2,12 +2,12 @@ - hook = local_assigns.fetch(:hook) - triggers = hook.class.triggers -.hook-test-button.dropdown.gl-new-dropdown.inline> +.hook-test-button.dropdown.gl-dropdown.inline> %button.btn.gl-button{ 'data-toggle' => 'dropdown', class: button_class } = _('Test') = sprite_icon('chevron-down') %ul.dropdown-menu.dropdown-menu-right{ role: 'menu' } - .gl-new-dropdown-inner + .gl-dropdown-inner - triggers.each_value do |event| - %li.gl-new-dropdown-item + %li.gl-dropdown-item = link_to_test_hook(hook, event) diff --git a/app/workers/all_queues.yml b/app/workers/all_queues.yml index 8cce80e2771..40c74e9429f 100644 --- a/app/workers/all_queues.yml +++ b/app/workers/all_queues.yml @@ -1801,7 +1801,7 @@ :urgency: :low :resource_boundary: :unknown :weight: 4 - :idempotent: false + :idempotent: true :tags: [] - :name: pipeline_default:ci_create_cross_project_pipeline :worker_name: Ci::CreateCrossProjectPipelineWorker @@ -2721,15 +2721,6 @@ :weight: 1 :idempotent: true :tags: [] -- :name: merge_requests_delete_branch - :worker_name: MergeRequests::DeleteBranchWorker - :feature_category: :source_code_management - :has_external_dependencies: false - :urgency: :high - :resource_boundary: :unknown - :weight: 1 - :idempotent: true - :tags: [] - :name: merge_requests_delete_source_branch :worker_name: MergeRequests::DeleteSourceBranchWorker :feature_category: :source_code_management @@ -3000,6 +2991,15 @@ :weight: 1 :idempotent: true :tags: [] +- :name: projects_delete_branch + :worker_name: Projects::DeleteBranchWorker + :feature_category: :source_code_management + :has_external_dependencies: false + :urgency: :high + :resource_boundary: :unknown + :weight: 1 + :idempotent: true + :tags: [] - :name: projects_git_garbage_collect :worker_name: Projects::GitGarbageCollectWorker :feature_category: :gitaly diff --git a/app/workers/merge_requests/delete_branch_worker.rb b/app/workers/merge_requests/delete_branch_worker.rb deleted file mode 100644 index 6816f9a4b77..00000000000 --- a/app/workers/merge_requests/delete_branch_worker.rb +++ /dev/null @@ -1,27 +0,0 @@ -# frozen_string_literal: true - -module MergeRequests - class DeleteBranchWorker - include ApplicationWorker - - data_consistency :always - - feature_category :source_code_management - urgency :high - idempotent! - - def perform(merge_request_id, user_id, branch_name, retarget_branch) - merge_request = MergeRequest.find_by_id(merge_request_id) - user = User.find_by_id(user_id) - - return unless merge_request.present? && user.present? - - ::Branches::DeleteService.new(merge_request.source_project, user).execute(branch_name) - - return unless retarget_branch - - ::MergeRequests::RetargetChainService.new(project: merge_request.source_project, current_user: user) - .execute(merge_request) - end - end -end diff --git a/app/workers/merge_requests/delete_source_branch_worker.rb b/app/workers/merge_requests/delete_source_branch_worker.rb index 96dde413d5b..da1eca067a9 100644 --- a/app/workers/merge_requests/delete_source_branch_worker.rb +++ b/app/workers/merge_requests/delete_source_branch_worker.rb @@ -19,13 +19,14 @@ class MergeRequests::DeleteSourceBranchWorker return if merge_request.source_branch_sha != source_branch_sha if Feature.enabled?(:add_delete_branch_worker, merge_request.source_project) - ::MergeRequests::DeleteBranchWorker.perform_async(merge_request_id, user_id, merge_request.source_branch, true) + ::Projects::DeleteBranchWorker.new.perform(merge_request.source_project.id, user_id, + merge_request.source_branch) else ::Branches::DeleteService.new(merge_request.source_project, user).execute(merge_request.source_branch) - - ::MergeRequests::RetargetChainService.new(project: merge_request.source_project, current_user: user) - .execute(merge_request) end + + ::MergeRequests::RetargetChainService.new(project: merge_request.source_project, current_user: user) + .execute(merge_request) rescue ActiveRecord::RecordNotFound end end diff --git a/app/workers/projects/delete_branch_worker.rb b/app/workers/projects/delete_branch_worker.rb new file mode 100644 index 00000000000..1949fb67e83 --- /dev/null +++ b/app/workers/projects/delete_branch_worker.rb @@ -0,0 +1,30 @@ +# frozen_string_literal: true + +module Projects + class DeleteBranchWorker + include ApplicationWorker + + data_consistency :always + + feature_category :source_code_management + urgency :high + idempotent! + + def perform(project_id, user_id, branch_name) + project = Project.find_by_id(project_id) + user = User.find_by_id(user_id) + + return unless project.present? && user.present? + return unless project.repository.branch_exists?(branch_name) + + delete_service_result = ::Branches::DeleteService.new(project, user) + .execute(branch_name) + + return unless Feature.enabled?(:track_and_raise_delete_source_errors, project) + # Only want to raise on 400 to avoid permission and non existant branch error + return unless delete_service_result[:http_status] == 400 + + delete_service_result.track_and_raise_exception + end + end +end diff --git a/app/workers/run_pipeline_schedule_worker.rb b/app/workers/run_pipeline_schedule_worker.rb index 8974ddce47b..f31f006eec1 100644 --- a/app/workers/run_pipeline_schedule_worker.rb +++ b/app/workers/run_pipeline_schedule_worker.rb @@ -10,6 +10,8 @@ class RunPipelineScheduleWorker # rubocop:disable Scalability/IdempotentWorker queue_namespace :pipeline_creation feature_category :continuous_integration + deduplicate :until_executed + idempotent! def perform(schedule_id, user_id) schedule = Ci::PipelineSchedule.find_by_id(schedule_id) diff --git a/config/feature_categories.yml b/config/feature_categories.yml index 0aa77bdb66b..e3b208cd495 100644 --- a/config/feature_categories.yml +++ b/config/feature_categories.yml @@ -25,6 +25,7 @@ - cluster_cost_management - code_quality - code_review +- code_suggestions - code_testing - commerce_integrations - compliance_management diff --git a/config/feature_flags/development/disable_pagination_counts_on_jobs_api.yml b/config/feature_flags/development/disable_pagination_counts_on_jobs_api.yml new file mode 100644 index 00000000000..b4774fdf2c7 --- /dev/null +++ b/config/feature_flags/development/disable_pagination_counts_on_jobs_api.yml @@ -0,0 +1,8 @@ +--- +name: disable_pagination_counts_on_jobs_api +introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/105814 +rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/384399 +milestone: '15.7' +type: development +group: group::pipeline execution +default_enabled: false diff --git a/config/feature_flags/development/markdown_image_attributes.yml b/config/feature_flags/development/markdown_image_attributes.yml new file mode 100644 index 00000000000..ddc2ca6ac63 --- /dev/null +++ b/config/feature_flags/development/markdown_image_attributes.yml @@ -0,0 +1,8 @@ +--- +name: markdown_image_attributes +introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/104904 +rollout_issue_url: +milestone: '15.7' +type: development +group: group::project management +default_enabled: false diff --git a/config/feature_flags/development/actors_aware_gitaly_calls.yml b/config/feature_flags/development/track_and_raise_delete_source_errors.yml index 0ae6140c579..5f34ab47f19 100644 --- a/config/feature_flags/development/actors_aware_gitaly_calls.yml +++ b/config/feature_flags/development/track_and_raise_delete_source_errors.yml @@ -1,8 +1,8 @@ --- -name: actors_aware_gitaly_calls -introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/101218 -rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/381516 +name: track_and_raise_delete_source_errors +introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/103842 +rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/382440 milestone: '15.6' type: development -group: group::gitaly +group: group::code review default_enabled: false diff --git a/config/feature_flags/ops/suggested_reviewers_internal_api.yml b/config/feature_flags/ops/suggested_reviewers_internal_api.yml new file mode 100644 index 00000000000..ed3045765d5 --- /dev/null +++ b/config/feature_flags/ops/suggested_reviewers_internal_api.yml @@ -0,0 +1,8 @@ +--- +name: suggested_reviewers_internal_api +introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/103881 +rollout_issue_url: +milestone: '15.7' +type: ops +group: group::applied ml +default_enabled: true diff --git a/config/gitlab.yml.example b/config/gitlab.yml.example index 732d46d284b..0435e66b720 100644 --- a/config/gitlab.yml.example +++ b/config/gitlab.yml.example @@ -1258,6 +1258,11 @@ production: &base # The URL to the Kubernetes API proxy (used by GitLab users) # external_k8s_proxy_url: https://localhost:8154 # default: nil + gitlab_suggested_reviewers: + # File that contains the secret key for verifying access for gitlab-suggested-reviewers. + # Default is '.gitlab_suggested_reviewers_secret' relative to Rails.root (i.e. root of the GitLab app). + # secret_file: /home/git/gitlab/.gitlab_suggested_reviewers_secret + ## GitLab Elasticsearch settings elasticsearch: indexer_path: /home/git/gitlab-elasticsearch-indexer/ diff --git a/config/initializers/1_settings.rb b/config/initializers/1_settings.rb index d0a67c13622..dd5770447c3 100644 --- a/config/initializers/1_settings.rb +++ b/config/initializers/1_settings.rb @@ -853,6 +853,12 @@ Settings.gitlab_kas['internal_url'] ||= 'grpc://localhost:8153' # Settings.gitlab_kas['external_k8s_proxy_url'] ||= 'grpc://localhost:8154' # NOTE: Do not set a default until all distributions have been updated with a correct value # +# GitLab Suggested Reviewers +# +Settings['gitlab_suggested_reviewers'] ||= Settingslogic.new({}) +Settings.gitlab_suggested_reviewers['secret_file'] ||= Rails.root.join('.gitlab_suggested_reviewers_secret') + +# # Repositories # Settings['repositories'] ||= Settingslogic.new({}) diff --git a/config/initializers/gitlab_suggested_reviewers_secret.rb b/config/initializers/gitlab_suggested_reviewers_secret.rb new file mode 100644 index 00000000000..01c7299c414 --- /dev/null +++ b/config/initializers/gitlab_suggested_reviewers_secret.rb @@ -0,0 +1,5 @@ +# frozen_string_literal: true + +return unless Gitlab.com? && Gitlab.ee? + +Gitlab::AppliedMl::SuggestedReviewers.ensure_secret! diff --git a/config/sidekiq_queues.yml b/config/sidekiq_queues.yml index 46305512a7a..5b1b78b3046 100644 --- a/config/sidekiq_queues.yml +++ b/config/sidekiq_queues.yml @@ -303,8 +303,6 @@ - 1 - - merge_requests_create_approval_note - 1 -- - merge_requests_delete_branch - - 1 - - merge_requests_delete_source_branch - 1 - - merge_requests_execute_approval_hooks @@ -403,6 +401,8 @@ - 1 - - projects_after_import - 1 +- - projects_delete_branch + - 1 - - projects_git_garbage_collect - 1 - - projects_import_export_parallel_project_export diff --git a/doc/ci/yaml/index.md b/doc/ci/yaml/index.md index 3f5e11129f9..52422cba111 100644 --- a/doc/ci/yaml/index.md +++ b/doc/ci/yaml/index.md @@ -86,6 +86,7 @@ of the listed keywords use the value defined in the `default` section. - [`artifacts`](#artifacts) - [`before_script`](#before_script) - [`cache`](#cache) +- [`hooks`](#hooks) - [`image`](#image) - [`interruptible`](#interruptible) - [`retry`](#retry) @@ -1862,6 +1863,47 @@ rspec: - [Reuse configuration sections by using `extends`](yaml_optimization.md#use-extends-to-reuse-configuration-sections). - Use `extends` to reuse configuration from [included configuration files](yaml_optimization.md#use-extends-and-include-together). +### `hooks` + +> Introduced in GitLab 15.6 [with a flag](../../administration/feature_flags.md) named `ci_hooks_pre_get_sources_script`. Disabled by default. + +FLAG: +On self-managed GitLab, by default this feature is not available. To make it available, +ask an administrator to [enable the feature flag](../../administration/feature_flags.md) named `ci_hooks_pre_get_sources_script`. +The feature is not ready for production use. + +Use `hooks` to specify lists of commands to execute on the runner +at certain stages of job execution, like before retrieving the Git repository. + +**Keyword type**: Job keyword. You can use it only as part of a job or in the +[`default` section](#default). + +**Possible inputs**: + +- A hash of hooks and their commands. Available hooks: `pre_get_sources_script`. + +#### `hooks:pre_get_sources_script` + +> Introduced in GitLab 15.6 [with a flag](../../administration/feature_flags.md) named `ci_hooks_pre_get_sources_script`. Disabled by default. + +Use `hooks:pre_get_sources_script` to specify a list of commands to execute on the runner +before retrieving the Git repository and any submodules. You can use it +to adjust the Git client configuration first, for example. + +**Related topics**: + +- [GitLab Runner configuration](https://docs.gitlab.com/runner/configuration/advanced-configuration.html#the-runners-section) + +**Example of `hooks:pre_get_sources_script`**: + +```yaml +job1: + hooks: + pre_get_sources_script: + - echo 'hello job1 pre_get_sources_script' + script: echo 'hello job1 script' +``` + ### `image` Use `image` to specify a Docker image that the job runs in. diff --git a/doc/operations/incident_management/alerts.md b/doc/operations/incident_management/alerts.md index 44e619ed166..69b74127891 100644 --- a/doc/operations/incident_management/alerts.md +++ b/doc/operations/incident_management/alerts.md @@ -149,7 +149,7 @@ To change an alert's status: 1. On the right sidebar, select **Edit**. 1. Select a status. -To stop email notifications for alert reoccurrences in projects with [email notifications enabled](paging.md#email-notifications-for-alerts), +To stop email notifications for alert recurrences in projects with [email notifications enabled](paging.md#email-notifications-for-alerts), change the alert's status away from **Triggered**. #### Resolve an alert by closing the linked incident diff --git a/doc/operations/incident_management/linked_resources.md b/doc/operations/incident_management/linked_resources.md index 40b2bbdc757..eb289076424 100644 --- a/doc/operations/incident_management/linked_resources.md +++ b/doc/operations/incident_management/linked_resources.md @@ -15,9 +15,9 @@ you can add linked resources to an incident issue. Resources you might want link to: -- Zoom meetings -- Slack channels or threads -- Google Docs +- The incident Slack channel +- Zoom meeting +- Resources for resolving the incidents ## View linked resources of an incident diff --git a/doc/operations/metrics/alerts.md b/doc/operations/metrics/alerts.md index ffd304c8897..aa46384688b 100644 --- a/doc/operations/metrics/alerts.md +++ b/doc/operations/metrics/alerts.md @@ -6,8 +6,6 @@ info: To determine the technical writer assigned to the Stage/Group associated w # Set up alerts for Prometheus metrics **(FREE)** -> [Moved](https://gitlab.com/gitlab-org/gitlab/-/issues/42640) to GitLab Free in 12.10. - After [configuring metrics for your CI/CD environment](index.md), you can set up alerting for Prometheus metrics, and [trigger actions from alerts](#trigger-actions-from-alerts) to notify @@ -15,7 +13,7 @@ your team when environment performance falls outside of the boundaries you set. ## Prometheus cluster integrations -Alerts are not currently supported for [Prometheus cluster integrations](../../user/clusters/integrations.md). +Alerts are not supported for [Prometheus cluster integrations](../../user/clusters/integrations.md). ## Trigger actions from alerts **(ULTIMATE)** diff --git a/doc/tutorials/index.md b/doc/tutorials/index.md index 118c2acdfa9..8e03029e501 100644 --- a/doc/tutorials/index.md +++ b/doc/tutorials/index.md @@ -31,6 +31,7 @@ the most out of GitLab. |-------|-------------|--------------------| | [Make your first Git commit](make_your_first_git_commit.md) | Create a project, edit a file, and commit changes to a Git repository from the command line. | **{star}** | | [Start using Git on the command line](../gitlab-basics/start-using-git.md) | Learn how to set up Git, clone repositories, and work with branches. | **{star}** | +| [Take advantage of Git rebase](https://about.gitlab.com/blog/2022/10/06/take-advantage-of-git-rebase/)| Learn how to use the `rebase` command in your workflow. | | | [Git cheat sheet](https://about.gitlab.com/images/press/git-cheat-sheet.pdf) | Download a PDF of common Git commands. | | ## Plan your work in projects @@ -84,6 +85,7 @@ GitLab can check your application for security vulnerabilities. | Topic | Description | Good for beginners | |-------|-------------|--------------------| | [Set up dependency scanning](https://about.gitlab.com/blog/2021/01/14/try-dependency-scanning/) | Try out dependency scanning, which checks for known vulnerabilities in dependencies. | **{star}** | +| [Get started with GitLab application security](../user/application_security/get-started-security.md) | Follow recommended steps to set up security tools. | | ## Work with a self-managed instance diff --git a/doc/user/markdown.md b/doc/user/markdown.md index b6f3ba1cfdd..f0dbfb854bb 100644 --- a/doc/user/markdown.md +++ b/doc/user/markdown.md @@ -1017,8 +1017,31 @@ Do not change to a reference style link. ![alt text](img/markdown_logo.png "Title Text") -In the rare case where you must set a specific height or width for an image, -you can use the `img` HTML tag instead of Markdown and set its `height` and +#### Change the image dimensions + +> Introduced in GitLab 15.7 [with a flag](../administration/feature_flags.md) named `markdown_image_attributes`. Disabled by default. + +FLAG: +On self-managed GitLab, by default this feature is not available. To make it available, +ask an administrator to [enable the feature flag](../administration/feature_flags.md) named `markdown_image_attributes`. +On GitLab.com, this feature is available. +The feature is not ready for production use. + +You can control the width and height of an image by following the image with +an attribute list. +The value must an integer with a unit of either `px` (default) or `%`. + +For example + +```markdown +![alt text](img/markdown_logo.png "Title Text"){width=100 height=100px} + +![alt text](img/markdown_logo.png "Title Text"){width=75%} +``` + +![alt text](img/markdown_logo.png "Title Text"){width=100 height=100px} + +You can also use the `img` HTML tag instead of Markdown and set its `height` and `width` parameters. #### Videos diff --git a/lib/api/ci/jobs.rb b/lib/api/ci/jobs.rb index 9e41e1c0d8f..e988ac4bf3b 100644 --- a/lib/api/ci/jobs.rb +++ b/lib/api/ci/jobs.rb @@ -55,7 +55,9 @@ module API builds = filter_builds(builds, params[:scope]) builds = builds.preload(:user, :job_artifacts_archive, :job_artifacts, :runner, :tags, pipeline: :project) - present paginate(builds), with: Entities::Ci::Job + without_count = Feature.enabled?(:disable_pagination_counts_on_jobs_api, user_project) + + present paginate(builds, without_count: without_count), with: Entities::Ci::Job end # rubocop: enable CodeReuse/ActiveRecord diff --git a/lib/api/internal/kubernetes.rb b/lib/api/internal/kubernetes.rb index c42b6fd8951..777d5019a29 100644 --- a/lib/api/internal/kubernetes.rb +++ b/lib/api/internal/kubernetes.rb @@ -146,7 +146,7 @@ module API end optional :unique_counters, type: Hash do - optional :agent_users_using_ci_tunnel, type: Set[Integer], desc: 'A set of user ids that have interacted a CI Tunnel to' + optional :agent_users_using_ci_tunnel, type: Array[Integer], desc: 'An array of user ids that have interacted with CI Tunnel' end end post '/', feature_category: :kubernetes_management do diff --git a/lib/banzai/filter/attributes_filter.rb b/lib/banzai/filter/attributes_filter.rb new file mode 100644 index 00000000000..1cf7155b80e --- /dev/null +++ b/lib/banzai/filter/attributes_filter.rb @@ -0,0 +1,57 @@ +# frozen_string_literal: true + +module Banzai + module Filter + # Looks for attributes that are specified for an element. Follows the basic syntax laid out + # in https://github.com/jgm/commonmark-hs/blob/master/commonmark-extensions/test/attributes.md + # For example, + # ![](http://example.com/image.jpg){width=50%} + # + # However we currently have the following limitations: + # - only support images + # - only support the `width` and `height` attributes + # - attributes can not span multiple lines + # - unsupported attributes are thrown away + class AttributesFilter < HTML::Pipeline::Filter + CSS = 'img' + XPATH = Gitlab::Utils::Nokogiri.css_to_xpath(CSS).freeze + + ATTRIBUTES_PATTERN = %r{\A(?<matched>\{(?<attributes>.{1,100})\})}.freeze + WIDTH_HEIGHT_REGEX = %r{\A(?<name>height|width)="?(?<size>[\w%]{1,10})"?\z}.freeze + VALID_SIZE_REGEX = %r{\A\d{1,4}(%|px)?\z}.freeze + + def call + return doc unless Feature.enabled?(:markdown_image_attributes, group) + + doc.xpath(XPATH).each do |img| + sibling = img.next + next unless sibling && sibling.text? && sibling.content.first == '{' + + match = sibling.content.match(ATTRIBUTES_PATTERN) + next unless match && match[:attributes] + + match[:attributes].split(' ').each do |attribute| + next unless attribute.match?(WIDTH_HEIGHT_REGEX) + + attribute_match = attribute.match(WIDTH_HEIGHT_REGEX) + img[attribute_match[:name].to_sym] = attribute_match[:size] if valid_size?(attribute_match[:size]) + end + + sibling.content = sibling.content.sub(match[:matched], '') + end + + doc + end + + private + + def valid_size?(size) + size.match?(VALID_SIZE_REGEX) + end + + def group + context[:group] || context[:project]&.group + end + end + end +end diff --git a/lib/banzai/pipeline/gfm_pipeline.rb b/lib/banzai/pipeline/gfm_pipeline.rb index 0727652d88c..5e34ff8a33c 100644 --- a/lib/banzai/pipeline/gfm_pipeline.rb +++ b/lib/banzai/pipeline/gfm_pipeline.rb @@ -22,6 +22,7 @@ module Banzai Filter::MermaidFilter, Filter::VideoLinkFilter, Filter::AudioLinkFilter, + Filter::AttributesFilter, Filter::ImageLazyLoadFilter, Filter::ImageLinkFilter, *metrics_filters, diff --git a/lib/gitlab/gitaly_client/with_feature_flag_actors.rb b/lib/gitlab/gitaly_client/with_feature_flag_actors.rb index 92fc524b724..3d81292da16 100644 --- a/lib/gitlab/gitaly_client/with_feature_flag_actors.rb +++ b/lib/gitlab/gitaly_client/with_feature_flag_actors.rb @@ -16,8 +16,6 @@ module Gitlab # 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") @@ -34,11 +32,8 @@ module Gitlab 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. + # gitaly_feature_flag_actors returns a hash of actors implied from input repository. def gitaly_feature_flag_actors(repository) - return {} unless actors_aware_gitaly_calls? - container = find_repository_container(repository) { repository: repository, @@ -92,10 +87,6 @@ module Gitlab repository.container end end - - def actors_aware_gitaly_calls? - Feature.enabled?(:actors_aware_gitaly_calls) - end end end end diff --git a/lib/gitlab/pagination/offset_pagination.rb b/lib/gitlab/pagination/offset_pagination.rb index 00304f48dc5..3d10bcef019 100644 --- a/lib/gitlab/pagination/offset_pagination.rb +++ b/lib/gitlab/pagination/offset_pagination.rb @@ -11,15 +11,15 @@ module Gitlab @request_context = request_context end - def paginate(relation, exclude_total_headers: false, skip_default_order: false) - paginate_with_limit_optimization(add_default_order(relation, skip_default_order: skip_default_order)).tap do |data| + def paginate(relation, exclude_total_headers: false, skip_default_order: false, without_count: false) + paginate_with_limit_optimization(add_default_order(relation, skip_default_order: skip_default_order), without_count: without_count).tap do |data| add_pagination_headers(data, exclude_total_headers) end end private - def paginate_with_limit_optimization(relation) + def paginate_with_limit_optimization(relation, without_count:) pagination_data = if needs_pagination?(relation) relation.page(params[:page]).per(params[:per_page]) else @@ -28,8 +28,7 @@ module Gitlab return pagination_data unless pagination_data.is_a?(ActiveRecord::Relation) - limited_total_count = pagination_data.total_count_with_limit - if limited_total_count > Kaminari::ActiveRecordRelationMethods::MAX_COUNT_LIMIT + if without_count || exceeeds_count?(pagination_data) # The call to `total_count_with_limit` memoizes `@arel` because of a call to `references_eager_loaded_tables?` # We need to call `reset` because `without_count` relies on `@arel` being unmemoized pagination_data.reset.without_count @@ -78,6 +77,12 @@ module Gitlab # Ensure there is in total at least 1 page [paginated_data.total_pages, 1].max end + + def exceeeds_count?(paginated_data) + limited_total_count = paginated_data.total_count_with_limit + + limited_total_count > Kaminari::ActiveRecordRelationMethods::MAX_COUNT_LIMIT + end end end end diff --git a/lib/gitlab/patch/prependable.rb b/lib/gitlab/patch/prependable.rb index 1ed341e1c26..b974c0b2c7f 100644 --- a/lib/gitlab/patch/prependable.rb +++ b/lib/gitlab/patch/prependable.rb @@ -26,7 +26,7 @@ module Gitlab # https://github.com/rails/rails/pull/42067 # # Let's keep our own implementation, until the issue is fixed - Module.instance_method(:prepend_features).bind(self).call(base) + Module.instance_method(:prepend_features).bind_call(self, base) if const_defined?(:ClassMethods) klass_methods = const_get(:ClassMethods, false) diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 425384ca40d..709d15193db 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -12278,6 +12278,9 @@ msgstr "" msgid "DastConfig|Not enabled" msgstr "" +msgid "DastProfiles|/graphql" +msgstr "" + msgid "DastProfiles|A passive scan monitors all HTTP messages (requests and responses) sent to the target. An active scan attacks the target to find potential vulnerabilities." msgstr "" @@ -12425,6 +12428,9 @@ msgstr "" msgid "DastProfiles|Monitors all HTTP requests sent to the target to find potential vulnerabilities." msgstr "" +msgid "DastProfiles|Must allow introspection queries to request the API schema. %{linkStart}How do I enable introspection%{linkEnd}?" +msgstr "" + msgid "DastProfiles|New scanner profile" msgstr "" @@ -19037,6 +19043,12 @@ msgstr "" msgid "Graph" msgstr "" +msgid "GraphQL" +msgstr "" + +msgid "GraphQL endpoint path" +msgstr "" + msgid "GraphViewType|Job dependencies" msgstr "" @@ -28787,9 +28799,6 @@ msgstr "" msgid "Opens new window" msgstr "" -msgid "Operation failed. Check pod logs for %{pod_name} for more details." -msgstr "" - msgid "Operation not allowed" msgstr "" diff --git a/package.json b/package.json index 3377ee82c43..2e44fb8be2f 100644 --- a/package.json +++ b/package.json @@ -55,7 +55,7 @@ "@gitlab/at.js": "1.5.7", "@gitlab/favicon-overlay": "2.0.0", "@gitlab/svgs": "3.13.0", - "@gitlab/ui": "50.1.2", + "@gitlab/ui": "51.1.1", "@gitlab/visual-review-tools": "1.7.3", "@gitlab/web-ide": "0.0.1-dev-20221114183058", "@rails/actioncable": "6.1.4-7", diff --git a/qa/qa/page/component/dropdown.rb b/qa/qa/page/component/dropdown.rb index 2d05bea50a9..58a6fee5e2b 100644 --- a/qa/qa/page/component/dropdown.rb +++ b/qa/qa/page/component/dropdown.rb @@ -9,20 +9,20 @@ module QA def select_item(item_text) return super if use_select2? - find('li.gl-new-dropdown-item', text: item_text, match: :prefer_exact).click + find('li.gl-dropdown-item', text: item_text, match: :prefer_exact).click end def has_item?(item_text) return super if use_select2? - has_css?('li.gl-new-dropdown-item', text: item_text, match: :prefer_exact) + has_css?('li.gl-dropdown-item', text: item_text, match: :prefer_exact) end def current_selection return super if use_select2? expand_select_list unless dropdown_open? - find('span.gl-new-dropdown-button-text').text + find('span.gl-dropdown-button-text').text end def clear_current_selection_if_present @@ -69,7 +69,7 @@ module QA raise QA::Page::Base::ElementNotFound, %(Couldn't find option named "#{item_text}") end - find('li.gl-new-dropdown-item span:nth-child(2)', text: item_text, exact_text: true).click + find('li.gl-dropdown-item span:nth-child(2)', text: item_text, exact_text: true).click end def expand_select_list @@ -90,7 +90,7 @@ module QA def dropdown_open? return super if use_select2? - has_css?('ul.gl-new-dropdown-contents', wait: 1) + has_css?('ul.gl-dropdown-contents', wait: 1) end private diff --git a/spec/controllers/import/github_controller_spec.rb b/spec/controllers/import/github_controller_spec.rb index f3632e7370c..9c2c90b147c 100644 --- a/spec/controllers/import/github_controller_spec.rb +++ b/spec/controllers/import/github_controller_spec.rb @@ -331,12 +331,12 @@ RSpec.describe Import::GithubController do describe "GET realtime_changes" do let(:user) { create(:user) } - it_behaves_like 'a GitHub-ish import controller: GET realtime_changes' - before do assign_session_token(provider) end + it_behaves_like 'a GitHub-ish import controller: GET realtime_changes' + it 'includes stats in response' do create(:project, import_type: provider, namespace: user.namespace, import_status: :finished, import_source: 'example/repo') diff --git a/spec/features/admin/users/users_spec.rb b/spec/features/admin/users/users_spec.rb index 53d3aec9cff..4b49e8f4bc6 100644 --- a/spec/features/admin/users/users_spec.rb +++ b/spec/features/admin/users/users_spec.rb @@ -604,8 +604,8 @@ RSpec.describe 'Admin::Users', feature_category: :user_management do def sort_by(option) page.within('.filtered-search-block') do - find('.gl-new-dropdown').click - find('.gl-new-dropdown-item', text: option).click + find('.gl-dropdown').click + find('.gl-dropdown-item', text: option).click end end end diff --git a/spec/features/alert_management/alert_details_spec.rb b/spec/features/alert_management/alert_details_spec.rb index 26233fbda64..abb97d3be4d 100644 --- a/spec/features/alert_management/alert_details_spec.rb +++ b/spec/features/alert_management/alert_details_spec.rb @@ -61,7 +61,7 @@ RSpec.describe 'Alert details', :js, feature_category: :incident_management do expect(alert_status).to have_content('Triggered') find('.gl-button').click - find('.gl-new-dropdown-item', text: 'Acknowledged').click + find('.gl-dropdown-item', text: 'Acknowledged').click wait_for_requests diff --git a/spec/features/boards/board_filters_spec.rb b/spec/features/boards/board_filters_spec.rb index 4ed1a7a58f4..dee63be8119 100644 --- a/spec/features/boards/board_filters_spec.rb +++ b/spec/features/boards/board_filters_spec.rb @@ -191,7 +191,7 @@ RSpec.describe 'Issue board filters', :js, feature_category: :team_planning do end def expect_filtered_search_dropdown_results(filter_dropdown, count) - expect(filter_dropdown).to have_selector('.gl-new-dropdown-item', count: count) + expect(filter_dropdown).to have_selector('.gl-dropdown-item', count: count) end def visit_project_board diff --git a/spec/features/boards/sidebar_assignee_spec.rb b/spec/features/boards/sidebar_assignee_spec.rb index 44ae0c5a3b4..e3de594f856 100644 --- a/spec/features/boards/sidebar_assignee_spec.rb +++ b/spec/features/boards/sidebar_assignee_spec.rb @@ -113,7 +113,7 @@ RSpec.describe 'Project issue boards sidebar assignee', :js, quarantine: 'https: page.within(assignees_widget) do click_button('Edit') - expect(find('.dropdown-menu')).to have_selector('.gl-new-dropdown-item-check-icon') + expect(find('.dropdown-menu')).to have_selector('.gl-dropdown-item-check-icon') end end end diff --git a/spec/features/groups/board_spec.rb b/spec/features/groups/board_spec.rb index ffd1d83b619..11ec38f637b 100644 --- a/spec/features/groups/board_spec.rb +++ b/spec/features/groups/board_spec.rb @@ -35,7 +35,7 @@ RSpec.describe 'Group Boards', feature_category: :team_planning do page.within("[data-testid='project-select-dropdown']") do find('button.gl-dropdown-toggle').click - find('.gl-new-dropdown-item button').click + find('.gl-dropdown-item button').click end click_button 'Create issue' diff --git a/spec/features/incidents/incident_timeline_events_spec.rb b/spec/features/incidents/incident_timeline_events_spec.rb index 2aed289884b..3a73ea50247 100644 --- a/spec/features/incidents/incident_timeline_events_spec.rb +++ b/spec/features/incidents/incident_timeline_events_spec.rb @@ -50,9 +50,9 @@ RSpec.describe 'Incident timeline events', :js, feature_category: :incident_mana it 'shows the confirmation modal and edits the event' do click_button _('More actions') - page.within '.gl-new-dropdown-contents' do + page.within '.gl-dropdown-contents' do expect(page).to have_content(_('Edit')) - page.find('.gl-new-dropdown-item-text-primary', text: _('Edit')).click + page.find('.gl-dropdown-item-text-primary', text: _('Edit')).click end expect(page).to have_selector('.common-note-form') @@ -82,9 +82,9 @@ RSpec.describe 'Incident timeline events', :js, feature_category: :incident_mana it 'shows the confirmation modal and deletes the event' do click_button _('More actions') - page.within '.gl-new-dropdown-contents' do + page.within '.gl-dropdown-contents' do expect(page).to have_content(_('Delete')) - page.find('.gl-new-dropdown-item-text-primary', text: 'Delete').click + page.find('.gl-dropdown-item-text-primary', text: 'Delete').click end page.within '.modal' do diff --git a/spec/features/issuables/shortcuts_issuable_spec.rb b/spec/features/issuables/shortcuts_issuable_spec.rb index dea0137f654..0190111b2f0 100644 --- a/spec/features/issuables/shortcuts_issuable_spec.rb +++ b/spec/features/issuables/shortcuts_issuable_spec.rb @@ -87,7 +87,7 @@ RSpec.describe 'Blob shortcuts', :js, feature_category: :team_planning do it "opens milestones dropdown for editing" do find('body').native.send_key('m') - expect(find('[data-testid="milestone-edit"]')).to have_selector('.gl-new-dropdown-inner') + expect(find('[data-testid="milestone-edit"]')).to have_selector('.gl-dropdown-inner') end end diff --git a/spec/features/issues/form_spec.rb b/spec/features/issues/form_spec.rb index d434fa9441f..2898c97c2e9 100644 --- a/spec/features/issues/form_spec.rb +++ b/spec/features/issues/form_spec.rb @@ -43,7 +43,7 @@ RSpec.describe 'New/edit issue', :js, feature_category: :team_planning do # To work around this, we have to hold on to and call to the original implementation manually. original_issue_dropdown_options = FormHelper.instance_method(:assignees_dropdown_options) allow_any_instance_of(FormHelper).to receive(:assignees_dropdown_options).and_wrap_original do |original, *args| - options = original_issue_dropdown_options.bind(original.receiver).call(*args) + options = original_issue_dropdown_options.bind_call(original.receiver, *args) options[:data][:per_page] = 2 options diff --git a/spec/features/issues/user_edits_issue_spec.rb b/spec/features/issues/user_edits_issue_spec.rb index 97739908053..223832a6ede 100644 --- a/spec/features/issues/user_edits_issue_spec.rb +++ b/spec/features/issues/user_edits_issue_spec.rb @@ -416,7 +416,7 @@ RSpec.describe "Issues > User edits issue", :js, feature_category: :team_plannin find('.gl-form-input', visible: true).send_keys "\"#{milestones[0].title}\"" wait_for_requests - page.within '.gl-new-dropdown-contents' do + page.within '.gl-dropdown-contents' do expect(page).to have_content milestones[0].title end end diff --git a/spec/features/markdown/markdown_spec.rb b/spec/features/markdown/markdown_spec.rb index 08f9b8eda13..73670772402 100644 --- a/spec/features/markdown/markdown_spec.rb +++ b/spec/features/markdown/markdown_spec.rb @@ -290,6 +290,13 @@ RSpec.describe 'GitLab Markdown', :aggregate_failures do aggregate_failures 'KrokiFilter' do expect(doc).to parse_kroki end + + aggregate_failures 'AttributeFilter' do + img = doc.at_css('img[alt="Sized Image"]') + + expect(img.attr('width')).to eq('75%') + expect(img.attr('height')).to eq('100') + end end end diff --git a/spec/features/projects/compare_spec.rb b/spec/features/projects/compare_spec.rb index 22b0f344606..0c269f55212 100644 --- a/spec/features/projects/compare_spec.rb +++ b/spec/features/projects/compare_spec.rb @@ -17,10 +17,10 @@ RSpec.describe "Compare", :js do visit project_compare_index_path(project, from: 'master', to: 'master') select_using_dropdown 'from', 'feature' - expect(find('.js-compare-from-dropdown .gl-new-dropdown-button-text')).to have_content('feature') + expect(find('.js-compare-from-dropdown .gl-dropdown-button-text')).to have_content('feature') select_using_dropdown 'to', 'binary-encoding' - expect(find('.js-compare-to-dropdown .gl-new-dropdown-button-text')).to have_content('binary-encoding') + expect(find('.js-compare-to-dropdown .gl-dropdown-button-text')).to have_content('binary-encoding') click_button 'Compare' @@ -32,8 +32,8 @@ RSpec.describe "Compare", :js do it "pre-populates fields" do visit project_compare_index_path(project, from: "master", to: "master") - expect(find(".js-compare-from-dropdown .gl-new-dropdown-button-text")).to have_content("master") - expect(find(".js-compare-to-dropdown .gl-new-dropdown-button-text")).to have_content("master") + expect(find(".js-compare-from-dropdown .gl-dropdown-button-text")).to have_content("master") + expect(find(".js-compare-to-dropdown .gl-dropdown-button-text")).to have_content("master") end it_behaves_like 'compares branches' @@ -99,7 +99,7 @@ RSpec.describe "Compare", :js do find(".js-compare-from-dropdown .compare-dropdown-toggle").click - expect(find(".js-compare-from-dropdown .gl-new-dropdown-contents")).to have_selector('li.gl-new-dropdown-item', count: 1) + expect(find(".js-compare-from-dropdown .gl-dropdown-contents")).to have_selector('li.gl-dropdown-item', count: 1) end context 'when commit has overflow', :js do @@ -153,10 +153,10 @@ RSpec.describe "Compare", :js do visit project_compare_index_path(project, from: "master", to: "master") select_using_dropdown "from", "v1.0.0" - expect(find(".js-compare-from-dropdown .gl-new-dropdown-button-text")).to have_content("v1.0.0") + expect(find(".js-compare-from-dropdown .gl-dropdown-button-text")).to have_content("v1.0.0") select_using_dropdown "to", "v1.1.0" - expect(find(".js-compare-to-dropdown .gl-new-dropdown-button-text")).to have_content("v1.1.0") + expect(find(".js-compare-to-dropdown .gl-dropdown-button-text")).to have_content("v1.1.0") click_button "Compare" expect(page).to have_content "Commits" diff --git a/spec/features/projects/pages/user_configures_pages_pipeline_spec.rb b/spec/features/projects/pages/user_configures_pages_pipeline_spec.rb index 029479d6b95..83e9e3d6617 100644 --- a/spec/features/projects/pages/user_configures_pages_pipeline_spec.rb +++ b/spec/features/projects/pages/user_configures_pages_pipeline_spec.rb @@ -46,14 +46,14 @@ RSpec.describe 'Pages edits pages settings', :js do Feature.disable(:use_pipeline_wizard_for_pages) end + after do + Feature.enable(:use_pipeline_wizard_for_pages) + end + it 'shows configure pages instructions' do visit project_pages_path(project) expect(page).to have_content('Configure pages') end - - after do - Feature.enable(:use_pipeline_wizard_for_pages) - end end end diff --git a/spec/features/projects/pipeline_schedules_spec.rb b/spec/features/projects/pipeline_schedules_spec.rb index e569fef76f8..3f1f834bfb1 100644 --- a/spec/features/projects/pipeline_schedules_spec.rb +++ b/spec/features/projects/pipeline_schedules_spec.rb @@ -64,7 +64,7 @@ RSpec.describe 'Pipeline Schedules', :js do it 'shows the pipeline schedule with default ref' do page.within('[data-testid="schedule-target-ref"]') do - expect(first('.gl-new-dropdown-button-text').text).to eq('master') + expect(first('.gl-dropdown-button-text').text).to eq('master') end end end @@ -77,7 +77,7 @@ RSpec.describe 'Pipeline Schedules', :js do it 'shows the pipeline schedule with default ref' do page.within('[data-testid="schedule-target-ref"]') do - expect(first('.gl-new-dropdown-button-text').text).to eq('master') + expect(first('.gl-dropdown-button-text').text).to eq('master') end end end diff --git a/spec/features/projects/pipelines/legacy_pipelines_spec.rb b/spec/features/projects/pipelines/legacy_pipelines_spec.rb index 9d3ac71a875..a99a5b7ac4a 100644 --- a/spec/features/projects/pipelines/legacy_pipelines_spec.rb +++ b/spec/features/projects/pipelines/legacy_pipelines_spec.rb @@ -598,8 +598,8 @@ RSpec.describe 'Pipelines', :js do it 'changes the Pipeline ID column for Pipeline IID' do page.find('[data-testid="pipeline-key-dropdown"]').click - within '.gl-new-dropdown-contents' do - dropdown_options = page.find_all '.gl-new-dropdown-item' + within '.gl-dropdown-contents' do + dropdown_options = page.find_all '.gl-dropdown-item' dropdown_options[1].click end @@ -800,7 +800,7 @@ RSpec.describe 'Pipelines', :js do page.within '[data-testid="ref-select"]' do find('[data-testid="search-refs"]').native.send_keys('fix') - page.within '.gl-new-dropdown-contents' do + page.within '.gl-dropdown-contents' do expect(page).to have_content('fix') end end diff --git a/spec/features/projects/pipelines/pipelines_spec.rb b/spec/features/projects/pipelines/pipelines_spec.rb index 8260786adac..4378b709d9e 100644 --- a/spec/features/projects/pipelines/pipelines_spec.rb +++ b/spec/features/projects/pipelines/pipelines_spec.rb @@ -596,8 +596,8 @@ RSpec.describe 'Pipelines', :js do it 'changes the Pipeline ID column for Pipeline IID' do page.find('[data-testid="pipeline-key-dropdown"]').click - within '.gl-new-dropdown-contents' do - dropdown_options = page.find_all '.gl-new-dropdown-item' + within '.gl-dropdown-contents' do + dropdown_options = page.find_all '.gl-dropdown-item' dropdown_options[1].click end @@ -825,7 +825,7 @@ RSpec.describe 'Pipelines', :js do page.within '[data-testid="ref-select"]' do find('[data-testid="search-refs"]').native.send_keys('fix') - page.within '.gl-new-dropdown-contents' do + page.within '.gl-dropdown-contents' do expect(page).to have_content('fix') end end diff --git a/spec/features/projects/show/user_manages_notifications_spec.rb b/spec/features/projects/show/user_manages_notifications_spec.rb index 1df37eef6a9..4a3bc6b82e6 100644 --- a/spec/features/projects/show/user_manages_notifications_spec.rb +++ b/spec/features/projects/show/user_manages_notifications_spec.rb @@ -23,7 +23,7 @@ RSpec.describe 'Projects > Show > User manages notifications', :js do click_notifications_button page.within first('[data-testid="notification-dropdown"]') do - expect(page.find('.gl-new-dropdown-item.is-active')).to have_content('On mention') + expect(page.find('.gl-dropdown-item.is-active')).to have_content('On mention') expect(page).to have_css('[data-testid="notifications-icon"]') end end diff --git a/spec/fixtures/markdown.md.erb b/spec/fixtures/markdown.md.erb index 14885813d93..38b2a8381bb 100644 --- a/spec/fixtures/markdown.md.erb +++ b/spec/fixtures/markdown.md.erb @@ -399,3 +399,7 @@ Bob -> Sara : Hello [beard]-:>[foul mouth] ] ``` + +### Image Attributes + +![Sized Image](app/assets/images/touch-icon-ipad.png){width=75% height=100} diff --git a/spec/frontend/boards/board_list_spec.js b/spec/frontend/boards/board_list_spec.js index eddc916ed14..34c0504143c 100644 --- a/spec/frontend/boards/board_list_spec.js +++ b/spec/frontend/boards/board_list_spec.js @@ -198,6 +198,13 @@ describe('Board list component', () => { expect(findDraggable().exists()).toBe(true); }); + it('sets delay and delayOnTouchOnly attributes on board list', () => { + const listEl = wrapper.findComponent({ ref: 'list' }); + + expect(listEl.attributes('delay')).toBe('100'); + expect(listEl.attributes('delayontouchonly')).toBe('true'); + }); + describe('handleDragOnStart', () => { it('adds a class `is-dragging` to document body', () => { expect(document.body.classList.contains('is-dragging')).toBe(false); diff --git a/spec/frontend/boards/components/board_content_spec.js b/spec/frontend/boards/components/board_content_spec.js index 39b2fe2ebbf..82e7ab48e7d 100644 --- a/spec/frontend/boards/components/board_content_spec.js +++ b/spec/frontend/boards/components/board_content_spec.js @@ -150,6 +150,13 @@ describe('BoardContent', () => { expect(style).toBe('height: 900px;'); }); + + it('sets delay and delayOnTouchOnly attributes on board list', () => { + const listEl = wrapper.findComponent({ ref: 'list' }); + + expect(listEl.attributes('delay')).toBe('100'); + expect(listEl.attributes('delayontouchonly')).toBe('true'); + }); }); describe('when issuableType is not issue', () => { diff --git a/spec/frontend/boards/project_select_spec.js b/spec/frontend/boards/project_select_spec.js index 7ff34ffdf9e..4324e7068e0 100644 --- a/spec/frontend/boards/project_select_spec.js +++ b/spec/frontend/boards/project_select_spec.js @@ -156,7 +156,7 @@ describe('ProjectSelect component', () => { }); it('renders the name of the selected project', () => { - expect(findGlDropdown().find('.gl-new-dropdown-button-text').text()).toBe( + expect(findGlDropdown().find('.gl-dropdown-button-text').text()).toBe( mockProjectsList1[0].name, ); }); diff --git a/spec/frontend/content_editor/components/__snapshots__/toolbar_link_button_spec.js.snap b/spec/frontend/content_editor/components/__snapshots__/toolbar_link_button_spec.js.snap index 6ad8a9de8d3..331a0a474a3 100644 --- a/spec/frontend/content_editor/components/__snapshots__/toolbar_link_button_spec.js.snap +++ b/spec/frontend/content_editor/components/__snapshots__/toolbar_link_button_spec.js.snap @@ -14,15 +14,15 @@ exports[`content_editor/components/toolbar_link_button renders dropdown componen </div> </form> </li> - <li role=\\"presentation\\" class=\\"gl-new-dropdown-divider\\"> + <li role=\\"presentation\\" class=\\"gl-dropdown-divider\\"> <hr role=\\"separator\\" aria-orientation=\\"horizontal\\" class=\\"dropdown-divider\\"> </li> - <li role=\\"presentation\\" class=\\"gl-new-dropdown-item\\"><button role=\\"menuitem\\" type=\\"button\\" class=\\"dropdown-item\\"> + <li role=\\"presentation\\" class=\\"gl-dropdown-item\\"><button role=\\"menuitem\\" type=\\"button\\" class=\\"dropdown-item\\"> <!----> <!----> <!----> - <div class=\\"gl-new-dropdown-item-text-wrapper\\"> - <p class=\\"gl-new-dropdown-item-text-primary\\"> + <div class=\\"gl-dropdown-item-text-wrapper\\"> + <p class=\\"gl-dropdown-item-text-primary\\"> Upload file </p> <!----> diff --git a/spec/frontend/editor/schema/ci/ci_schema_spec.js b/spec/frontend/editor/schema/ci/ci_schema_spec.js index 9c622d49db9..a06f81e4d1c 100644 --- a/spec/frontend/editor/schema/ci/ci_schema_spec.js +++ b/spec/frontend/editor/schema/ci/ci_schema_spec.js @@ -31,6 +31,7 @@ import ProjectPathYaml from './yaml_tests/positive_tests/project_path.yml'; import VariablesYaml from './yaml_tests/positive_tests/variables.yml'; import JobWhenYaml from './yaml_tests/positive_tests/job_when.yml'; import IdTokensYaml from './yaml_tests/positive_tests/id_tokens.yml'; +import HooksYaml from './yaml_tests/positive_tests/hooks.yml'; // YAML NEGATIVE TEST import ArtifactsNegativeYaml from './yaml_tests/negative_tests/artifacts.yml'; @@ -47,6 +48,7 @@ import TriggerNegative from './yaml_tests/negative_tests/trigger.yml'; import VariablesInvalidSyntaxDescYaml from './yaml_tests/negative_tests/variables/invalid_syntax_desc.yml'; import VariablesWrongSyntaxUsageExpand from './yaml_tests/negative_tests/variables/wrong_syntax_usage_expand.yml'; import IdTokensNegativeYaml from './yaml_tests/negative_tests/id_tokens.yml'; +import HooksNegative from './yaml_tests/negative_tests/hooks.yml'; const ajv = new Ajv({ strictTypes: false, @@ -79,6 +81,7 @@ describe('positive tests', () => { FilterYaml, IncludeYaml, JobWhenYaml, + HooksYaml, RulesYaml, VariablesYaml, ProjectPathYaml, @@ -118,6 +121,7 @@ describe('negative tests', () => { ProjectPathIncludeNoSlashYaml, ProjectPathIncludeTailSlashYaml, TriggerNegative, + HooksNegative, }), )('schema validates %s', (_, input) => { // We construct a new "JSON" from each main key that is inside a diff --git a/spec/frontend/editor/schema/ci/yaml_tests/negative_tests/hooks.yml b/spec/frontend/editor/schema/ci/yaml_tests/negative_tests/hooks.yml new file mode 100644 index 00000000000..e3366b0b6d3 --- /dev/null +++ b/spec/frontend/editor/schema/ci/yaml_tests/negative_tests/hooks.yml @@ -0,0 +1,10 @@ +job1: + hooks: + invalid_script: + - echo 'hello job1 invalid_script' + script: echo 'hello job1 script' + +job2: + hooks: + pre_get_sources_script: true + script: echo 'hello job1 script' diff --git a/spec/frontend/editor/schema/ci/yaml_tests/positive_tests/hooks.yml b/spec/frontend/editor/schema/ci/yaml_tests/positive_tests/hooks.yml new file mode 100644 index 00000000000..4d45c5528ea --- /dev/null +++ b/spec/frontend/editor/schema/ci/yaml_tests/positive_tests/hooks.yml @@ -0,0 +1,10 @@ +default: + hooks: + pre_get_sources_script: + - echo 'hello default pre_get_sources_script' + +job1: + hooks: + pre_get_sources_script: + - echo 'hello job1 pre_get_sources_script' + script: echo 'hello job1 script' diff --git a/spec/frontend/jira_import/components/__snapshots__/jira_import_form_spec.js.snap b/spec/frontend/jira_import/components/__snapshots__/jira_import_form_spec.js.snap index a72528ae36b..748e151f31b 100644 --- a/spec/frontend/jira_import/components/__snapshots__/jira_import_form_spec.js.snap +++ b/spec/frontend/jira_import/components/__snapshots__/jira_import_form_spec.js.snap @@ -87,7 +87,7 @@ exports[`JiraImportForm table body shows correct information in each cell 1`] = > <div aria-label="The GitLab user to which the Jira user Jane Doe will be mapped" - class="dropdown b-dropdown gl-new-dropdown w-100 btn-group" + class="dropdown b-dropdown gl-dropdown w-100 btn-group" > <!----> <button @@ -101,7 +101,7 @@ exports[`JiraImportForm table body shows correct information in each cell 1`] = <!----> <span - class="gl-new-dropdown-button-text" + class="gl-dropdown-button-text" > janedoe </span> @@ -123,14 +123,14 @@ exports[`JiraImportForm table body shows correct information in each cell 1`] = tabindex="-1" > <div - class="gl-new-dropdown-inner" + class="gl-dropdown-inner" > <!----> <!----> <div - class="gl-new-dropdown-contents" + class="gl-dropdown-contents" > <!----> @@ -165,7 +165,7 @@ exports[`JiraImportForm table body shows correct information in each cell 1`] = </div> <li - class="gl-new-dropdown-text text-secondary" + class="gl-dropdown-text text-secondary" role="presentation" > <p @@ -218,7 +218,7 @@ exports[`JiraImportForm table body shows correct information in each cell 1`] = > <div aria-label="The GitLab user to which the Jira user Fred Chopin will be mapped" - class="dropdown b-dropdown gl-new-dropdown w-100 btn-group" + class="dropdown b-dropdown gl-dropdown w-100 btn-group" > <!----> <button @@ -232,7 +232,7 @@ exports[`JiraImportForm table body shows correct information in each cell 1`] = <!----> <span - class="gl-new-dropdown-button-text" + class="gl-dropdown-button-text" > mrgitlab </span> @@ -254,14 +254,14 @@ exports[`JiraImportForm table body shows correct information in each cell 1`] = tabindex="-1" > <div - class="gl-new-dropdown-inner" + class="gl-dropdown-inner" > <!----> <!----> <div - class="gl-new-dropdown-contents" + class="gl-dropdown-contents" > <!----> @@ -296,7 +296,7 @@ exports[`JiraImportForm table body shows correct information in each cell 1`] = </div> <li - class="gl-new-dropdown-text text-secondary" + class="gl-dropdown-text text-secondary" role="presentation" > <p diff --git a/spec/frontend/language_switcher/components/app_spec.js b/spec/frontend/language_switcher/components/app_spec.js index d44187897b8..6a1b94cd813 100644 --- a/spec/frontend/language_switcher/components/app_spec.js +++ b/spec/frontend/language_switcher/components/app_spec.js @@ -27,7 +27,7 @@ describe('<LanguageSwitcher />', () => { wrapper.destroy(); }); - const getPreferredLanguage = () => wrapper.find('.gl-new-dropdown-button-text').text(); + const getPreferredLanguage = () => wrapper.find('.gl-dropdown-button-text').text(); const findLanguageDropdownItem = (code) => wrapper.findByTestId(`language_switcher_lang_${code}`); it('preferred language', () => { diff --git a/spec/frontend/milestones/components/milestone_combobox_spec.js b/spec/frontend/milestones/components/milestone_combobox_spec.js index ce5b2a1000b..c20c51db75e 100644 --- a/spec/frontend/milestones/components/milestone_combobox_spec.js +++ b/spec/frontend/milestones/components/milestone_combobox_spec.js @@ -346,7 +346,7 @@ describe('Milestone combobox component', () => { expect( findFirstProjectMilestonesDropdownItem() .find('svg') - .classes('gl-new-dropdown-item-check-icon'), + .classes('gl-dropdown-item-check-icon'), ).toBe(true); selectFirstProjectMilestone(); @@ -473,7 +473,7 @@ describe('Milestone combobox component', () => { expect( findFirstGroupMilestonesDropdownItem() .find('svg') - .classes('gl-new-dropdown-item-check-icon'), + .classes('gl-dropdown-item-check-icon'), ).toBe(true); selectFirstGroupMilestone(); diff --git a/spec/frontend/packages_and_registries/package_registry/components/details/__snapshots__/pypi_installation_spec.js.snap b/spec/frontend/packages_and_registries/package_registry/components/details/__snapshots__/pypi_installation_spec.js.snap index 92c2cd90568..c4020eeb75f 100644 --- a/spec/frontend/packages_and_registries/package_registry/components/details/__snapshots__/pypi_installation_spec.js.snap +++ b/spec/frontend/packages_and_registries/package_registry/components/details/__snapshots__/pypi_installation_spec.js.snap @@ -13,7 +13,7 @@ exports[`PypiInstallation renders all the messages 1`] = ` <div> <div - class="dropdown b-dropdown gl-new-dropdown btn-group" + class="dropdown b-dropdown gl-dropdown btn-group" id="__BVID__27" lazy="" > @@ -30,7 +30,7 @@ exports[`PypiInstallation renders all the messages 1`] = ` <!----> <span - class="gl-new-dropdown-button-text" + class="gl-dropdown-button-text" > Show PyPi commands </span> diff --git a/spec/frontend/vue_merge_request_widget/components/states/__snapshots__/mr_widget_auto_merge_enabled_spec.js.snap b/spec/frontend/vue_merge_request_widget/components/states/__snapshots__/mr_widget_auto_merge_enabled_spec.js.snap index b473f7c343c..4077564486c 100644 --- a/spec/frontend/vue_merge_request_widget/components/states/__snapshots__/mr_widget_auto_merge_enabled_spec.js.snap +++ b/spec/frontend/vue_merge_request_widget/components/states/__snapshots__/mr_widget_auto_merge_enabled_spec.js.snap @@ -61,7 +61,7 @@ exports[`MRWidgetAutoMergeEnabled template should have correct elements 1`] = ` class="gl-display-flex gl-align-items-flex-start" > <div - class="dropdown b-dropdown gl-new-dropdown gl-display-block gl-md-display-none! btn-group" + class="dropdown b-dropdown gl-dropdown gl-display-block gl-md-display-none! btn-group" lazy="" no-caret="" title="Options" @@ -87,7 +87,7 @@ exports[`MRWidgetAutoMergeEnabled template should have correct elements 1`] = ` </svg> <span - class="gl-new-dropdown-button-text gl-sr-only" + class="gl-dropdown-button-text gl-sr-only" > </span> diff --git a/spec/frontend/vue_shared/components/__snapshots__/awards_list_spec.js.snap b/spec/frontend/vue_shared/components/__snapshots__/awards_list_spec.js.snap index bdf5ea23812..06be40fc941 100644 --- a/spec/frontend/vue_shared/components/__snapshots__/awards_list_spec.js.snap +++ b/spec/frontend/vue_shared/components/__snapshots__/awards_list_spec.js.snap @@ -225,7 +225,7 @@ exports[`vue_shared/components/awards_list default matches snapshot 1`] = ` > <div boundary="scrollParent" - class="dropdown b-dropdown gl-new-dropdown btn-group" + class="dropdown b-dropdown gl-dropdown btn-group" id="__BVID__13" lazy="" menu-class="dropdown-extended-height" diff --git a/spec/frontend/vue_shared/components/listbox_input/listbox_input_spec.js b/spec/frontend/vue_shared/components/listbox_input/listbox_input_spec.js new file mode 100644 index 00000000000..cb7262b15e3 --- /dev/null +++ b/spec/frontend/vue_shared/components/listbox_input/listbox_input_spec.js @@ -0,0 +1,132 @@ +import { shallowMount } from '@vue/test-utils'; +import { GlListbox } from '@gitlab/ui'; +import ListboxInput from '~/vue_shared/components/listbox_input/listbox_input.vue'; + +describe('ListboxInput', () => { + let wrapper; + + // Props + const name = 'name'; + const defaultToggleText = 'defaultToggleText'; + const items = [ + { + text: 'Group 1', + options: [ + { text: 'Item 1', value: '1' }, + { text: 'Item 2', value: '2' }, + ], + }, + { + text: 'Group 2', + options: [{ text: 'Item 3', value: '3' }], + }, + ]; + + // Finders + const findGlListbox = () => wrapper.findComponent(GlListbox); + const findInput = () => wrapper.find('input'); + + const createComponent = (propsData) => { + wrapper = shallowMount(ListboxInput, { + propsData: { + name, + defaultToggleText, + items, + ...propsData, + }, + }); + }; + + describe('input attributes', () => { + beforeEach(() => { + createComponent(); + }); + + it('sets the input name', () => { + expect(findInput().attributes('name')).toBe(name); + }); + }); + + describe('toggle text', () => { + it('uses the default toggle text while no value is selected', () => { + createComponent(); + + expect(findGlListbox().props('toggleText')).toBe(defaultToggleText); + }); + + it("uses the selected option's text as the toggle text", () => { + const selectedOption = items[0].options[0]; + createComponent({ selected: selectedOption.value }); + + expect(findGlListbox().props('toggleText')).toBe(selectedOption.text); + }); + }); + + describe('input value', () => { + const selectedOption = items[0].options[0]; + + beforeEach(() => { + createComponent({ selected: selectedOption.value }); + jest.spyOn(findInput().element, 'dispatchEvent'); + }); + + it("sets the listbox's and input's values", () => { + const { value } = selectedOption; + + expect(findGlListbox().props('selected')).toBe(value); + expect(findInput().attributes('value')).toBe(value); + }); + + describe("when the listbox's value changes", () => { + const newSelectedOption = items[1].options[0]; + + beforeEach(() => { + findGlListbox().vm.$emit('select', newSelectedOption.value); + }); + + it('emits the `select` event', () => { + expect(wrapper.emitted('select')).toEqual([[newSelectedOption.value]]); + }); + }); + }); + + describe('search', () => { + beforeEach(() => { + createComponent(); + }); + + it('passes all items to GlListbox by default', () => { + createComponent(); + expect(findGlListbox().props('items')).toStrictEqual(items); + }); + + describe('with groups', () => { + beforeEach(() => { + createComponent(); + findGlListbox().vm.$emit('search', '1'); + }); + + it('passes only the items that match the search string', async () => { + expect(findGlListbox().props('items')).toStrictEqual([ + { + text: 'Group 1', + options: [{ text: 'Item 1', value: '1' }], + }, + ]); + }); + }); + + describe('with flat items', () => { + beforeEach(() => { + createComponent({ + items: items[0].options, + }); + findGlListbox().vm.$emit('search', '1'); + }); + + it('passes only the items that match the search string', async () => { + expect(findGlListbox().props('items')).toStrictEqual([{ text: 'Item 1', value: '1' }]); + }); + }); + }); +}); diff --git a/spec/graphql/resolvers/design_management/design_resolver_spec.rb b/spec/graphql/resolvers/design_management/design_resolver_spec.rb index 0915dddf438..3a62f24993a 100644 --- a/spec/graphql/resolvers/design_management/design_resolver_spec.rb +++ b/spec/graphql/resolvers/design_management/design_resolver_spec.rb @@ -6,14 +6,14 @@ RSpec.describe Resolvers::DesignManagement::DesignResolver do include GraphqlHelpers include DesignManagementTestHelpers - specify do - expect(described_class).to have_nullable_graphql_type(::Types::DesignManagement::DesignType) - end - before do enable_design_management end + specify do + expect(described_class).to have_nullable_graphql_type(::Types::DesignManagement::DesignType) + end + describe '#resolve' do let_it_be(:issue) { create(:issue) } let_it_be(:project) { issue.project } diff --git a/spec/graphql/resolvers/design_management/designs_resolver_spec.rb b/spec/graphql/resolvers/design_management/designs_resolver_spec.rb index 64eae14d888..7024e62c670 100644 --- a/spec/graphql/resolvers/design_management/designs_resolver_spec.rb +++ b/spec/graphql/resolvers/design_management/designs_resolver_spec.rb @@ -6,14 +6,14 @@ RSpec.describe Resolvers::DesignManagement::DesignsResolver do include GraphqlHelpers include DesignManagementTestHelpers - specify do - expect(described_class).to have_nullable_graphql_type(::Types::DesignManagement::DesignType.connection_type) - end - before do enable_design_management end + specify do + expect(described_class).to have_nullable_graphql_type(::Types::DesignManagement::DesignType.connection_type) + end + describe '#resolve' do let_it_be(:issue) { create(:issue) } let_it_be(:project) { issue.project } diff --git a/spec/graphql/resolvers/project_pipeline_resolver_spec.rb b/spec/graphql/resolvers/project_pipeline_resolver_spec.rb index 398f8f52269..9250485e4c8 100644 --- a/spec/graphql/resolvers/project_pipeline_resolver_spec.rb +++ b/spec/graphql/resolvers/project_pipeline_resolver_spec.rb @@ -12,6 +12,10 @@ RSpec.describe Resolvers::ProjectPipelineResolver do let(:current_user) { create(:user) } + before do + project.add_developer(current_user) + end + specify do expect(described_class).to have_nullable_graphql_type(::Types::Ci::PipelineType) end @@ -20,10 +24,6 @@ RSpec.describe Resolvers::ProjectPipelineResolver do resolve(described_class, obj: project, args: args, ctx: { current_user: current_user }) end - before do - project.add_developer(current_user) - end - it 'resolves pipeline for the passed iid' do expect(Ci::PipelinesFinder) .to receive(:new) diff --git a/spec/helpers/listbox_helper_spec.rb b/spec/helpers/listbox_helper_spec.rb index cba00b43ae5..bae9c40aa02 100644 --- a/spec/helpers/listbox_helper_spec.rb +++ b/spec/helpers/listbox_helper_spec.rb @@ -30,7 +30,7 @@ RSpec.describe ListboxHelper do *%w[ dropdown b-dropdown - gl-new-dropdown + gl-dropdown btn-group js-redirect-listbox ]) diff --git a/spec/lib/banzai/filter/attributes_filter_spec.rb b/spec/lib/banzai/filter/attributes_filter_spec.rb new file mode 100644 index 00000000000..86af3364fef --- /dev/null +++ b/spec/lib/banzai/filter/attributes_filter_spec.rb @@ -0,0 +1,85 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Banzai::Filter::AttributesFilter, feature_category: :team_planning do + using RSpec::Parameterized::TableSyntax + include FilterSpecHelper + + def image + %(<img src="example.jpg">) + end + + it 'does not recognize new syntax when feature flag is off' do + stub_feature_flags(markdown_image_attributes: false) + doc = filter("#{image}{width=100}") + + expect(doc.to_s).to eq "#{image}{width=100}" + end + + describe 'attribute syntax' do + context 'when attribute syntax is valid' do + where(:text, :result) do + "#{image}{width=100}" | '<img src="example.jpg" width="100">' + "#{image}{ width=100 }" | '<img src="example.jpg" width="100">' + "#{image}{width=\"100\"}" | '<img src="example.jpg" width="100">' + "#{image}{width=100 width=200}" | '<img src="example.jpg" width="200">' + + "#{image}{.test_class width=100 style=\"width:400\"}" | '<img src="example.jpg" width="100">' + "<img src=\"example.jpg\" class=\"lazy\" />{width=100}" | '<img src="example.jpg" class="lazy" width="100">' + end + + with_them do + it 'adds them to the img' do + expect(filter(text).to_html).to eq result + end + end + end + + context 'when attribute syntax is invalid' do + where(:text, :result) do + "#{image} {width=100}" | '<img src="example.jpg"> {width=100}' + "#{image}{width=100\nheight=100}" | "<img src=\"example.jpg\">{width=100\nheight=100}" + "{width=100 height=100}\n#{image}" | "{width=100 height=100}\n<img src=\"example.jpg\">" + '<h1>header</h1>{width=100}' | '<h1>header</h1>{width=100}' + end + + with_them do + it 'does not recognize as attributes' do + expect(filter(text).to_html).to eq result + end + end + end + end + + describe 'height and width' do + context 'when size attributes are valid' do + where(:text, :result) do + "#{image}{width=100 height=200px}" | '<img src="example.jpg" width="100" height="200px">' + "#{image}{width=100}" | '<img src="example.jpg" width="100">' + "#{image}{width=100px}" | '<img src="example.jpg" width="100px">' + "#{image}{height=100%}" | '<img src="example.jpg" height="100%">' + "#{image}{width=\"100%\"}" | '<img src="example.jpg" width="100%">' + end + + with_them do + it 'adds them to the img' do + expect(filter(text).to_html).to eq result + end + end + end + + context 'when size attributes are invalid' do + where(:text, :result) do + "#{image}{width=100cs}" | '<img src="example.jpg">' + "#{image}{width=auto height=200}" | '<img src="example.jpg" height="200">' + end + + with_them do + it 'ignores them' do + expect(filter(text).to_html).to eq result + end + end + end + end +end diff --git a/spec/lib/banzai/filter/inline_grafana_metrics_filter_spec.rb b/spec/lib/banzai/filter/inline_grafana_metrics_filter_spec.rb index d29af311ee5..db0c10a802b 100644 --- a/spec/lib/banzai/filter/inline_grafana_metrics_filter_spec.rb +++ b/spec/lib/banzai/filter/inline_grafana_metrics_filter_spec.rb @@ -29,12 +29,12 @@ RSpec.describe Banzai::Filter::InlineGrafanaMetricsFilter do ) end - it_behaves_like 'a metrics embed filter' - around do |example| travel_to(Time.utc(2019, 3, 17, 13, 10)) { example.run } end + it_behaves_like 'a metrics embed filter' + context 'when grafana is not configured' do before do allow(project).to receive(:grafana_integration).and_return(nil) diff --git a/spec/lib/banzai/filter/references/user_reference_filter_spec.rb b/spec/lib/banzai/filter/references/user_reference_filter_spec.rb index b153efd9655..d61b71c711d 100644 --- a/spec/lib/banzai/filter/references/user_reference_filter_spec.rb +++ b/spec/lib/banzai/filter/references/user_reference_filter_spec.rb @@ -42,12 +42,12 @@ RSpec.describe Banzai::Filter::References::UserReferenceFilter do context 'mentioning @all' do let(:reference) { User.reference_prefix + 'all' } - it_behaves_like 'a reference containing an element node' - before do project.add_developer(project.creator) end + it_behaves_like 'a reference containing an element node' + it 'supports a special @all mention' do project.add_developer(user) doc = reference_filter("Hey #{reference}", author: user) diff --git a/spec/lib/feature/definition_spec.rb b/spec/lib/feature/definition_spec.rb index 3d11ad4c0d8..10334b3d0ea 100644 --- a/spec/lib/feature/definition_spec.rb +++ b/spec/lib/feature/definition_spec.rb @@ -111,6 +111,11 @@ RSpec.describe Feature::Definition do subject { described_class.send(:load_all!) } + after do + FileUtils.rm_rf(store1) + FileUtils.rm_rf(store2) + end + it "when there's no feature flags a list of definitions is empty" do is_expected.to be_empty end @@ -136,11 +141,6 @@ RSpec.describe Feature::Definition do .to raise_error(/Feature flag is missing name/) end - after do - FileUtils.rm_rf(store1) - FileUtils.rm_rf(store2) - end - def write_feature_flag(store, path, content) path = File.join(store, path) dir = File.dirname(path) diff --git a/spec/lib/gitlab/auth/saml/user_spec.rb b/spec/lib/gitlab/auth/saml/user_spec.rb index 796512bc52b..6c9598f920e 100644 --- a/spec/lib/gitlab/auth/saml/user_spec.rb +++ b/spec/lib/gitlab/auth/saml/user_spec.rb @@ -46,6 +46,10 @@ RSpec.describe Gitlab::Auth::Saml::User do end context 'external groups' do + before do + stub_saml_group_config(%w(Interns)) + end + context 'are defined' do it 'marks the user as external' do stub_saml_group_config(%w(Freelancers)) @@ -55,10 +59,6 @@ RSpec.describe Gitlab::Auth::Saml::User do end end - before do - stub_saml_group_config(%w(Interns)) - end - context 'are defined but the user does not belong there' do it 'does not mark the user as external' do saml_user.save # rubocop:disable Rails/SaveBang diff --git a/spec/lib/gitlab/email/handler/create_issue_handler_spec.rb b/spec/lib/gitlab/email/handler/create_issue_handler_spec.rb index ad183fd422b..8ff8de2379a 100644 --- a/spec/lib/gitlab/email/handler/create_issue_handler_spec.rb +++ b/spec/lib/gitlab/email/handler/create_issue_handler_spec.rb @@ -16,13 +16,13 @@ RSpec.describe Gitlab::Email::Handler::CreateIssueHandler do let(:namespace) { create(:namespace, path: 'gitlabhq') } let(:email_raw) { email_fixture('emails/valid_new_issue.eml') } - it_behaves_like 'reply processing shared examples' - before do stub_incoming_email_setting(enabled: true, address: "incoming+%{key}@appmail.adventuretime.ooo") stub_config_setting(host: 'localhost') end + it_behaves_like 'reply processing shared examples' + context "when email key" do let(:mail) { Mail::Message.new(email_raw) } diff --git a/spec/lib/gitlab/email/handler/create_merge_request_handler_spec.rb b/spec/lib/gitlab/email/handler/create_merge_request_handler_spec.rb index 60bb94e841f..f5b44d30c50 100644 --- a/spec/lib/gitlab/email/handler/create_merge_request_handler_spec.rb +++ b/spec/lib/gitlab/email/handler/create_merge_request_handler_spec.rb @@ -16,16 +16,16 @@ RSpec.describe Gitlab::Email::Handler::CreateMergeRequestHandler do let(:namespace) { create(:namespace, path: 'gitlabhq') } let(:email_raw) { email_fixture('emails/valid_new_merge_request.eml') } - it_behaves_like 'reply processing shared examples' + after do + TestEnv.clean_test_path + end before do stub_incoming_email_setting(enabled: true, address: "incoming+%{key}@appmail.adventuretime.ooo") stub_config_setting(host: 'localhost') end - after do - TestEnv.clean_test_path - end + it_behaves_like 'reply processing shared examples' context "when email key" do let(:mail) { Mail::Message.new(email_raw) } diff --git a/spec/lib/gitlab/email/handler/create_note_handler_spec.rb b/spec/lib/gitlab/email/handler/create_note_handler_spec.rb index 222fdb2088b..f70645a8272 100644 --- a/spec/lib/gitlab/email/handler/create_note_handler_spec.rb +++ b/spec/lib/gitlab/email/handler/create_note_handler_spec.rb @@ -15,6 +15,11 @@ RSpec.describe Gitlab::Email::Handler::CreateNoteHandler do SentNotification.record_note(note, user.id, mail_key) end + before do + stub_incoming_email_setting(enabled: true, address: "reply+%{key}@appmail.adventuretime.ooo") + stub_config_setting(host: 'localhost') + end + it_behaves_like 'reply processing shared examples' it_behaves_like 'note handler shared examples' do @@ -26,11 +31,6 @@ RSpec.describe Gitlab::Email::Handler::CreateNoteHandler do let(:with_quick_actions) { fixture_file('emails/valid_reply_with_quick_actions.eml') } end - before do - stub_incoming_email_setting(enabled: true, address: "reply+%{key}@appmail.adventuretime.ooo") - stub_config_setting(host: 'localhost') - end - context 'when the recipient address does not include a mail key' do let(:email_raw) { fixture_file('emails/valid_reply.eml').gsub(mail_key, '') } diff --git a/spec/lib/gitlab/email/hook/disable_email_interceptor_spec.rb b/spec/lib/gitlab/email/hook/disable_email_interceptor_spec.rb index 47f6015c6f8..b22c55208f0 100644 --- a/spec/lib/gitlab/email/hook/disable_email_interceptor_spec.rb +++ b/spec/lib/gitlab/email/hook/disable_email_interceptor_spec.rb @@ -7,15 +7,15 @@ RSpec.describe Gitlab::Email::Hook::DisableEmailInterceptor do Mail.register_interceptor(described_class) end + after do + Mail.unregister_interceptor(described_class) + end + it 'does not send emails' do allow(Gitlab.config.gitlab).to receive(:email_enabled).and_return(false) expect { deliver_mail }.not_to change(ActionMailer::Base.deliveries, :count) end - after do - Mail.unregister_interceptor(described_class) - end - def deliver_mail key = create :personal_key Notify.new_ssh_key_email(key.id) 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 41dce5d76dd..61945cc06b8 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 @@ -157,70 +157,47 @@ RSpec.describe Gitlab::GitalyClient::WithFeatureFlagActors do let(:call_arg_2) { double } let(:call_arg_3) { double } let(:call_result) { double } + let(:repository_actor) { instance_double(::Repository) } + let(:user_actor) { instance_double(::User) } + let(:project_actor) { instance_double(Project) } + let(:group_actor) { instance_double(Group) } before do + allow(service).to receive(:user_actor).and_return(user_actor) + allow(service).to receive(:repository_actor).and_return(repository_actor) + allow(service).to receive(:project_actor).and_return(project_actor) + allow(service).to receive(:group_actor).and_return(group_actor) + allow(Gitlab::GitalyClient).to receive(:with_feature_flag_actors).and_call_original allow(Gitlab::GitalyClient).to receive(:call).and_return(call_result) end - context 'when actors_aware_gitaly_calls flag is enabled' do - let(:repository_actor) { instance_double(::Repository) } - 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(service).to receive(:user_actor).and_return(user_actor) - allow(service).to receive(:repository_actor).and_return(repository_actor) - allow(service).to receive(:project_actor).and_return(project_actor) - allow(service).to receive(:group_actor).and_return(group_actor) - allow(Gitlab::GitalyClient).to receive(:with_feature_flag_actors).and_call_original - end - - it 'triggers client call with feature flag actors' do - result = service.gitaly_client_call(call_arg_1, call_arg_2, karg: call_arg_3) - - expect(Gitlab::GitalyClient).to have_received(:call).with(call_arg_1, call_arg_2, karg: call_arg_3) - expect(Gitlab::GitalyClient).to have_received(:with_feature_flag_actors).with( - repository: repository_actor, - user: user_actor, - project: project_actor, - group: group_actor - ) - 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 + it 'triggers client call with feature flag actors' do + result = service.gitaly_client_call(call_arg_1, call_arg_2, karg: call_arg_3) + + expect(Gitlab::GitalyClient).to have_received(:call).with(call_arg_1, call_arg_2, karg: call_arg_3) + expect(Gitlab::GitalyClient).to have_received(:with_feature_flag_actors).with( + repository: repository_actor, + user: user_actor, + project: project_actor, + group: group_actor + ) + expect(result).to be(call_result) end - context 'when actors_aware_gitaly_calls not enabled' do + context 'when call without repository_actor' do before do - stub_feature_flags(actors_aware_gitaly_calls: false) + 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 'triggers client call without feature flag actors' do - expect(Gitlab::GitalyClient).not_to receive(:with_feature_flag_actors) + 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/ - result = service.gitaly_client_call(call_arg_1, call_arg_2, karg: call_arg_3) - - expect(Gitlab::GitalyClient).to have_received(:call).with(call_arg_1, call_arg_2, karg: call_arg_3) - expect(result).to be(call_result) + expect(Gitlab::ErrorTracking).to have_received(:track_and_raise_for_dev_exception).with( + be_a(Feature::InvalidFeatureFlagError) + ) end end @@ -228,47 +205,28 @@ RSpec.describe Gitlab::GitalyClient::WithFeatureFlagActors 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 + let(:user_actor) { instance_double(::User) } + let(:project_actor) { instance_double(Project) } + let(:group_actor) { instance_double(Group) } - context 'when actors_aware_gitaly_calls not enabled' do - before do - stub_feature_flags(actors_aware_gitaly_calls: false) - end + before do + 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 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) + 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 + ) - result = service.gitaly_feature_flag_actors(repository_actor) - expect(result).to eql({}) - end + 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 end diff --git a/spec/lib/gitlab/pagination/offset_pagination_spec.rb b/spec/lib/gitlab/pagination/offset_pagination_spec.rb index ebbd207cc11..b1c4ffd6c29 100644 --- a/spec/lib/gitlab/pagination/offset_pagination_spec.rb +++ b/spec/lib/gitlab/pagination/offset_pagination_spec.rb @@ -101,6 +101,28 @@ RSpec.describe Gitlab::Pagination::OffsetPagination do end end + context 'when without_count is true' do + it_behaves_like 'paginated response' + + it 'does not return the X-Total and X-Total-Pages headers' do + expect_no_header('X-Total') + expect_no_header('X-Total-Pages') + expect_header('X-Per-Page', '2') + expect_header('X-Page', '1') + expect_header('X-Next-Page', '2') + expect_header('X-Prev-Page', '') + + expect_header('Link', anything) do |_key, val| + expect(val).to include(%Q(<#{incoming_api_projects_url}?#{query.merge(page: 1).to_query}>; rel="first")) + expect(val).to include(%Q(<#{incoming_api_projects_url}?#{query.merge(page: 2).to_query}>; rel="next")) + expect(val).not_to include('rel="last"') + expect(val).not_to include('rel="prev"') + end + + expect { subject.paginate(resource, without_count: true) }.to make_queries_matching(/SELECT COUNT/, 0) + end + end + it 'does not return the total headers when excluding them' do expect_no_header('X-Total') expect_no_header('X-Total-Pages') diff --git a/spec/lib/gitlab/tracking/event_definition_spec.rb b/spec/lib/gitlab/tracking/event_definition_spec.rb index 623009e9a30..c8e616b092b 100644 --- a/spec/lib/gitlab/tracking/event_definition_spec.rb +++ b/spec/lib/gitlab/tracking/event_definition_spec.rb @@ -83,6 +83,11 @@ RSpec.describe Gitlab::Tracking::EventDefinition do subject { described_class.definitions } + after do + FileUtils.rm_rf(metric1) + FileUtils.rm_rf(metric2) + end + it 'has empty list when there are no definition files' do is_expected.to be_empty end @@ -92,10 +97,5 @@ RSpec.describe Gitlab::Tracking::EventDefinition do is_expected.to be_one end - - after do - FileUtils.rm_rf(metric1) - FileUtils.rm_rf(metric2) - end end end diff --git a/spec/lib/gitlab/usage/metric_definition_spec.rb b/spec/lib/gitlab/usage/metric_definition_spec.rb index 931340947a2..4b835d11975 100644 --- a/spec/lib/gitlab/usage/metric_definition_spec.rb +++ b/spec/lib/gitlab/usage/metric_definition_spec.rb @@ -233,6 +233,11 @@ RSpec.describe Gitlab::Usage::MetricDefinition do subject { described_class.send(:load_all!) } + after do + FileUtils.rm_rf(metric1) + FileUtils.rm_rf(metric2) + end + it 'has empty list when there are no definition files' do is_expected.to be_empty end @@ -251,11 +256,6 @@ RSpec.describe Gitlab::Usage::MetricDefinition do subject end - - after do - FileUtils.rm_rf(metric1) - FileUtils.rm_rf(metric2) - end end describe 'dump_metrics_yaml' 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 e4cb1e74d32..7b5cfb57707 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 @@ -138,14 +138,14 @@ RSpec.describe Gitlab::UsageDataCounters::HLLRedisCounter, :clean_gitlab_redis_s File.open(ce_temp_file.path, "w+b") { |f| f.write [ce_event].to_yaml } end - it 'returns ce events' do - expect(described_class.known_events).to include(ce_event) - end - after do ce_temp_file.unlink FileUtils.remove_entry(ce_temp_dir) if Dir.exist?(ce_temp_dir) end + + it 'returns ce events' do + expect(described_class.known_events).to include(ce_event) + end end describe 'known_events' do diff --git a/spec/lib/mattermost/session_spec.rb b/spec/lib/mattermost/session_spec.rb index d208ef93224..87e2e341777 100644 --- a/spec/lib/mattermost/session_spec.rb +++ b/spec/lib/mattermost/session_spec.rb @@ -14,15 +14,15 @@ RSpec.describe Mattermost::Session, type: :request do subject { described_class.new(user) } # Needed for doorkeeper to function + before do + subject.base_uri = mattermost_url + end + it { is_expected.to respond_to(:current_resource_owner) } it { is_expected.to respond_to(:request) } it { is_expected.to respond_to(:authorization) } it { is_expected.to respond_to(:strategy) } - before do - subject.base_uri = mattermost_url - end - describe '#with session' do let(:location) { 'http://location.tld' } let(:cookie_header) { 'MMOAUTH=taskik8az7rq8k6rkpuas7htia; Path=/;' } diff --git a/spec/mailers/notify_spec.rb b/spec/mailers/notify_spec.rb index 5733e892d2a..bae92b78a24 100644 --- a/spec/mailers/notify_spec.rb +++ b/spec/mailers/notify_spec.rb @@ -419,15 +419,15 @@ RSpec.describe Notify do end it 'includes the reason in the footer' do - text = EmailsHelper.instance_method(:notification_reason_text).bind(self).call(reason: NotificationReason::ASSIGNED, format: :html) + text = EmailsHelper.instance_method(:notification_reason_text).bind_call(self, reason: NotificationReason::ASSIGNED, format: :html) is_expected.to have_body_text(text) new_subject = described_class.reassigned_merge_request_email(recipient.id, merge_request.id, [previous_assignee.id], current_user.id, NotificationReason::MENTIONED) - text = EmailsHelper.instance_method(:notification_reason_text).bind(self).call(reason: NotificationReason::MENTIONED, format: :html) + text = EmailsHelper.instance_method(:notification_reason_text).bind_call(self, reason: NotificationReason::MENTIONED, format: :html) expect(new_subject).to have_body_text(text) new_subject = described_class.reassigned_merge_request_email(recipient.id, merge_request.id, [previous_assignee.id], current_user.id, nil) - text = EmailsHelper.instance_method(:notification_reason_text).bind(self).call(format: :html) + text = EmailsHelper.instance_method(:notification_reason_text).bind_call(self, format: :html) expect(new_subject).to have_body_text(text) end end diff --git a/spec/models/ci/build_trace_chunk_spec.rb b/spec/models/ci/build_trace_chunk_spec.rb index 7fc916f0939..ac0a18a176d 100644 --- a/spec/models/ci/build_trace_chunk_spec.rb +++ b/spec/models/ci/build_trace_chunk_spec.rb @@ -15,13 +15,13 @@ RSpec.describe Ci::BuildTraceChunk, :clean_gitlab_redis_shared_state, :clean_git described_class.new(build: build, chunk_index: chunk_index, data_store: data_store, raw_data: raw_data) end - it_behaves_like 'having unique enum values' - before do stub_feature_flags(ci_enable_live_trace: true) stub_artifacts_object_storage end + it_behaves_like 'having unique enum values' + def redis_instance { redis: Gitlab::Redis::SharedState, diff --git a/spec/models/clusters/applications/ingress_spec.rb b/spec/models/clusters/applications/ingress_spec.rb index e5caa11452e..2be59e5f515 100644 --- a/spec/models/clusters/applications/ingress_spec.rb +++ b/spec/models/clusters/applications/ingress_spec.rb @@ -5,6 +5,11 @@ require 'spec_helper' RSpec.describe Clusters::Applications::Ingress do let(:ingress) { create(:clusters_applications_ingress) } + before do + allow(ClusterWaitForIngressIpAddressWorker).to receive(:perform_in) + allow(ClusterWaitForIngressIpAddressWorker).to receive(:perform_async) + end + it_behaves_like 'having unique enum values' include_examples 'cluster application core specs', :clusters_applications_ingress @@ -13,11 +18,6 @@ RSpec.describe Clusters::Applications::Ingress do include_examples 'cluster application helm specs', :clusters_applications_ingress include_examples 'cluster application initial status specs' - before do - allow(ClusterWaitForIngressIpAddressWorker).to receive(:perform_in) - allow(ClusterWaitForIngressIpAddressWorker).to receive(:perform_async) - end - describe 'default values' do it { expect(subject.ingress_type).to eq("nginx") } it { expect(subject.version).to eq(described_class::VERSION) } diff --git a/spec/models/clusters/applications/knative_spec.rb b/spec/models/clusters/applications/knative_spec.rb index 3914450339a..f6b13f4a93f 100644 --- a/spec/models/clusters/applications/knative_spec.rb +++ b/spec/models/clusters/applications/knative_spec.rb @@ -5,18 +5,18 @@ require 'spec_helper' RSpec.describe Clusters::Applications::Knative do let(:knative) { create(:clusters_applications_knative) } - include_examples 'cluster application core specs', :clusters_applications_knative - include_examples 'cluster application status specs', :clusters_applications_knative - include_examples 'cluster application helm specs', :clusters_applications_knative - include_examples 'cluster application version specs', :clusters_applications_knative - include_examples 'cluster application initial status specs' - before do allow(ClusterWaitForIngressIpAddressWorker).to receive(:perform_in) allow(ClusterWaitForIngressIpAddressWorker).to receive(:perform_async) allow(ClusterConfigureIstioWorker).to receive(:perform_async) end + include_examples 'cluster application core specs', :clusters_applications_knative + include_examples 'cluster application status specs', :clusters_applications_knative + include_examples 'cluster application helm specs', :clusters_applications_knative + include_examples 'cluster application version specs', :clusters_applications_knative + include_examples 'cluster application initial status specs' + describe 'associations' do it { is_expected.to have_one(:serverless_domain_cluster).class_name('::Serverless::DomainCluster').with_foreign_key('clusters_applications_knative_id').inverse_of(:knative) } end diff --git a/spec/models/concerns/has_user_type_spec.rb b/spec/models/concerns/has_user_type_spec.rb index b6e711e8325..f8a7763aeed 100644 --- a/spec/models/concerns/has_user_type_spec.rb +++ b/spec/models/concerns/has_user_type_spec.rb @@ -5,7 +5,8 @@ require 'spec_helper' RSpec.describe User do specify 'types consistency checks', :aggregate_failures do expect(described_class::USER_TYPES.keys) - .to match_array(%w[human ghost alert_bot project_bot support_bot service_user security_bot visual_review_bot migration_bot automation_bot admin_bot]) + .to match_array(%w[human ghost alert_bot project_bot support_bot service_user security_bot visual_review_bot + migration_bot automation_bot admin_bot suggested_reviewers_bot]) expect(described_class::USER_TYPES).to include(*described_class::BOT_USER_TYPES) expect(described_class::USER_TYPES).to include(*described_class::NON_INTERNAL_USER_TYPES) expect(described_class::USER_TYPES).to include(*described_class::INTERNAL_USER_TYPES) diff --git a/spec/models/integrations/chat_message/pipeline_message_spec.rb b/spec/models/integrations/chat_message/pipeline_message_spec.rb index f3388853b37..413cb097327 100644 --- a/spec/models/integrations/chat_message/pipeline_message_spec.rb +++ b/spec/models/integrations/chat_message/pipeline_message_spec.rb @@ -40,8 +40,6 @@ RSpec.describe Integrations::ChatMessage::PipelineMessage do let(:has_yaml_errors) { false } - it_behaves_like Integrations::ChatMessage - before do test_commit = double("A test commit", committer: args[:user], title: "A test commit message") test_project = build(:project, name: args[:project][:name]) @@ -62,6 +60,8 @@ RSpec.describe Integrations::ChatMessage::PipelineMessage do allow(Gitlab::UrlBuilder).to receive(:build).with(args[:user]).and_return("http://example.gitlab.com/hacker") end + it_behaves_like Integrations::ChatMessage + it 'returns an empty pretext' do expect(subject.pretext).to be_empty end diff --git a/spec/models/repository_spec.rb b/spec/models/repository_spec.rb index c17e180f282..01e5c3d04db 100644 --- a/spec/models/repository_spec.rb +++ b/spec/models/repository_spec.rb @@ -164,11 +164,11 @@ RSpec.describe Repository do repository.add_tag(user, annotated_tag_name, 'a48e4fc218069f68ef2e769dd8dfea3991362175', 'test tag message\n') end - it { is_expected.to eq(['v1.0.0', 'v1.1.0', annotated_tag_name]) } - after do repository.rm_tag(user, annotated_tag_name) end + + it { is_expected.to eq(['v1.0.0', 'v1.1.0', annotated_tag_name]) } end end diff --git a/spec/requests/api/ci/jobs_spec.rb b/spec/requests/api/ci/jobs_spec.rb index c1b7461f444..fcf3ab201fb 100644 --- a/spec/requests/api/ci/jobs_spec.rb +++ b/spec/requests/api/ci/jobs_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe API::Ci::Jobs do +RSpec.describe API::Ci::Jobs, feature_category: :continuous_integration do include HttpBasicAuthHelpers include DependencyProxyHelpers @@ -325,7 +325,7 @@ RSpec.describe API::Ci::Jobs do context 'authorized user' do it 'returns project jobs' do expect(response).to have_gitlab_http_status(:ok) - expect(response).to include_pagination_headers + expect(response).to include_limited_pagination_headers expect(json_response).to be_an Array end diff --git a/spec/requests/api/internal/kubernetes_spec.rb b/spec/requests/api/internal/kubernetes_spec.rb index 3c6604cf409..f8fd81953b2 100644 --- a/spec/requests/api/internal/kubernetes_spec.rb +++ b/spec/requests/api/internal/kubernetes_spec.rb @@ -103,15 +103,19 @@ RSpec.describe API::Internal::Kubernetes do expect(response).to have_gitlab_http_status(:bad_request) end - it 'tracks events' do + it 'tracks events and unique events', :aggregate_failures do + request_count = 2 counters = { gitops_sync: 10, k8s_api_proxy_request: 5 } - unique_counters = { agent_users_using_ci_tunnel: [10] } + unique_counters = { agent_users_using_ci_tunnel: [10, 999, 777, 10] } expected_counters = { - kubernetes_agent_gitops_sync: counters[:gitops_sync], - kubernetes_agent_k8s_api_proxy_request: counters[:k8s_api_proxy_request] + kubernetes_agent_gitops_sync: request_count * counters[:gitops_sync], + kubernetes_agent_k8s_api_proxy_request: request_count * counters[:k8s_api_proxy_request] } + expected_hll_count = unique_counters[:agent_users_using_ci_tunnel].uniq.count - send_request(params: { counters: counters, unique_counters: unique_counters }) + request_count.times do + send_request(params: { counters: counters, unique_counters: unique_counters }) + end expect(Gitlab::UsageDataCounters::KubernetesAgentCounter.totals).to eq(expected_counters) @@ -121,7 +125,7 @@ RSpec.describe API::Internal::Kubernetes do event_names: 'agent_users_using_ci_tunnel', start_date: Date.current, end_date: Date.current + 10 ) - ).to eq(1) + ).to eq(expected_hll_count) end end end diff --git a/spec/serializers/pipeline_details_entity_spec.rb b/spec/serializers/pipeline_details_entity_spec.rb index ca38721cc7f..764d55e8b4a 100644 --- a/spec/serializers/pipeline_details_entity_spec.rb +++ b/spec/serializers/pipeline_details_entity_spec.rb @@ -11,16 +11,16 @@ RSpec.describe PipelineDetailsEntity do described_class.represent(pipeline, request: request) end - it 'inherits from PipelineEntity' do - expect(described_class).to be < Ci::PipelineEntity - end - before do stub_not_protect_default_branch allow(request).to receive(:current_user).and_return(user) end + it 'inherits from PipelineEntity' do + expect(described_class).to be < Ci::PipelineEntity + end + describe '#as_json' do subject { entity.as_json } diff --git a/spec/services/feature_flags/hook_service_spec.rb b/spec/services/feature_flags/hook_service_spec.rb index 19c935e43f3..f3edaca52a9 100644 --- a/spec/services/feature_flags/hook_service_spec.rb +++ b/spec/services/feature_flags/hook_service_spec.rb @@ -14,14 +14,14 @@ RSpec.describe FeatureFlags::HookService do subject(:service) { described_class.new(feature_flag, user) } - describe 'HOOK_NAME' do - specify { expect(described_class::HOOK_NAME).to eq(:feature_flag_hooks) } - end - before do allow(Gitlab::DataBuilder::FeatureFlag).to receive(:build).with(feature_flag, user).once.and_return(hook_data) end + describe 'HOOK_NAME' do + specify { expect(described_class::HOOK_NAME).to eq(:feature_flag_hooks) } + end + it 'calls feature_flag.project.execute_hooks' do expect(feature_flag.project).to receive(:execute_hooks).with(hook_data, described_class::HOOK_NAME) diff --git a/spec/services/projects/container_repository/gitlab/delete_tags_service_spec.rb b/spec/services/projects/container_repository/gitlab/delete_tags_service_spec.rb index 8d8907119f0..f03912dba80 100644 --- a/spec/services/projects/container_repository/gitlab/delete_tags_service_spec.rb +++ b/spec/services/projects/container_repository/gitlab/delete_tags_service_spec.rb @@ -24,6 +24,10 @@ RSpec.describe Projects::ContainerRepository::Gitlab::DeleteTagsService do context 'with tags to delete' do let(:timeout) { 10 } + before do + stub_application_setting(container_registry_delete_tags_service_timeout: timeout) + end + it_behaves_like 'deleting tags' it 'succeeds when tag delete returns 404' do @@ -48,10 +52,6 @@ RSpec.describe Projects::ContainerRepository::Gitlab::DeleteTagsService do end end - before do - stub_application_setting(container_registry_delete_tags_service_timeout: timeout) - end - context 'with timeout' do context 'set to a valid value' do before do diff --git a/spec/support/helpers/listbox_input_helper.rb b/spec/support/helpers/listbox_input_helper.rb new file mode 100644 index 00000000000..ca7fbac5daa --- /dev/null +++ b/spec/support/helpers/listbox_input_helper.rb @@ -0,0 +1,18 @@ +# frozen_string_literal: true + +module ListboxInputHelper + include WaitForRequests + + def listbox_input(value, from:) + open_listbox_input(from) do + find('[role="option"]', text: value).click + end + end + + def open_listbox_input(selector) + page.within(selector) do + page.find('button[aria-haspopup="listbox"]').click + yield + end + end +end diff --git a/spec/support/patches/rspec_mocks_prepended_methods.rb b/spec/support/patches/rspec_mocks_prepended_methods.rb index fa3a74c670c..d51fb37a499 100644 --- a/spec/support/patches/rspec_mocks_prepended_methods.rb +++ b/spec/support/patches/rspec_mocks_prepended_methods.rb @@ -45,7 +45,7 @@ module RSpec private def method_owner - @method_owner ||= Object.instance_method(:method).bind(object).call(@method_name).owner + @method_owner ||= Object.instance_method(:method).bind_call(object, @method_name).owner end end end diff --git a/spec/support/shared_examples/features/content_editor_shared_examples.rb b/spec/support/shared_examples/features/content_editor_shared_examples.rb index f01e3c88dad..efdf7513b2d 100644 --- a/spec/support/shared_examples/features/content_editor_shared_examples.rb +++ b/spec/support/shared_examples/features/content_editor_shared_examples.rb @@ -327,7 +327,7 @@ RSpec.shared_examples 'edits content using the content editor' do end def dropdown_scroll_top - evaluate_script("document.querySelector('#{suggestions_dropdown} .gl-new-dropdown-inner').scrollTop") + evaluate_script("document.querySelector('#{suggestions_dropdown} .gl-dropdown-inner').scrollTop") end end end diff --git a/spec/support/shared_examples/features/sidebar/sidebar_labels_shared_examples.rb b/spec/support/shared_examples/features/sidebar/sidebar_labels_shared_examples.rb index 281a70e46c4..95b306fdaaa 100644 --- a/spec/support/shared_examples/features/sidebar/sidebar_labels_shared_examples.rb +++ b/spec/support/shared_examples/features/sidebar/sidebar_labels_shared_examples.rb @@ -17,7 +17,7 @@ RSpec.shared_examples 'labels sidebar widget' do end it 'shows labels list in the dropdown' do - expect(labels_widget.find('.gl-new-dropdown-contents')).to have_selector('li.gl-new-dropdown-item', count: 4) + expect(labels_widget.find('.gl-dropdown-contents')).to have_selector('li.gl-dropdown-item', count: 4) end it 'adds a label' do @@ -57,8 +57,8 @@ RSpec.shared_examples 'labels sidebar widget' do expect(page).to have_css('.labels-fetch-loading') wait_for_all_requests - expect(page).to have_css('[data-testid="dropdown-content"] .gl-new-dropdown-item') - expect(page.all(:css, '[data-testid="dropdown-content"] .gl-new-dropdown-item').length).to eq(1) + expect(page).to have_css('[data-testid="dropdown-content"] .gl-dropdown-item') + expect(page.all(:css, '[data-testid="dropdown-content"] .gl-dropdown-item').length).to eq(1) find_field('Search').native.send_keys(:enter) click_button 'Close' diff --git a/spec/support/shared_examples/features/sidebar/sidebar_milestone_shared_examples.rb b/spec/support/shared_examples/features/sidebar/sidebar_milestone_shared_examples.rb index da730240e8e..f8982c242f8 100644 --- a/spec/support/shared_examples/features/sidebar/sidebar_milestone_shared_examples.rb +++ b/spec/support/shared_examples/features/sidebar/sidebar_milestone_shared_examples.rb @@ -20,15 +20,15 @@ RSpec.shared_examples 'milestone sidebar widget' do it 'shows milestones list in the dropdown' do # 5 milestones + "No milestone" = 6 items - expect(milestone_widget.find('.gl-new-dropdown-contents')).to have_selector('li.gl-new-dropdown-item', count: 6) + expect(milestone_widget.find('.gl-dropdown-contents')).to have_selector('li.gl-dropdown-item', count: 6) end it 'shows expired milestone at the bottom of the list and milestone due earliest at the top of the list', :aggregate_failures do - within(milestone_widget, '.gl-new-dropdown-contents') do + within(milestone_widget, '.gl-dropdown-contents') do expect(page.find('li:last-child')).to have_content milestone_expired.title [milestone3, milestone2, milestone1, milestone_no_duedate].each_with_index do |m, i| - expect(page.all('li.gl-new-dropdown-item')[i + 1]).to have_content m.title + expect(page.all('li.gl-dropdown-item')[i + 1]).to have_content m.title end end end diff --git a/spec/support/shared_examples/lib/gitlab/cycle_analytics/deployment_metrics.rb b/spec/support/shared_examples/lib/gitlab/cycle_analytics/deployment_metrics.rb index 9ffc55f7e7e..d471a758f3e 100644 --- a/spec/support/shared_examples/lib/gitlab/cycle_analytics/deployment_metrics.rb +++ b/spec/support/shared_examples/lib/gitlab/cycle_analytics/deployment_metrics.rb @@ -62,14 +62,14 @@ RSpec.shared_examples 'deployment metrics examples' do describe '#deployment_frequency' do subject { stage_summary.fourth[:value] } - it 'includes the unit: `/day`' do - expect(stage_summary.fourth[:unit]).to eq _('/day') - end - before do travel_to(5.days.ago) { create_deployment(project: project) } end + it 'includes the unit: `/day`' do + expect(stage_summary.fourth[:unit]).to eq _('/day') + end + it 'returns 0.0 when there were deploys but the frequency was too low' do options[:from] = 30.days.ago diff --git a/spec/workers/merge_requests/delete_branch_worker_spec.rb b/spec/workers/merge_requests/delete_branch_worker_spec.rb deleted file mode 100644 index 80ca8c061f5..00000000000 --- a/spec/workers/merge_requests/delete_branch_worker_spec.rb +++ /dev/null @@ -1,65 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -RSpec.describe MergeRequests::DeleteBranchWorker do - let_it_be(:merge_request) { create(:merge_request) } - let_it_be(:user) { create(:user) } - - let(:branch) { merge_request.source_branch } - let(:sha) { merge_request.source_branch_sha } - let(:retarget_branch) { true } - let(:worker) { described_class.new } - - describe '#perform' do - context 'with a non-existing merge request' do - it 'does nothing' do - expect(::Branches::DeleteService).not_to receive(:new) - worker.perform(non_existing_record_id, user.id, branch, retarget_branch) - end - end - - context 'with a non-existing user' do - it 'does nothing' do - expect(::Branches::DeleteService).not_to receive(:new) - - worker.perform(merge_request.id, non_existing_record_id, branch, retarget_branch) - end - end - - context 'with existing user and merge request' do - it 'calls service to delete source branch' do - expect_next_instance_of(::Branches::DeleteService) do |instance| - expect(instance).to receive(:execute).with(branch) - end - - worker.perform(merge_request.id, user.id, branch, retarget_branch) - end - - context 'when retarget branch param is true' do - it 'calls the retarget chain service' do - expect_next_instance_of(::MergeRequests::RetargetChainService) do |instance| - expect(instance).to receive(:execute).with(merge_request) - end - - worker.perform(merge_request.id, user.id, branch, retarget_branch) - end - end - - context 'when retarget branch param is false' do - let(:retarget_branch) { false } - - it 'does not call the retarget chain service' do - expect(::MergeRequests::RetargetChainService).not_to receive(:new) - - worker.perform(merge_request.id, user.id, branch, retarget_branch) - end - end - end - - it_behaves_like 'an idempotent worker' do - let(:merge_request) { create(:merge_request) } - let(:job_args) { [merge_request.id, sha, user.id, true] } - end - end -end diff --git a/spec/workers/merge_requests/delete_source_branch_worker_spec.rb b/spec/workers/merge_requests/delete_source_branch_worker_spec.rb index 2935d3ef5dc..a7e4ffad259 100644 --- a/spec/workers/merge_requests/delete_source_branch_worker_spec.rb +++ b/spec/workers/merge_requests/delete_source_branch_worker_spec.rb @@ -3,17 +3,24 @@ require 'spec_helper' RSpec.describe MergeRequests::DeleteSourceBranchWorker do - let_it_be(:merge_request) { create(:merge_request) } let_it_be(:user) { create(:user) } + let_it_be(:merge_request) { create(:merge_request, author: user) } let(:sha) { merge_request.source_branch_sha } let(:worker) { described_class.new } describe '#perform' do + before do + allow_next_instance_of(::Projects::DeleteBranchWorker) do |instance| + allow(instance).to receive(:perform).with(merge_request.source_project.id, user.id, + merge_request.source_branch) + end + end + context 'when the add_delete_branch_worker feature flag is enabled' do context 'with a non-existing merge request' do it 'does nothing' do - expect(::MergeRequests::DeleteBranchWorker).not_to receive(:perform_async) + expect(::Projects::DeleteBranchWorker).not_to receive(:new) worker.perform(non_existing_record_id, sha, user.id) end @@ -21,7 +28,7 @@ RSpec.describe MergeRequests::DeleteSourceBranchWorker do context 'with a non-existing user' do it 'does nothing' do - expect(::MergeRequests::DeleteBranchWorker).not_to receive(:perform_async) + expect(::Projects::DeleteBranchWorker).not_to receive(:new) worker.perform(merge_request.id, sha, non_existing_record_id) end @@ -29,15 +36,17 @@ RSpec.describe MergeRequests::DeleteSourceBranchWorker do context 'with existing user and merge request' do it 'creates a new delete branch worker async' do - expect(::MergeRequests::DeleteBranchWorker).to receive(:perform_async).with(merge_request.id, user.id, - merge_request.source_branch, true) + expect_next_instance_of(::Projects::DeleteBranchWorker) do |instance| + expect(instance).to receive(:perform).with(merge_request.source_project.id, user.id, + merge_request.source_branch) + end worker.perform(merge_request.id, sha, user.id) end context 'source branch sha does not match' do it 'does nothing' do - expect(::MergeRequests::DeleteBranchWorker).not_to receive(:perform_async) + expect(::Projects::DeleteBranchWorker).not_to receive(:new) worker.perform(merge_request.id, 'new-source-branch-sha', user.id) end @@ -45,7 +54,6 @@ RSpec.describe MergeRequests::DeleteSourceBranchWorker do end it_behaves_like 'an idempotent worker' do - let(:merge_request) { create(:merge_request) } let(:job_args) { [merge_request.id, sha, user.id] } end end @@ -117,7 +125,6 @@ RSpec.describe MergeRequests::DeleteSourceBranchWorker do end it_behaves_like 'an idempotent worker' do - let(:merge_request) { create(:merge_request) } let(:job_args) { [merge_request.id, sha, user.id] } end end diff --git a/spec/workers/pipeline_schedule_worker_spec.rb b/spec/workers/pipeline_schedule_worker_spec.rb index 4a7db0eca56..d23907a8def 100644 --- a/spec/workers/pipeline_schedule_worker_spec.rb +++ b/spec/workers/pipeline_schedule_worker_spec.rb @@ -17,8 +17,12 @@ RSpec.describe PipelineScheduleWorker do before do stub_application_setting(auto_devops_enabled: false) stub_ci_pipeline_to_return_yaml_file + end - pipeline_schedule.update_column(:next_run_at, 1.day.ago) + around do |example| + travel_to(pipeline_schedule.next_run_at + 1.hour) do + example.run + end end context 'when the schedule is runnable by the user' do @@ -26,16 +30,22 @@ RSpec.describe PipelineScheduleWorker do project.add_maintainer(user) end - context 'when there is a scheduled pipeline within next_run_at' do + context 'when there is a scheduled pipeline within next_run_at', :sidekiq_inline do shared_examples 'successful scheduling' do - it 'creates a new pipeline', :sidekiq_might_not_need_inline do + it 'creates a new pipeline' do expect { subject }.to change { project.ci_pipelines.count }.by(1) - expect(Ci::Pipeline.last).to be_schedule + last_pipeline = project.ci_pipelines.last + + expect(last_pipeline).to be_schedule + expect(last_pipeline.pipeline_schedule).to eq(pipeline_schedule) + end + + it 'updates next_run_at' do + expect { subject }.to change { pipeline_schedule.reload.next_run_at }.by(1.day) + end - pipeline_schedule.reload - expect(pipeline_schedule.next_run_at).to be > Time.current - expect(pipeline_schedule).to eq(project.ci_pipelines.last.pipeline_schedule) - expect(pipeline_schedule).to be_active + it 'does not change active status' do + expect { subject }.not_to change { pipeline_schedule.reload.active? }.from(true) end end diff --git a/spec/workers/projects/delete_branch_worker_spec.rb b/spec/workers/projects/delete_branch_worker_spec.rb new file mode 100644 index 00000000000..c1289f56929 --- /dev/null +++ b/spec/workers/projects/delete_branch_worker_spec.rb @@ -0,0 +1,112 @@ +# frozen_string_literal: true +# rubocop: disable Gitlab/ServiceResponse + +require 'spec_helper' + +RSpec.describe Projects::DeleteBranchWorker, feature_category: :source_code_management do + let_it_be(:project) { create(:project, :repository) } + let_it_be(:user) { create(:user) } + + let(:branch) { 'master' } + let(:worker) { described_class.new } + let(:service_result) { ServiceResponse.success(message: 'placeholder', http_status: 200) } + + before do + allow_next_instance_of(::Branches::DeleteService) do |instance| + allow(instance).to receive(:execute).with(branch).and_return(service_result) + end + end + + describe '#perform' do + context 'when the branch does not exist' do + let(:branch) { 'non_existent_branch_name' } + + it 'does nothing' do + expect(::Branches::DeleteService).not_to receive(:new) + + worker.perform(project.id, user.id, branch) + end + end + + context 'with a non-existing project' do + it 'does nothing' do + expect(::Branches::DeleteService).not_to receive(:new) + + worker.perform(non_existing_record_id, user.id, branch) + end + end + + context 'with a non-existing user' do + it 'does nothing' do + expect(::Branches::DeleteService).not_to receive(:new) + + worker.perform(project.id, non_existing_record_id, branch) + end + end + + context 'with existing user and project' do + it 'calls service to delete source branch' do + expect_next_instance_of(::Branches::DeleteService) do |instance| + expect(instance).to receive(:execute).with(branch).and_return(service_result) + end + + worker.perform(project.id, user.id, branch) + end + + context 'when delete service returns an error' do + let(:service_result) { ServiceResponse.error(message: 'placeholder', http_status: status_code) } + + context 'when the status code is 400' do + let(:status_code) { 400 } + + it 'tracks and raises the exception' do + expect_next_instance_of(::Branches::DeleteService) do |instance| + expect(instance).to receive(:execute).with(branch).and_return(service_result) + end + + expect(service_result).to receive(:track_and_raise_exception).and_call_original + + expect { worker.perform(project.id, user.id, branch) }.to raise_error(StandardError) + end + end + + context 'when the status code is not 400' do + let(:status_code) { 405 } + + it 'does not track the exception' do + expect_next_instance_of(::Branches::DeleteService) do |instance| + expect(instance).to receive(:execute).with(branch).and_return(service_result) + end + + expect(service_result).not_to receive(:track_and_raise_exception) + + expect { worker.perform(project.id, user.id, branch) }.not_to raise_error + end + end + + context 'when track_and_raise_delete_source_errors is disabled' do + let(:status_code) { 400 } + + before do + stub_feature_flags(track_and_raise_delete_source_errors: false) + end + + it 'does not track the exception' do + expect_next_instance_of(::Branches::DeleteService) do |instance| + expect(instance).to receive(:execute).with(branch).and_return(service_result) + end + + expect(service_result).not_to receive(:track_and_raise_exception) + + expect { worker.perform(project.id, user.id, branch) }.not_to raise_error + end + end + end + end + + it_behaves_like 'an idempotent worker' do + let(:job_args) { [project.id, user.id, branch] } + end + end + # rubocop: enable Gitlab/ServiceResponse +end diff --git a/spec/workers/run_pipeline_schedule_worker_spec.rb b/spec/workers/run_pipeline_schedule_worker_spec.rb index 5fa7c5d64db..4fdf6149435 100644 --- a/spec/workers/run_pipeline_schedule_worker_spec.rb +++ b/spec/workers/run_pipeline_schedule_worker_spec.rb @@ -3,6 +3,10 @@ require 'spec_helper' RSpec.describe RunPipelineScheduleWorker do + it 'has an until_executed deduplicate strategy' do + expect(described_class.get_deduplicate_strategy).to eq(:until_executed) + end + describe '#perform' do let_it_be(:group) { create(:group) } let_it_be(:project) { create(:project, namespace: group) } @@ -11,6 +15,12 @@ RSpec.describe RunPipelineScheduleWorker do let(:worker) { described_class.new } + around do |example| + travel_to(pipeline_schedule.next_run_at + 1.hour) do + example.run + end + end + context 'when a schedule not found' do it 'does not call the Service' do expect(Ci::CreatePipelineService).not_to receive(:new) diff --git a/yarn.lock b/yarn.lock index 87b8973b151..2a95e1f2a1f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1125,10 +1125,10 @@ resolved "https://registry.yarnpkg.com/@gitlab/svgs/-/svgs-3.13.0.tgz#2d62286c956bd49ba7156b2aa4eed79507baca53" integrity sha512-Yv4dZ4pOyUVMCZXNxLuMinZ/x8E6+g8/yM1z/2ERT0t7hSAC3bCUHn2OEFpujtYzFtwMZXMFPQFEJJipQ1I/+w== -"@gitlab/ui@50.1.2": - version "50.1.2" - resolved "https://registry.yarnpkg.com/@gitlab/ui/-/ui-50.1.2.tgz#d24100b6d6fc77c7708d9139f09e2142b1292d31" - integrity sha512-bRzF9SkDGY2WrmIuFriFLyRMym2ydAeJB71FCXfHvhei3EWAeaiZv5oEB/a4oMN8nmt0rt4GrPQ5PpGiQKoKfQ== +"@gitlab/ui@51.1.1": + version "51.1.1" + resolved "https://registry.yarnpkg.com/@gitlab/ui/-/ui-51.1.1.tgz#fa6d3bee9a08865fadbf6a7d753785b0fbb964a2" + integrity sha512-s2bupr0Fv4jR1Pdt7kMSTJa9gQ2IxGMx/3MWbqnrn20bUSaeI3eUSmrBzzar7My9/mJVS6JYgfN3JN+V0XuTTg== dependencies: "@popperjs/core" "^2.11.2" bootstrap-vue "2.20.1" |