diff options
40 files changed, 531 insertions, 303 deletions
diff --git a/.rubocop_todo/style/if_unless_modifier.yml b/.rubocop_todo/style/if_unless_modifier.yml index a58f71aee8f..cc21860d462 100644 --- a/.rubocop_todo/style/if_unless_modifier.yml +++ b/.rubocop_todo/style/if_unless_modifier.yml @@ -652,6 +652,7 @@ Style/IfUnlessModifier: - 'ee/app/services/vulnerability_feedback/create_service.rb' - 'ee/app/services/vulnerability_feedback/destroy_service.rb' - 'ee/app/services/vulnerability_feedback_module/update_service.rb' + - 'ee/app/services/elastic/cluster_reindexing_service.rb' - 'ee/app/validators/host_validator.rb' - 'ee/app/validators/password/complexity_validator.rb' - 'ee/app/workers/app_sec/dast/profile_schedule_worker.rb' diff --git a/.rubocop_todo/style/next.yml b/.rubocop_todo/style/next.yml index 4106cba955f..9570bd7b036 100644 --- a/.rubocop_todo/style/next.yml +++ b/.rubocop_todo/style/next.yml @@ -3,17 +3,7 @@ Style/Next: Exclude: - 'app/models/concerns/integrations/slack_mattermost_notifier.rb' - - 'app/models/preloaders/environments/deployment_preloader.rb' - - 'app/models/route.rb' - - 'app/services/authorized_project_update/find_records_due_for_refresh_service.rb' - 'app/validators/nested_attributes_duplicates_validator.rb' - - 'config/initializers/01_secret_token.rb' - - 'config/initializers/sidekiq_cluster.rb' - - 'ee/app/controllers/groups/analytics/cycle_analytics/value_streams_controller.rb' - - 'ee/app/services/app_sec/dast/profiles/create_associations_service.rb' - - 'ee/app/services/elastic/cluster_reindexing_service.rb' - - 'ee/app/services/gitlab_subscriptions/fetch_purchase_eligible_namespaces_service.rb' - - 'ee/app/services/security/auto_fix_service.rb' - 'ee/app/services/security/ingestion/tasks/update_vulnerability_uuids.rb' - 'ee/db/fixtures/development/20_vulnerabilities.rb' - 'ee/lib/ee/audit/protected_branches_changes_auditor.rb' diff --git a/app/assets/javascripts/ci_variable_list/index.js b/app/assets/javascripts/ci_variable_list/index.js index a74af8aed12..d4924c7e20b 100644 --- a/app/assets/javascripts/ci_variable_list/index.js +++ b/app/assets/javascripts/ci_variable_list/index.js @@ -77,7 +77,7 @@ const mountLegacyCiVariableListApp = (containerEl) => { const { endpoint, projectId, - group, + isGroup, maskableRegex, protectedByDefault, awsLogoSvgPath, @@ -89,13 +89,13 @@ const mountLegacyCiVariableListApp = (containerEl) => { maskedEnvironmentVariablesLink, environmentScopeLink, } = containerEl.dataset; - const isGroup = parseBoolean(group); + const parsedIsGroup = parseBoolean(isGroup); const isProtectedByDefault = parseBoolean(protectedByDefault); const store = createStore({ endpoint, projectId, - isGroup, + isGroup: parsedIsGroup, maskableRegex, isProtectedByDefault, awsLogoSvgPath, diff --git a/app/assets/javascripts/editor/extensions/source_editor_webide_ext.js b/app/assets/javascripts/editor/extensions/source_editor_webide_ext.js index 6270517b3f3..bacc05af3f1 100644 --- a/app/assets/javascripts/editor/extensions/source_editor_webide_ext.js +++ b/app/assets/javascripts/editor/extensions/source_editor_webide_ext.js @@ -60,7 +60,7 @@ const renderSideBySide = (domElement) => { const updateDiffInstanceRendering = (instance) => { instance.updateOptions({ - renderSideBySide: renderSideBySide(instance.getDomNode()), + renderSideBySide: renderSideBySide(instance.getContainerDomNode()), }); }; diff --git a/app/assets/javascripts/editor/utils.js b/app/assets/javascripts/editor/utils.js index df9d3f2b9fb..cb38ccf0043 100644 --- a/app/assets/javascripts/editor/utils.js +++ b/app/assets/javascripts/editor/utils.js @@ -26,7 +26,7 @@ export const getBlobLanguage = (blobPath) => { const ext = `.${blobPath.split('.').pop()}`; const language = monacoLanguages .getLanguages() - .find((lang) => lang.extensions.indexOf(ext) !== -1); + .find((lang) => lang.extensions?.indexOf(ext) >= 0); return language ? language.id : defaultLanguage; }; diff --git a/app/assets/javascripts/ide/lib/editor.js b/app/assets/javascripts/ide/lib/editor.js index 80191f635a3..4f2564556f8 100644 --- a/app/assets/javascripts/ide/lib/editor.js +++ b/app/assets/javascripts/ide/lib/editor.js @@ -184,7 +184,7 @@ export default class Editor { if (!this.isDiffEditorType) return; this.instance.updateOptions({ - renderSideBySide: Editor.renderSideBySide(this.instance.getDomNode()), + renderSideBySide: Editor.renderSideBySide(this.instance.getContainerDomNode()), }); } diff --git a/app/assets/javascripts/ide/utils.js b/app/assets/javascripts/ide/utils.js index a7e6506b045..83a3d7f2ac3 100644 --- a/app/assets/javascripts/ide/utils.js +++ b/app/assets/javascripts/ide/utils.js @@ -1,5 +1,6 @@ import { flatten, isString } from 'lodash'; import { languages } from 'monaco-editor'; +import { setDiagnosticsOptions as yamlDiagnosticsOptions } from 'monaco-yaml'; import { performanceMarkAndMeasure } from '~/performance/utils'; import { SIDE_LEFT, SIDE_RIGHT } from './constants'; @@ -82,17 +83,16 @@ export function registerLanguages(def, ...defs) { } export function registerSchema(schema, options = {}) { - const defaults = [languages.json.jsonDefaults, languages.yaml.yamlDefaults]; - defaults.forEach((d) => - d.setDiagnosticsOptions({ - validate: true, - enableSchemaRequest: true, - hover: true, - completion: true, - schemas: [schema], - ...options, - }), - ); + const defaultOptions = { + validate: true, + enableSchemaRequest: true, + hover: true, + completion: true, + schemas: [schema], + ...options, + }; + languages.json.jsonDefaults.setDiagnosticsOptions(defaultOptions); + yamlDiagnosticsOptions(defaultOptions); } export const otherSide = (side) => (side === SIDE_RIGHT ? SIDE_LEFT : SIDE_RIGHT); diff --git a/app/assets/javascripts/issues/show/components/incidents/timeline_events_item.vue b/app/assets/javascripts/issues/show/components/incidents/timeline_events_item.vue index 6175c9969ec..83a35e52a12 100644 --- a/app/assets/javascripts/issues/show/components/incidents/timeline_events_item.vue +++ b/app/assets/javascripts/issues/show/components/incidents/timeline_events_item.vue @@ -1,12 +1,5 @@ <script> -import { - GlButton, - GlDropdown, - GlDropdownItem, - GlIcon, - GlSafeHtmlDirective, - GlSprintf, -} from '@gitlab/ui'; +import { GlDropdown, GlDropdownItem, GlIcon, GlSafeHtmlDirective, GlSprintf } from '@gitlab/ui'; import { formatDate } from '~/lib/utils/datetime_utility'; import { __ } from '~/locale'; import { getEventIcon } from './utils'; @@ -19,7 +12,6 @@ export default { timeUTC: __('%{time} UTC'), }, components: { - GlButton, GlDropdown, GlDropdownItem, GlIcon, @@ -58,43 +50,39 @@ export default { }; </script> <template> - <li - class="timeline-entry timeline-entry-vertical-line note system-note note-wrapper gl-my-2! gl-pr-0!" - > - <div class="gl-display-flex gl-align-items-center"> - <div - class="gl-display-flex gl-align-items-center gl-justify-content-center gl-bg-white gl-text-gray-200 gl-border-gray-100 gl-border-1 gl-border-solid gl-rounded-full gl-mt-n2 gl-mr-3 gl-w-8 gl-h-8 gl-p-3 gl-z-index-1" - > - <gl-icon :name="getEventIcon(action)" class="note-icon" /> + <div class="gl-display-flex gl-align-items-center"> + <div + class="gl-display-flex gl-align-items-center gl-justify-content-center gl-bg-white gl-text-gray-200 gl-border-gray-100 gl-border-1 gl-border-solid gl-rounded-full gl-mt-n2 gl-mr-3 gl-w-8 gl-h-8 gl-p-3 gl-z-index-1" + > + <gl-icon :name="getEventIcon(action)" class="note-icon" /> + </div> + <div + class="timeline-event-note gl-w-full gl-display-flex gl-flex-direction-row" + :class="{ 'gl-pb-3 gl-border-gray-50 gl-border-1 gl-border-b-solid': !isLastItem }" + data-testid="event-text-container" + > + <div> + <strong class="gl-font-lg" data-testid="event-time"> + <gl-sprintf :message="$options.i18n.timeUTC"> + <template #time>{{ time }}</template> + </gl-sprintf> + </strong> + <div v-safe-html="noteHtml"></div> </div> - <div - class="timeline-event-note gl-w-full gl-display-flex gl-flex-direction-row" - :class="{ 'gl-pb-3 gl-border-gray-50 gl-border-1 gl-border-b-solid': !isLastItem }" - data-testid="event-text-container" + <gl-dropdown + v-if="canUpdate" + right + class="event-note-actions gl-ml-auto gl-align-self-center" + icon="ellipsis_v" + text-sr-only + :text="$options.i18n.moreActions" + category="tertiary" + no-caret > - <div> - <strong class="gl-font-lg" data-testid="event-time"> - <gl-sprintf :message="$options.i18n.timeUTC"> - <template #time>{{ time }}</template> - </gl-sprintf> - </strong> - <div v-safe-html="noteHtml"></div> - </div> - <gl-dropdown - v-if="canUpdate" - right - class="event-note-actions gl-ml-auto gl-align-self-center" - icon="ellipsis_v" - text-sr-only - :text="$options.i18n.moreActions" - category="tertiary" - no-caret - > - <gl-dropdown-item @click="$emit('delete')"> - <gl-button>{{ $options.i18n.delete }}</gl-button> - </gl-dropdown-item> - </gl-dropdown> - </div> + <gl-dropdown-item @click="$emit('delete')"> + {{ $options.i18n.delete }} + </gl-dropdown-item> + </gl-dropdown> </div> - </li> + </div> </template> diff --git a/app/controllers/jwt_controller.rb b/app/controllers/jwt_controller.rb index 8eebf9fbf6b..d62c0b3cf98 100644 --- a/app/controllers/jwt_controller.rb +++ b/app/controllers/jwt_controller.rb @@ -36,6 +36,7 @@ class JwtController < ApplicationController @authentication_result = Gitlab::Auth.find_for_git_client(login, password, project: nil, ip: request.ip) if @authentication_result.failed? + log_authentication_failed(login, @authentication_result) render_unauthorized end end @@ -54,6 +55,19 @@ class JwtController < ApplicationController }, status: :unauthorized end + def log_authentication_failed(login, result) + log_info = { + message: 'JWT authentication failed', + http_user: login, + remote_ip: request.ip, + auth_service: params[:service], + 'auth_result.type': result.type, + 'auth_result.actor_type': result.actor&.class + }.merge(::Gitlab::ApplicationContext.current) + + Gitlab::AuthLogger.warn(log_info) + end + def render_unauthorized render json: { errors: [ diff --git a/app/models/concerns/integrations/slack_mattermost_notifier.rb b/app/models/concerns/integrations/slack_mattermost_notifier.rb index 142e62bb501..1ecddc015ab 100644 --- a/app/models/concerns/integrations/slack_mattermost_notifier.rb +++ b/app/models/concerns/integrations/slack_mattermost_notifier.rb @@ -21,13 +21,13 @@ module Integrations ) responses.each do |response| - unless response.success? - log_error('SlackMattermostNotifier HTTP error response', - request_host: response.request.uri.host, - response_code: response.code, - response_body: response.body - ) - end + next if response.success? + + log_error('SlackMattermostNotifier HTTP error response', + request_host: response.request.uri.host, + response_code: response.code, + response_body: response.body + ) end end diff --git a/app/models/pool_repository.rb b/app/models/pool_repository.rb index 3461104ae35..f22a63ee980 100644 --- a/app/models/pool_repository.rb +++ b/app/models/pool_repository.rb @@ -81,8 +81,8 @@ class PoolRepository < ApplicationRecord object_pool.link(repository.raw) end - def unlink_repository(repository) - repository.disconnect_alternates + def unlink_repository(repository, disconnect: true) + repository.disconnect_alternates if disconnect if member_projects.where.not(id: repository.project.id).exists? true diff --git a/app/models/preloaders/environments/deployment_preloader.rb b/app/models/preloaders/environments/deployment_preloader.rb index 251d1837f19..84aa7bc834f 100644 --- a/app/models/preloaders/environments/deployment_preloader.rb +++ b/app/models/preloaders/environments/deployment_preloader.rb @@ -41,11 +41,11 @@ module Preloaders environment.association(association_name).target = associated_deployment environment.association(association_name).loaded! - if associated_deployment - # `last?` in DeploymentEntity requires this environment to be loaded - associated_deployment.association(:environment).target = environment - associated_deployment.association(:environment).loaded! - end + next unless associated_deployment + + # `last?` in DeploymentEntity requires this environment to be loaded + associated_deployment.association(:environment).target = environment + associated_deployment.association(:environment).loaded! end end end diff --git a/app/models/project.rb b/app/models/project.rb index 0c49cc24a8d..66ed6ea6ce9 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -2693,7 +2693,12 @@ class Project < ApplicationRecord end def leave_pool_repository - pool_repository&.unlink_repository(repository) && update_column(:pool_repository_id, nil) + return if pool_repository.blank? + + # Disconnecting the repository can be expensive, so let's skip it if + # this repository is being deleted anyway. + pool_repository.unlink_repository(repository, disconnect: !pending_delete?) + update_column(:pool_repository_id, nil) end def link_pool_repository diff --git a/app/models/route.rb b/app/models/route.rb index 2f6b0a8e8f1..f2fe1664f9e 100644 --- a/app/models/route.rb +++ b/app/models/route.rb @@ -39,17 +39,17 @@ class Route < ApplicationRecord attributes[:name] = route.name.sub(name_before_last_save, name) end - if attributes.present? - old_path = route.path + next if attributes.empty? - # Callbacks must be run manually - route.update_columns(attributes.merge(updated_at: Time.current)) + old_path = route.path - # We are not calling route.delete_conflicting_redirects here, in hopes - # of avoiding deadlocks. The parent (self, in this method) already - # called it, which deletes conflicts for all descendants. - route.create_redirect(old_path) if attributes[:path] - end + # Callbacks must be run manually + route.update_columns(attributes.merge(updated_at: Time.current)) + + # We are not calling route.delete_conflicting_redirects here, in hopes + # of avoiding deadlocks. The parent (self, in this method) already + # called it, which deletes conflicts for all descendants. + route.create_redirect(old_path) if attributes[:path] end end diff --git a/app/services/authorized_project_update/find_records_due_for_refresh_service.rb b/app/services/authorized_project_update/find_records_due_for_refresh_service.rb index 3a2251f15cc..dd696da0447 100644 --- a/app/services/authorized_project_update/find_records_due_for_refresh_service.rb +++ b/app/services/authorized_project_update/find_records_due_for_refresh_service.rb @@ -28,31 +28,33 @@ module AuthorizedProjectUpdate current.except!(*projects_with_duplicates) remove |= current.each_with_object([]) do |(project_id, row), array| + next if fresh[project_id] && fresh[project_id] == row.access_level + # rows not in the new list or with a different access level should be # removed. - if !fresh[project_id] || fresh[project_id] != row.access_level - if incorrect_auth_found_callback - incorrect_auth_found_callback.call(project_id, row.access_level) - end - array << row.project_id + if incorrect_auth_found_callback + incorrect_auth_found_callback.call(project_id, row.access_level) end + + array << row.project_id end add = fresh.each_with_object([]) do |(project_id, level), array| + next if current[project_id] && current[project_id].access_level == level + # rows not in the old list or with a different access level should be # added. - if !current[project_id] || current[project_id].access_level != level - if missing_auth_found_callback - missing_auth_found_callback.call(project_id, level) - end - - array << { - user_id: user.id, - project_id: project_id, - access_level: level - } + + if missing_auth_found_callback + missing_auth_found_callback.call(project_id, level) end + + array << { + user_id: user.id, + project_id: project_id, + access_level: level + } end [remove, add] diff --git a/config/initializers/01_secret_token.rb b/config/initializers/01_secret_token.rb index c1f03dfdb07..bb13869c963 100644 --- a/config/initializers/01_secret_token.rb +++ b/config/initializers/01_secret_token.rb @@ -65,11 +65,10 @@ end def set_missing_keys(defaults) defaults.stringify_keys.each_with_object({}) do |(key, default), missing| - if Rails.application.secrets[key].blank? - warn_missing_secret(key) + next if Rails.application.secrets[key].present? - missing[key] = Rails.application.secrets[key] = default - end + warn_missing_secret(key) + missing[key] = Rails.application.secrets[key] = default end end diff --git a/config/initializers/excon.rb b/config/initializers/excon.rb new file mode 100644 index 00000000000..132cb2ff15b --- /dev/null +++ b/config/initializers/excon.rb @@ -0,0 +1,8 @@ +# frozen_string_literal: true + +require 'openssl' + +# Excon ships its own bundled certs by default. Avoid confusion +# by using the same set that GitLab uses. +Excon.defaults[:ssl_ca_file] = OpenSSL::X509::DEFAULT_CERT_FILE +Excon.defaults[:ssl_verify_peer] = true diff --git a/config/initializers/sidekiq_cluster.rb b/config/initializers/sidekiq_cluster.rb index 2f9c1de47eb..6fd598b3e25 100644 --- a/config/initializers/sidekiq_cluster.rb +++ b/config/initializers/sidekiq_cluster.rb @@ -9,22 +9,23 @@ if ENV['ENABLE_SIDEKIQ_CLUSTER'] loop do sleep(5) + next if Process.ppid == parent + # In cluster mode it's possible that the master process is SIGKILL'd. In # this case the parent PID changes and we need to terminate ourselves. - if Process.ppid != parent - Process.kill(:TERM, Process.pid) - - # Allow sidekiq to cleanly terminate and push any running jobs back - # into the queue. We use the configured timeout and add a small - # grace period - sleep(Sidekiq.options[:timeout] + 5) - - # Signaling the Sidekiq Pgroup as KILL is not forwarded to - # a possible child process. In Sidekiq Cluster, all child Sidekiq - # processes are PGROUP leaders (each process has its own pgroup). - Process.kill(:KILL, 0) - break - end + + Process.kill(:TERM, Process.pid) + + # Allow sidekiq to cleanly terminate and push any running jobs back + # into the queue. We use the configured timeout and add a small + # grace period + sleep(Sidekiq.options[:timeout] + 5) + + # Signaling the Sidekiq Pgroup as KILL is not forwarded to + # a possible child process. In Sidekiq Cluster, all child Sidekiq + # processes are PGROUP leaders (each process has its own pgroup). + Process.kill(:KILL, 0) + break end end end diff --git a/config/plugins/monaco_webpack.js b/config/plugins/monaco_webpack.js deleted file mode 100644 index 01d88ca37db..00000000000 --- a/config/plugins/monaco_webpack.js +++ /dev/null @@ -1,17 +0,0 @@ -const { languagesArr } = require('monaco-editor-webpack-plugin/out/languages'); - -// monaco-yaml library doesn't play so well with monaco-editor-webpack-plugin -// so the only way to include its workers is by patching the list of languages -// in monaco-editor-webpack-plugin and adding support for yaml workers. This is -// a known issue in the library and this workaround was suggested here: -// https://github.com/pengx17/monaco-yaml/issues/20 - -const yamlLang = languagesArr.find((t) => t.label === 'yaml'); - -yamlLang.entry = [yamlLang.entry, '../../monaco-yaml/lib/esm/monaco.contribution']; -yamlLang.worker = { - id: 'vs/language/yaml/yamlWorker', - entry: '../../monaco-yaml/lib/esm/yaml.worker.js', -}; - -module.exports = require('monaco-editor-webpack-plugin'); diff --git a/config/webpack.config.js b/config/webpack.config.js index 0d759de4900..545262bcb70 100644 --- a/config/webpack.config.js +++ b/config/webpack.config.js @@ -19,13 +19,13 @@ const webpack = require('webpack'); const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer'); const { StatsWriterPlugin } = require('webpack-stats-plugin'); const WEBPACK_VERSION = require('webpack/package.json').version; +const MonacoWebpackPlugin = require('monaco-editor-webpack-plugin'); const createIncrementalWebpackCompiler = require('./helpers/incremental_webpack_compiler'); const IS_EE = require('./helpers/is_ee_env'); const IS_JH = require('./helpers/is_jh_env'); const vendorDllHash = require('./helpers/vendor_dll_hash'); -const MonacoWebpackPlugin = require('./plugins/monaco_webpack'); const GraphqlKnownOperationsPlugin = require('./plugins/graphql_known_operations_plugin'); const ROOT_PATH = path.resolve(__dirname, '..'); @@ -78,6 +78,20 @@ const incrementalCompiler = createIncrementalWebpackCompiler( INCREMENTAL_COMPILER_TTL, ); +const defaultJsTransformationOptions = { + cacheDirectory: path.join(CACHE_PATH, 'babel-loader'), + cacheIdentifier: [ + process.env.BABEL_ENV || process.env.NODE_ENV || 'development', + webpack.version, + BABEL_VERSION, + BABEL_LOADER_VERSION, + // Ensure that changing supported browsers will refresh the cache + // in order to not pull in outdated files that import core-js + SUPPORTED_BROWSERS_HASH, + ].join('|'), + cacheCompression: false, +}; + function generateEntries() { // generate automatic entry points const autoEntries = {}; @@ -269,17 +283,23 @@ module.exports = { /node_modules|vendor[\\/]assets/.test(modulePath) && !/\.vue\.js/.test(modulePath), loader: 'babel-loader', options: { - cacheDirectory: path.join(CACHE_PATH, 'babel-loader'), - cacheIdentifier: [ - process.env.BABEL_ENV || process.env.NODE_ENV || 'development', - webpack.version, - BABEL_VERSION, - BABEL_LOADER_VERSION, - // Ensure that changing supported browsers will refresh the cache - // in order to not pull in outdated files that import core-js - SUPPORTED_BROWSERS_HASH, - ].join('|'), - cacheCompression: false, + ...defaultJsTransformationOptions, + }, + }, + { + test: /\.js$/, + include: (modulePath) => /node_modules\/(monaco-|yaml)/.test(modulePath), + exclude: (modulePath) => + /node_modules\/(monaco-yaml|monaco-editor\/esm\/vs\/editor\/contrib)/.test(modulePath), + loader: 'babel-loader', + options: { + presets: ['@babel/preset-env'], + plugins: [ + '@babel/plugin-proposal-numeric-separator', + '@babel/plugin-syntax-dynamic-import', + '@babel/plugin-proposal-optional-chaining', + ], + ...defaultJsTransformationOptions, }, }, { @@ -473,7 +493,18 @@ module.exports = { new VueLoaderPlugin(), // automatically configure monaco editor web workers - new MonacoWebpackPlugin(), + new MonacoWebpackPlugin({ + customLanguages: [ + { + label: 'yaml', + entry: 'monaco-yaml', + worker: { + id: 'monaco-yaml/yamlWorker', + entry: 'monaco-yaml/yaml.worker', + }, + }, + ], + }), new GraphqlKnownOperationsPlugin({ filename: 'graphql_known_operations.yml' }), diff --git a/doc/development/code_review.md b/doc/development/code_review.md index e9e546c6f9b..4b2bde0d494 100644 --- a/doc/development/code_review.md +++ b/doc/development/code_review.md @@ -125,40 +125,25 @@ As described in the section on the responsibility of the maintainer below, you are recommended to get your merge request approved and merged by maintainers with [domain expertise](#domain-experts). -1. If your merge request includes `~backend` changes (*1*), it must be - **approved by a [backend maintainer](https://about.gitlab.com/handbook/engineering/projects/#gitlab_maintainers_backend)**. -1. If your merge request includes database migrations or changes to expensive queries (*2*), it must be - **approved by a [database maintainer](https://about.gitlab.com/handbook/engineering/projects/#gitlab_maintainers_database)**. - Read the [database review guidelines](database_review.md) for more details. -1. If your merge request includes `~frontend` changes (*1*), it must be - **approved by a [frontend maintainer](https://about.gitlab.com/handbook/engineering/projects/#gitlab_maintainers_frontend)**. -1. If your merge request includes (`~UX`) user-facing changes (*3*), it must be - **approved by a [Product Designer](https://about.gitlab.com/handbook/engineering/projects/#gitlab_reviewers_UX)**. - See the [design and user interface guidelines](contributing/design.md) for details. -1. If your merge request includes adding a new JavaScript library (*1*)... - - If the library significantly increases the - [bundle size](https://gitlab.com/gitlab-org/frontend/playground/webpack-memory-metrics/-/blob/master/doc/report.md), it must - be **approved by a [frontend foundations member](https://about.gitlab.com/direction/ecosystem/foundations/)**. - - If the license used by the new library hasn't been approved for use in - GitLab, the license must be **approved by a [legal department member](https://about.gitlab.com/handbook/legal/)**. - More information about license compatibility can be found in our - [GitLab Licensing and Compatibility documentation](licensing.md). -1. If your merge request includes a new dependency or a file system change, it must be - **approved by a [Distribution team member](https://about.gitlab.com/company/team/)**. See how to work with the [Distribution team](https://about.gitlab.com/handbook/engineering/development/enablement/systems/distribution/#how-to-work-with-distribution) for more details. -1. If your merge request includes documentation changes, it must be **approved - by a [Technical writer](https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments)**, - based on assignments in the appropriate [DevOps stage group](https://about.gitlab.com/handbook/product/categories/#devops-stages). -1. If your merge request includes changes to development guidelines, follow the [review process](development_processes.md#development-guidelines-review) and get the approvals accordingly. -1. If your merge request includes end-to-end **and** non-end-to-end changes (*4*), it must be **approved - by a [Software Engineer in Test](https://about.gitlab.com/handbook/engineering/quality/#individual-contributors)**. -1. If your merge request only includes end-to-end changes (*4*) **or** if the MR author is a [Software Engineer in Test](https://about.gitlab.com/handbook/engineering/quality/#individual-contributors), it must be **approved by a [Quality maintainer](https://about.gitlab.com/handbook/engineering/projects/#gitlab_maintainers_qa)** -1. If your merge request includes a new or updated [application limit](https://about.gitlab.com/handbook/product/product-processes/#introducing-application-limits), it must be **approved by a [product manager](https://about.gitlab.com/company/team/)**. -1. If your merge request includes Product Intelligence (telemetry or analytics) changes, it should be reviewed and approved by a [Product Intelligence engineer](https://gitlab.com/gitlab-org/analytics-section/product-intelligence/engineers). -1. If your merge request includes an addition of, or changes to a [Feature spec](testing_guide/testing_levels.md#frontend-feature-tests), it must be **approved by a [Quality maintainer](https://about.gitlab.com/handbook/engineering/projects/#gitlab_maintainers_qa) or [Quality reviewer](https://about.gitlab.com/handbook/engineering/projects/#gitlab_reviewers_qa)**. -1. If your merge request introduces a new service to GitLab (Puma, Sidekiq, Gitaly are examples), it must be **approved by a [product manager](https://about.gitlab.com/company/team/)**. See the [process for adding a service component to GitLab](adding_service_component.md) for details. -1. If your merge request includes changes related to authentication or authorization, it must be **approved by a [Manage:Authentication and Authorization team member](https://about.gitlab.com/company/team/)**. Check the [code review section on the group page](https://about.gitlab.com/handbook/engineering/development/dev/manage/authentication-and-authorization/#additional-considerations) for more details. Patterns for files known to require review from the team are listed in the in the `Authentication and Authorization` section of the [`CODEOWNERS`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/.gitlab/CODEOWNERS) file, and the team will be listed in the approvers section of all merge requests that modify these files. - -- (*1*): Specs other than JavaScript specs are considered `~backend` code. Haml markup is considered `~frontend` code. However, Ruby code within Haml templates is considered `~backend` code. +| If your merge request includes | It must be approved by a | +| ------------------------------- | ------------------------ | +| `~backend` changes (*1*) | [Backend maintainer](https://about.gitlab.com/handbook/engineering/projects/#gitlab_maintainers_backend). | +| `~database` migrations or changes to expensive queries (*2*) | [Database maintainer](https://about.gitlab.com/handbook/engineering/projects/#gitlab_maintainers_database). Refer to the [database review guidelines](database_review.md) for more details. | +| `~frontend` changes (*1*) | [Frontend maintainer](https://about.gitlab.com/handbook/engineering/projects/#gitlab_maintainers_frontend). | +| `~UX` user-facing changes (*3*) | [Product Designer](https://about.gitlab.com/handbook/engineering/projects/#gitlab_reviewers_UX). Refer to the [design and user interface guidelines](contributing/design.md) for details. | +| Adding a new JavaScript library (*1*) | <ul><li>[Frontend foundations member](https://about.gitlab.com/direction/ecosystem/foundations/) if the library significantly increases the [bundle size](https://gitlab.com/gitlab-org/frontend/playground/webpack-memory-metrics/-/blob/master/doc/report.md)</li><li>A [legal department member](https://about.gitlab.com/handbook/legal/) if the license used by the new library hasn't been approved for use in GitLab</li></ul> More information about license compatibility can be found in our [GitLab Licensing and Compatibility documentation](licensing.md). | +| A new dependency or a file system change | [Distribution team member](https://about.gitlab.com/company/team/). See how to work with the [Distribution team](https://about.gitlab.com/handbook/engineering/development/enablement/systems/distribution/#how-to-work-with-distribution) for more details. | +| `~documentation` changes | [Technical writer](https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments) based on assignments in the appropriate [DevOps stage group](https://about.gitlab.com/handbook/product/categories/#devops-stages). | +| Changes to development guidelines | Follow the [review process](development_processes.md#development-guidelines-review) and get the approvals accordingly. | +| End-to-end **and** non-end-to-end changes (*4*) | [Software Engineer in Test](https://about.gitlab.com/handbook/engineering/quality/#individual-contributors). | +| Only End-to-end changes (*4*) **or** if the MR author is a [Software Engineer in Test](https://about.gitlab.com/handbook/engineering/quality/#individual-contributors) | [Quality maintainer](https://about.gitlab.com/handbook/engineering/projects/#gitlab_maintainers_qa). | +| A new or updated [application limit](https://about.gitlab.com/handbook/product/product-processes/#introducing-application-limits) | [Product manager](https://about.gitlab.com/company/team/). | +| Product Intelligence (telemetry or analytics) changes | [Product Intelligence engineer](https://gitlab.com/gitlab-org/analytics-section/product-intelligence/engineers). | +| An addition of, or changes to a [Feature spec](testing_guide/testing_levels.md#frontend-feature-tests) | [Quality maintainer](https://about.gitlab.com/handbook/engineering/projects/#gitlab_maintainers_qa) or [Quality reviewer](https://about.gitlab.com/handbook/engineering/projects/#gitlab_reviewers_qa). | +| A new service to GitLab (Puma, Sidekiq, Gitaly are examples) | [Product manager](https://about.gitlab.com/company/team/). See the [process for adding a service component to GitLab](adding_service_component.md) for details. | +| Changes related to authentication or authorization | [Manage:Authentication and Authorization team member](https://about.gitlab.com/company/team/). Check the [code review section on the group page](https://about.gitlab.com/handbook/engineering/development/dev/manage/authentication-and-authorization/#additional-considerations) for more details. Patterns for files known to require review from the team are listed in the in the `Authentication and Authorization` section of the [`CODEOWNERS`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/.gitlab/CODEOWNERS) file, and the team will be listed in the approvers section of all merge requests that modify these files. | + +- (*1*): Specs other than JavaScript specs are considered `~backend` code. Haml markup is considered `~frontend` code. However, Ruby code within Haml templates is considered `~backend` code. When in doubt, request both a frontend and backend review. - (*2*): We encourage you to seek guidance from a database maintainer if your merge request is potentially introducing expensive queries. It is most efficient to comment on the line of code in question with the SQL queries so they can give their advice. diff --git a/doc/development/feature_flags/controls.md b/doc/development/feature_flags/controls.md index 8a862e5f7cd..b08e759a93a 100644 --- a/doc/development/feature_flags/controls.md +++ b/doc/development/feature_flags/controls.md @@ -112,6 +112,8 @@ incidents or in-progress change issues, for example: 2021-06-29 Canary deployment failing QA tests ``` +Before enabling a feature flag, verify that you are not violating any [Production Change Lock periods](https://about.gitlab.com/handbook/engineering/infrastructure/change-management/#production-change-lock-pcl) and are in compliance with the [Feature Flags and the Change Management Process](https://about.gitlab.com/handbook/engineering/infrastructure/change-management/#feature-flags-and-the-change-management-process). + The following `/chatops` commands should be performed in the Slack `#production` channel. diff --git a/jest.config.base.js b/jest.config.base.js index d90a3b9825e..b631c2009d9 100644 --- a/jest.config.base.js +++ b/jest.config.base.js @@ -144,6 +144,8 @@ module.exports = (path, options = {}) => { 'three', 'monaco-editor', 'monaco-yaml', + 'monaco-marker-data-provider', + 'monaco-worker-manager', 'fast-mersenne-twister', 'prosemirror-markdown', 'marked', diff --git a/locale/gitlab.pot b/locale/gitlab.pot index b86d02d5627..c3457506486 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -11746,6 +11746,9 @@ msgstr "" msgid "DastConfig|Not enabled" msgstr "" +msgid "DastProfiles| Profile is currently in-use" +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 "" @@ -11926,6 +11929,9 @@ msgstr "" msgid "DastProfiles|Profile is being used by this on-demand scan" msgstr "" +msgid "DastProfiles|Profile is currently in-use" +msgstr "" + msgid "DastProfiles|Profile name" msgstr "" diff --git a/package.json b/package.json index ecd30dd2560..f0a75269ce7 100644 --- a/package.json +++ b/package.json @@ -139,9 +139,9 @@ "mermaid": "^9.1.3", "micromatch": "^4.0.5", "minimatch": "^3.0.4", - "monaco-editor": "^0.30.1", - "monaco-editor-webpack-plugin": "^6.0.0", - "monaco-yaml": "3.0.0", + "monaco-editor": "^0.33.0", + "monaco-editor-webpack-plugin": "^7.0.1", + "monaco-yaml": "4.0.0", "mousetrap": "1.6.5", "papaparse": "^5.3.1", "patch-package": "^6.4.7", diff --git a/qa/qa/resource/ci_variable.rb b/qa/qa/resource/ci_variable.rb index ef663bb613f..b632446623d 100644 --- a/qa/qa/resource/ci_variable.rb +++ b/qa/qa/resource/ci_variable.rb @@ -3,7 +3,7 @@ module QA module Resource class CiVariable < Base - attr_accessor :key, :value, :masked, :protected + attr_accessor :key, :value, :masked, :protected, :variable_type attribute :project do Project.fabricate! do |resource| @@ -15,6 +15,7 @@ module QA def initialize @masked = false @protected = false + @variable_type = 'env_var' end def fabricate! @@ -55,7 +56,8 @@ module QA key: key, value: value, masked: masked, - protected: protected + protected: protected, + variable_type: variable_type } end end diff --git a/qa/qa/resource/job.rb b/qa/qa/resource/job.rb index 96c502e567c..5b0dac9b2df 100644 --- a/qa/qa/resource/job.rb +++ b/qa/qa/resource/job.rb @@ -22,6 +22,10 @@ module QA "/projects/#{project.id}/jobs/#{id}" end + def api_trace_path + "#{api_get_path}/trace" + end + def api_post_path end @@ -30,6 +34,11 @@ module QA artifacts: artifacts } end + + # Job log + def trace + get(request_url(api_trace_path)) + end end end end diff --git a/qa/qa/specs/features/api/4_verify/file_variable_spec.rb b/qa/qa/specs/features/api/4_verify/file_variable_spec.rb new file mode 100644 index 00000000000..9722f62d5a7 --- /dev/null +++ b/qa/qa/specs/features/api/4_verify/file_variable_spec.rb @@ -0,0 +1,142 @@ +# frozen_string_literal: true + +module QA + RSpec.describe 'Verify', :runner, feature_flag: { + name: 'ci_stop_expanding_file_vars_for_runners', + scope: :project + } do + describe 'Pipeline with project file variables' do + let(:executor) { "qa-runner-#{Faker::Alphanumeric.alphanumeric(number: 8)}" } + + let(:project) do + Resource::Project.fabricate_via_api! do |project| + project.name = 'project-with-file-variables' + end + end + + let(:runner) do + Resource::Runner.fabricate! do |runner| + runner.project = project + runner.name = executor + runner.tags = [executor] + end + end + + let(:add_ci_file) do + Resource::Repository::Commit.fabricate_via_api! do |commit| + commit.project = project + commit.commit_message = 'Add .gitlab-ci.yml' + commit.add_files( + [ + { + file_path: '.gitlab-ci.yml', + content: <<~YAML + variables: + EXTRA_ARGS: "-f $TEST_FILE" + DOCKER_REMOTE_ARGS: --tlscacert="$DOCKER_CA_CERT" + EXTRACTED_CRT_FILE: ${DOCKER_CA_CERT}.crt + MY_FILE_VAR: $TEST_FILE + + test: + tags: [#{executor}] + script: + - echo "run something $EXTRA_ARGS" + - echo "docker run $DOCKER_REMOTE_ARGS" + - echo "run --output=$EXTRACTED_CRT_FILE" + - echo "Will read private key from $MY_FILE_VAR" + YAML + } + ] + ) + end + end + + let(:add_file_variables) do + { + 'TEST_FILE' => 'hello, this is test', + 'DOCKER_CA_CERT' => 'This is secret' + }.each do |file_name, content| + add_file_variable_to_project(file_name, content) + end + end + + after do + runner.remove_via_api! + end + + shared_examples 'variables are read correctly' do + it 'shows in job log accordingly' do + job = Resource::Job.fabricate_via_api! do |job| + job.project = project + job.id = project.job_by_name('test')[:id] + end + + aggregate_failures do + trace = job.trace + expect(trace).to have_content('run something -f hello, this is test') + expect(trace).to have_content('docker run --tlscacert="This is secret"') + expect(trace).to have_content('run --output=This is secret.crt') + expect(trace).to have_content('Will read private key from hello, this is test') + end + end + end + + # FF does not change current behavior + # https://gitlab.com/gitlab-org/gitlab/-/merge_requests/94198#note_1057609893 + # + # TODO: Remove when FF is removed + # TODO: Archive testcase issue when FF is removed + # Rollout issue: https://gitlab.com/gitlab-org/gitlab/-/issues/369907 + context 'when FF is on', testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/370787' do + before do + Runtime::Feature.enable(:ci_stop_expanding_file_vars_for_runners, project: project) + + runner + add_file_variables + add_ci_file + trigger_pipeline + wait_for_pipeline + end + + it_behaves_like 'variables are read correctly' + end + + # TODO: Refactor when FF is removed + # TODO: Update testcase issue title and description to not refer to FF status + context 'when FF is off', testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/370791' do + before do + runner + add_file_variables + add_ci_file + trigger_pipeline + wait_for_pipeline + end + + it_behaves_like 'variables are read correctly' + end + + private + + def add_file_variable_to_project(key, value) + Resource::CiVariable.fabricate_via_api! do |ci_variable| + ci_variable.project = project + ci_variable.key = key + ci_variable.value = value + ci_variable.variable_type = 'file' + end + end + + def trigger_pipeline + Resource::Pipeline.fabricate_via_api! do |pipeline| + pipeline.project = project + end + end + + def wait_for_pipeline + Support::Waiter.wait_until do + project.pipelines.present? && project.pipelines.first[:status] == 'success' + end + end + end + end +end diff --git a/spec/frontend/__mocks__/monaco-editor/index.js b/spec/frontend/__mocks__/monaco-editor/index.js index 384f9993150..d09672a4ecf 100644 --- a/spec/frontend/__mocks__/monaco-editor/index.js +++ b/spec/frontend/__mocks__/monaco-editor/index.js @@ -8,10 +8,8 @@ import 'monaco-editor/esm/vs/language/css/monaco.contribution'; import 'monaco-editor/esm/vs/language/json/monaco.contribution'; import 'monaco-editor/esm/vs/language/html/monaco.contribution'; import 'monaco-editor/esm/vs/basic-languages/monaco.contribution'; -import 'monaco-yaml/lib/esm/monaco.contribution'; // This language starts trying to spin up web workers which obviously breaks in Jest environment jest.mock('monaco-editor/esm/vs/language/typescript/tsMode'); -jest.mock('monaco-yaml/lib/esm/yamlMode'); export * from 'monaco-editor/esm/vs/editor/editor.api'; diff --git a/spec/frontend/__mocks__/monaco-yaml/index.js b/spec/frontend/__mocks__/monaco-yaml/index.js new file mode 100644 index 00000000000..36681854d0b --- /dev/null +++ b/spec/frontend/__mocks__/monaco-yaml/index.js @@ -0,0 +1,4 @@ +const setDiagnosticsOptions = jest.fn(); +const yamlDefaults = {}; + +export { setDiagnosticsOptions, yamlDefaults }; diff --git a/spec/frontend/editor/source_editor_ci_schema_ext_spec.js b/spec/frontend/editor/source_editor_ci_schema_ext_spec.js index 9a14e1a55eb..21f8979f1a9 100644 --- a/spec/frontend/editor/source_editor_ci_schema_ext_spec.js +++ b/spec/frontend/editor/source_editor_ci_schema_ext_spec.js @@ -1,4 +1,4 @@ -import { languages } from 'monaco-editor'; +import { setDiagnosticsOptions } from 'monaco-yaml'; import { setHTMLFixture, resetHTMLFixture } from 'helpers/fixtures'; import { TEST_HOST } from 'helpers/test_constants'; import { CiSchemaExtension } from '~/editor/extensions/source_editor_ci_schema_ext'; @@ -52,16 +52,12 @@ describe('~/editor/editor_ci_config_ext', () => { }); describe('registerCiSchema', () => { - beforeEach(() => { - jest.spyOn(languages.yaml.yamlDefaults, 'setDiagnosticsOptions'); - }); - describe('register validations options with monaco for yaml language', () => { const mockProjectNamespace = 'namespace1'; const mockProjectPath = 'project1'; const getConfiguredYmlSchema = () => { - return languages.yaml.yamlDefaults.setDiagnosticsOptions.mock.calls[0][0].schemas[0]; + return setDiagnosticsOptions.mock.calls[0][0].schemas[0]; }; it('with expected basic validation configuration', () => { @@ -77,8 +73,8 @@ describe('~/editor/editor_ci_config_ext', () => { completion: true, }; - expect(languages.yaml.yamlDefaults.setDiagnosticsOptions).toHaveBeenCalledTimes(1); - expect(languages.yaml.yamlDefaults.setDiagnosticsOptions).toHaveBeenCalledWith( + expect(setDiagnosticsOptions).toHaveBeenCalledTimes(1); + expect(setDiagnosticsOptions).toHaveBeenCalledWith( expect.objectContaining(expectedOptions), ); }); diff --git a/spec/frontend/editor/source_editor_markdown_livepreview_ext_spec.js b/spec/frontend/editor/source_editor_markdown_livepreview_ext_spec.js index fe20c23e4d7..40a924b4f12 100644 --- a/spec/frontend/editor/source_editor_markdown_livepreview_ext_spec.js +++ b/spec/frontend/editor/source_editor_markdown_livepreview_ext_spec.js @@ -76,9 +76,9 @@ describe('Markdown Live Preview Extension for Source Editor', () => { actions: expect.any(Object), shown: false, modelChangeListener: undefined, - layoutChangeListener: { + layoutChangeListener: expect.objectContaining({ dispose: expect.anything(), - }, + }), path: previewMarkdownPath, actionShowPreviewCondition: expect.any(Object), }); diff --git a/spec/frontend/ide/lib/editor_spec.js b/spec/frontend/ide/lib/editor_spec.js index c21a7edb2da..cb2437b9db1 100644 --- a/spec/frontend/ide/lib/editor_spec.js +++ b/spec/frontend/ide/lib/editor_spec.js @@ -16,14 +16,6 @@ describe('Multi-file editor library', () => { let holder; let store; - const setNodeOffsetWidth = (val) => { - Object.defineProperty(instance.instance.getDomNode(), 'offsetWidth', { - get() { - return val; - }, - }); - }; - beforeEach(() => { store = createStore(); el = document.createElement('div'); @@ -272,6 +264,13 @@ describe('Multi-file editor library', () => { }); describe('updateDiffView', () => { + const setDiffNodeOffsetWidth = (val) => { + Object.defineProperty(instance.instance.getContainerDomNode(), 'offsetWidth', { + get() { + return val; + }, + }); + }; describe('edit mode', () => { it('does not update options', () => { instance.createInstance(holder); @@ -292,7 +291,7 @@ describe('Multi-file editor library', () => { }); it('sets renderSideBySide to false if el is less than 700 pixels', () => { - setNodeOffsetWidth(600); + setDiffNodeOffsetWidth(600); expect(instance.instance.updateOptions).not.toHaveBeenCalledWith({ renderSideBySide: false, @@ -300,7 +299,7 @@ describe('Multi-file editor library', () => { }); it('sets renderSideBySide to false if el is more than 700 pixels', () => { - setNodeOffsetWidth(800); + setDiffNodeOffsetWidth(800); expect(instance.instance.updateOptions).not.toHaveBeenCalledWith({ renderSideBySide: true, diff --git a/spec/frontend/ide/utils_spec.js b/spec/frontend/ide/utils_spec.js index fd9d481251d..4efc0ac6028 100644 --- a/spec/frontend/ide/utils_spec.js +++ b/spec/frontend/ide/utils_spec.js @@ -1,4 +1,5 @@ import { languages } from 'monaco-editor'; +import { setDiagnosticsOptions as yamlDiagnosticsOptions } from 'monaco-yaml'; import { isTextFile, registerLanguages, @@ -203,7 +204,6 @@ describe('WebIDE utils', () => { }; jest.spyOn(languages.json.jsonDefaults, 'setDiagnosticsOptions'); - jest.spyOn(languages.yaml.yamlDefaults, 'setDiagnosticsOptions'); }); it('registers the given schemas with monaco for both json and yaml languages', () => { @@ -212,7 +212,7 @@ describe('WebIDE utils', () => { expect(languages.json.jsonDefaults.setDiagnosticsOptions).toHaveBeenCalledWith( expect.objectContaining({ schemas: [schema] }), ); - expect(languages.yaml.yamlDefaults.setDiagnosticsOptions).toHaveBeenCalledWith( + expect(yamlDiagnosticsOptions).toHaveBeenCalledWith( expect.objectContaining({ schemas: [schema] }), ); }); diff --git a/spec/frontend/issues/show/components/incidents/mock_data.js b/spec/frontend/issues/show/components/incidents/mock_data.js index 75c0a7350ae..fd26fd86ced 100644 --- a/spec/frontend/issues/show/components/incidents/mock_data.js +++ b/spec/frontend/issues/show/components/incidents/mock_data.js @@ -125,3 +125,8 @@ export const mockGetTimelineData = { }, }, }; + +export const fakeDate = '2020-07-08T00:00:00.000Z'; + +const { id, note, occurredAt } = mockEvents[0]; +export const fakeEventData = { id, note, occurredAt }; diff --git a/spec/frontend/issues/show/components/incidents/timeline_events_list_spec.js b/spec/frontend/issues/show/components/incidents/timeline_events_list_spec.js index 4d2d53c990e..fe182016924 100644 --- a/spec/frontend/issues/show/components/incidents/timeline_events_list_spec.js +++ b/spec/frontend/issues/show/components/incidents/timeline_events_list_spec.js @@ -3,16 +3,21 @@ import VueApollo from 'vue-apollo'; import Vue from 'vue'; import { confirmAction } from '~/lib/utils/confirm_via_gl_modal/confirm_via_gl_modal'; import IncidentTimelineEventList from '~/issues/show/components/incidents/timeline_events_list.vue'; -import IncidentTimelineEventListItem from '~/issues/show/components/incidents/timeline_events_item.vue'; -import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; +import IncidentTimelineEventItem from '~/issues/show/components/incidents/timeline_events_item.vue'; +import { mountExtended } from 'helpers/vue_test_utils_helper'; import waitForPromises from 'helpers/wait_for_promises'; import deleteTimelineEventMutation from '~/issues/show/components/incidents/graphql/queries/delete_timeline_event.mutation.graphql'; +import getTimelineEvents from '~/issues/show/components/incidents/graphql/queries/get_timeline_events.query.graphql'; import createMockApollo from 'helpers/mock_apollo_helper'; +import { useFakeDate } from 'helpers/fake_date'; import { createAlert } from '~/flash'; import { mockEvents, timelineEventsDeleteEventResponse, timelineEventsDeleteEventError, + fakeDate, + fakeEventData, + timelineEventsQueryListResponse, } from './mock_data'; Vue.use(VueApollo); @@ -20,58 +25,61 @@ Vue.use(VueApollo); jest.mock('~/flash'); jest.mock('~/lib/utils/confirm_via_gl_modal/confirm_via_gl_modal'); -const deleteEventResponse = jest.fn(); - -function createMockApolloProvider() { - deleteEventResponse.mockResolvedValue(timelineEventsDeleteEventResponse); - const requestHandlers = [[deleteTimelineEventMutation, deleteEventResponse]]; - return createMockApollo(requestHandlers); -} - const mockConfirmAction = ({ confirmed }) => { confirmAction.mockResolvedValueOnce(confirmed); }; describe('IncidentTimelineEventList', () => { + useFakeDate(fakeDate); let wrapper; + const responseSpy = jest.fn().mockResolvedValue(timelineEventsDeleteEventResponse); + + const requestHandlers = [[deleteTimelineEventMutation, responseSpy]]; + const apolloProvider = createMockApollo(requestHandlers); + + apolloProvider.clients.defaultClient.cache.writeQuery({ + query: getTimelineEvents, + data: timelineEventsQueryListResponse.data, + variables: { + fullPath: 'group/project', + incidentId: 'gid://gitlab/Issue/1', + }, + }); - const mountComponent = (mockApollo) => { - const apollo = mockApollo ? { apolloProvider: mockApollo } : {}; - - wrapper = shallowMountExtended(IncidentTimelineEventList, { + const mountComponent = () => { + wrapper = mountExtended(IncidentTimelineEventList, { + propsData: { + timelineEvents: mockEvents, + }, provide: { fullPath: 'group/project', issuableId: '1', + canUpdate: true, }, - propsData: { - timelineEvents: mockEvents, - }, - ...apollo, + apolloProvider, }); }; const findTimelineEventGroups = () => wrapper.findAllByTestId('timeline-group'); - const findItems = (base = wrapper) => base.findAll(IncidentTimelineEventListItem); + const findItems = (base = wrapper) => base.findAll(IncidentTimelineEventItem); const findFirstTimelineEventGroup = () => findTimelineEventGroups().at(0); const findSecondTimelineEventGroup = () => findTimelineEventGroups().at(1); const findDates = () => wrapper.findAllByTestId('event-date'); const clickFirstDeleteButton = async () => { - findItems() - .at(0) - .vm.$emit('delete', { ...mockEvents[0] }); + findItems().at(0).vm.$emit('delete', { fakeEventData }); await waitForPromises(); }; + beforeEach(() => { + mountComponent(); + }); + afterEach(() => { - confirmAction.mockReset(); - deleteEventResponse.mockReset(); wrapper.destroy(); }); describe('template', () => { it('groups items correctly', () => { - mountComponent(); - expect(findTimelineEventGroups()).toHaveLength(2); expect(findItems(findFirstTimelineEventGroup())).toHaveLength(1); @@ -79,24 +87,18 @@ describe('IncidentTimelineEventList', () => { }); it('sets the isLastItem prop correctly', () => { - mountComponent(); - expect(findItems().at(0).props('isLastItem')).toBe(false); expect(findItems().at(1).props('isLastItem')).toBe(false); expect(findItems().at(2).props('isLastItem')).toBe(true); }); it('sets the event props correctly', () => { - mountComponent(); - expect(findItems().at(1).props('occurredAt')).toBe(mockEvents[1].occurredAt); expect(findItems().at(1).props('action')).toBe(mockEvents[1].action); expect(findItems().at(1).props('noteHtml')).toBe(mockEvents[1].noteHtml); }); it('formats dates correctly', () => { - mountComponent(); - expect(findDates().at(0).text()).toBe('2022-03-22'); expect(findDates().at(1).text()).toBe('2022-03-23'); }); @@ -110,8 +112,6 @@ describe('IncidentTimelineEventList', () => { describe(timezone, () => { beforeEach(() => { timezoneMock.register(timezone); - - mountComponent(); }); afterEach(() => { @@ -131,12 +131,9 @@ describe('IncidentTimelineEventList', () => { it('should delete when button is clicked', async () => { const expectedVars = { input: { id: mockEvents[0].id } }; - - mountComponent(createMockApolloProvider()); - await clickFirstDeleteButton(); - expect(deleteEventResponse).toHaveBeenCalledWith(expectedVars); + expect(responseSpy).toHaveBeenCalledWith(expectedVars); }); it('should show an error when delete returns an error', async () => { @@ -144,8 +141,7 @@ describe('IncidentTimelineEventList', () => { message: 'Error deleting incident timeline event: Item does not exist', }; - mountComponent(createMockApolloProvider()); - deleteEventResponse.mockResolvedValue(timelineEventsDeleteEventError); + responseSpy.mockResolvedValue(timelineEventsDeleteEventError); await clickFirstDeleteButton(); @@ -158,8 +154,7 @@ describe('IncidentTimelineEventList', () => { error: new Error(), message: 'Something went wrong while deleting the incident timeline event.', }; - mountComponent(createMockApolloProvider()); - deleteEventResponse.mockRejectedValueOnce(); + responseSpy.mockRejectedValueOnce(); await clickFirstDeleteButton(); diff --git a/spec/models/pool_repository_spec.rb b/spec/models/pool_repository_spec.rb index 447b7b2e0a2..bf88e941540 100644 --- a/spec/models/pool_repository_spec.rb +++ b/spec/models/pool_repository_spec.rb @@ -43,6 +43,15 @@ RSpec.describe PoolRepository do end end + context 'when skipping disconnect' do + it 'does not change the alternates file' do + before = File.read(alternates_file) + pool.unlink_repository(pool.source_project.repository, disconnect: false) + + expect(File.read(alternates_file)).to eq(before) + end + end + context 'when the second member leaves' do it 'does not schedule pool removal' do other_project = create(:project, :repository, pool_repository: pool) diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index 98b202299a8..49d6ff97894 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -6624,11 +6624,27 @@ RSpec.describe Project, factory_default: :keep do let(:pool) { create(:pool_repository) } let(:project) { create(:project, :repository, pool_repository: pool) } - it 'removes the membership' do - project.leave_pool_repository + subject { project.leave_pool_repository } + + it 'removes the membership and disconnects alternates' do + expect(pool).to receive(:unlink_repository).with(project.repository, disconnect: true).and_call_original + + subject expect(pool.member_projects.reload).not_to include(project) end + + context 'when the project is pending delete' do + it 'removes the membership and does not disconnect alternates' do + project.pending_delete = true + + expect(pool).to receive(:unlink_repository).with(project.repository, disconnect: false).and_call_original + + subject + + expect(pool.member_projects.reload).not_to include(project) + end + end end describe '#check_personal_projects_limit' do diff --git a/spec/requests/jwt_controller_spec.rb b/spec/requests/jwt_controller_spec.rb index db3be617a53..7427ca11431 100644 --- a/spec/requests/jwt_controller_spec.rb +++ b/spec/requests/jwt_controller_spec.rb @@ -27,6 +27,10 @@ RSpec.describe JwtController do let(:headers) { { authorization: credentials('personal_access_token', pat.token) } } it 'fails authentication' do + expect(::Gitlab::AuthLogger).to receive(:warn).with( + hash_including(message: 'JWT authentication failed', + http_user: 'personal_access_token')).and_call_original + get '/jwt/auth', params: parameters, headers: headers expect(response).to have_gitlab_http_status(:unauthorized) diff --git a/yarn.lock b/yarn.lock index c143f007aec..e80e0d0709e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2027,10 +2027,10 @@ jest-matcher-utils "^27.0.0" pretty-format "^27.0.0" -"@types/json-schema@^7.0.3", "@types/json-schema@^7.0.5", "@types/json-schema@^7.0.8", "@types/json-schema@^7.0.9": - version "7.0.9" - resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.9.tgz#97edc9037ea0c38585320b28964dde3b39e4660d" - integrity sha512-qcUXuemtEu+E5wZSJHNxUXeCZhAfXKQ41D+duX+VYPde7xyEVZci+/oXKJL13tnRs9lR2pr4fod59GT6/X1/yQ== +"@types/json-schema@^7.0.0", "@types/json-schema@^7.0.3", "@types/json-schema@^7.0.5", "@types/json-schema@^7.0.8", "@types/json-schema@^7.0.9": + version "7.0.11" + resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.11.tgz#d421b6c527a3037f7c84433fd2c4229e016863d3" + integrity sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ== "@types/json5@^0.0.29": version "0.0.29" @@ -7463,7 +7463,7 @@ js-cookie@^3.0.0: resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== -js-yaml@^3.13.1, js-yaml@^3.14.1: +js-yaml@^3.13.1: version "3.14.1" resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.14.1.tgz#dae812fdb3825fa306609a8717383c50c36a0537" integrity sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g== @@ -7558,7 +7558,7 @@ json5@^2.1.2, json5@^2.2.1: resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.1.tgz#655d50ed1e6f95ad1a3caababd2b0efda10b395c" integrity sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA== -jsonc-parser@~3.0.0: +jsonc-parser@^3.0.0, jsonc-parser@~3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/jsonc-parser/-/jsonc-parser-3.0.0.tgz#abdd785701c7e7eaca8a9ec8cf070ca51a745a22" integrity sha512-fQzRfAbIBnR0IQvftw9FJveWiHp72Fg20giDrHz6TdfB12UH/uue0D3hm57UB5KgAVuniLMCaS8P1IMj9NR7cA== @@ -7710,10 +7710,10 @@ loader-utils@^1.0.0, loader-utils@^1.0.2, loader-utils@^1.1.0, loader-utils@^1.2 emojis-list "^3.0.0" json5 "^1.0.1" -loader-utils@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-2.0.0.tgz#e4cace5b816d425a166b5f097e10cd12b36064b0" - integrity sha512-rP4F0h2RaWSvPEkD7BLDFQnvSf+nK+wr3ESUjNTyAGobqrijmW92zc+SO6d4p4B1wh7+B/Jg1mkQe5NYUEHtHQ== +loader-utils@^2.0.0, loader-utils@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-2.0.2.tgz#d6e3b4fb81870721ae4e0868ab11dd638368c129" + integrity sha512-TM57VeHptv569d/GKh6TAYdzKblwDNiumOdkFnejjD0XwTH87K90w3O7AiJRqdQoXygvi1VQTJTLGhJl7WqA7A== dependencies: big.js "^5.2.2" emojis-list "^3.0.0" @@ -8823,26 +8823,43 @@ moment-mini@^2.24.0: resolved "https://registry.yarnpkg.com/moment-mini/-/moment-mini-2.24.0.tgz#fa68d98f7fe93ae65bf1262f6abb5fb6983d8d18" integrity sha512-9ARkWHBs+6YJIvrIp0Ik5tyTTtP9PoV0Ssu2Ocq5y9v8+NOOpWiRshAp8c4rZVWTOe+157on/5G+zj5pwIQFEQ== -monaco-editor-webpack-plugin@^6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/monaco-editor-webpack-plugin/-/monaco-editor-webpack-plugin-6.0.0.tgz#628956ce1851afa2a5f6c88d0ecbb24e9a444898" - integrity sha512-vC886Mzpd2AkSM35XLkfQMjH+Ohz6RISVwhAejDUzZDheJAiz6G34lky1vyO8fZ702v7IrcKmsGwL1rRFnwvUA== +monaco-editor-webpack-plugin@^7.0.1: + version "7.0.1" + resolved "https://registry.yarnpkg.com/monaco-editor-webpack-plugin/-/monaco-editor-webpack-plugin-7.0.1.tgz#ba19c60aba990184e36ad8722b1ed6a564527c7c" + integrity sha512-M8qIqizltrPlIbrb73cZdTWfU9sIsUVFvAZkL3KGjAHmVWEJ0hZKa/uad14JuOckc0GwnCaoGHvMoYtJjVyCzw== dependencies: - loader-utils "^2.0.0" + loader-utils "^2.0.2" -monaco-editor@^0.30.1: - version "0.30.1" - resolved "https://registry.yarnpkg.com/monaco-editor/-/monaco-editor-0.30.1.tgz#47f8d18a0aa2264fc5654581741ab8d7bec01689" - integrity sha512-B/y4+b2O5G2gjuxIFtCE2EkM17R2NM7/3F8x0qcPsqy4V83bitJTIO4TIeZpYlzu/xy6INiY/+84BEm6+7Cmzg== +monaco-editor@^0.33.0: + version "0.33.0" + resolved "https://registry.yarnpkg.com/monaco-editor/-/monaco-editor-0.33.0.tgz#842e244f3750a2482f8a29c676b5684e75ff34af" + integrity sha512-VcRWPSLIUEgQJQIE0pVT8FcGBIgFoxz7jtqctE+IiCxWugD0DwgyQBcZBhdSrdMC84eumoqMZsGl2GTreOzwqw== -monaco-yaml@3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/monaco-yaml/-/monaco-yaml-3.0.0.tgz#b3d59c3485bd5a161438072a8e5a8aaf74041dfd" - integrity sha512-WkgdfjCj0L2VPPwPoiwc4l8+B78FyVpSsMoufEWqe3ukm8+WygKUtmtCFOFnehmMih6tSqhy4DUtoAhfnicyZA== - dependencies: - "@types/json-schema" "^7.0.8" - js-yaml "^3.14.1" - yaml-ast-parser-custom-tags "^0.0.43" +monaco-marker-data-provider@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/monaco-marker-data-provider/-/monaco-marker-data-provider-1.1.0.tgz#370f41de6b4db0bdbc41403766a091c04eaefba0" + integrity sha512-g5YH4FLItl3usf0Qxr1Td/vCd4XZmPOFh+dFpo8d+Q5mp8nJZTPFMVA7kwz0vWAy0zBd7bzQYUmfdvHfR1ETBw== + +monaco-worker-manager@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/monaco-worker-manager/-/monaco-worker-manager-2.0.1.tgz#f67c54dfca34ed4b225d5de84e77b24b4e36de8a" + integrity sha512-kdPL0yvg5qjhKPNVjJoym331PY/5JC11aPJXtCZNwWRvBr6jhkIamvYAyiY5P1AWFmNOy0aRDRoMdZfa71h8kg== + +monaco-yaml@4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/monaco-yaml/-/monaco-yaml-4.0.0.tgz#3bf8624f984665aba7e5f10c4987cecf2890e89b" + integrity sha512-7/CbvZIHE1iOQF6ny+uPclcsrBnkK54lcZugIB9SzqADljhb3TliJyYs7vouaGNDF73RWvrtZrMsWCC5N/GW+g== + dependencies: + "@types/json-schema" "^7.0.0" + jsonc-parser "^3.0.0" + monaco-marker-data-provider "^1.0.0" + monaco-worker-manager "^2.0.0" + path-browserify "^1.0.0" + prettier "^2.0.0" + vscode-languageserver-textdocument "^1.0.0" + vscode-languageserver-types "^3.0.0" + vscode-uri "^3.0.0" + yaml "^2.0.0" mousetrap@1.6.5: version "1.6.5" @@ -9427,6 +9444,11 @@ path-browserify@0.0.1: resolved "https://registry.yarnpkg.com/path-browserify/-/path-browserify-0.0.1.tgz#e6c4ddd7ed3aa27c68a20cc4e50e1a4ee83bbc4a" integrity sha512-BapA40NHICOS+USX9SN4tyhq+A2RrN/Ws5F0Z5aMHDp98Fl86lX8Oti8B7uN93L4Ifv4fHOEA+pQw87gmMO/lQ== +path-browserify@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/path-browserify/-/path-browserify-1.0.1.tgz#d98454a9c3753d5790860f16f68867b9e46be1fd" + integrity sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g== + path-exists@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-3.0.0.tgz#ce0ebeaa5f78cb18925ea7d810d7b59b010fd515" @@ -9660,7 +9682,7 @@ pretender@^3.4.3: fake-xml-http-request "^2.1.1" route-recognizer "^0.3.3" -prettier@2.2.1: +prettier@2.2.1, prettier@^2.0.0: version "2.2.1" resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.2.1.tgz#795a1a78dd52f073da0cd42b21f9c91381923ff5" integrity sha512-PqyhM2yCjg/oKkFPtTGUojv7gnZAoG80ttl45O6x2Ug/rMJw4wcc9k6aaf2hibP7BGVCCM33gZoGjyvt9mm16Q== @@ -11924,6 +11946,21 @@ vm-browserify@^1.0.1: resolved "https://registry.yarnpkg.com/vm-browserify/-/vm-browserify-1.1.0.tgz#bd76d6a23323e2ca8ffa12028dc04559c75f9019" integrity sha512-iq+S7vZJE60yejDYM0ek6zg308+UZsdtPExWP9VZoCFCz1zkJoXFnAX7aZfd/ZwrkidzdUZL0C/ryW+JwAiIGw== +vscode-languageserver-textdocument@^1.0.0: + version "1.0.5" + resolved "https://registry.yarnpkg.com/vscode-languageserver-textdocument/-/vscode-languageserver-textdocument-1.0.5.tgz#838769940ece626176ec5d5a2aa2d0aa69f5095c" + integrity sha512-1ah7zyQjKBudnMiHbZmxz5bYNM9KKZYz+5VQLj+yr8l+9w3g+WAhCkUkWbhMEdC5u0ub4Ndiye/fDyS8ghIKQg== + +vscode-languageserver-types@^3.0.0: + version "3.17.2" + resolved "https://registry.yarnpkg.com/vscode-languageserver-types/-/vscode-languageserver-types-3.17.2.tgz#b2c2e7de405ad3d73a883e91989b850170ffc4f2" + integrity sha512-zHhCWatviizPIq9B7Vh9uvrH6x3sK8itC84HkamnBWoDFJtzBf7SWlpLCZUit72b3os45h6RWQNC9xHRDF8dRA== + +vscode-uri@^3.0.0: + version "3.0.3" + resolved "https://registry.yarnpkg.com/vscode-uri/-/vscode-uri-3.0.3.tgz#a95c1ce2e6f41b7549f86279d19f47951e4f4d84" + integrity sha512-EcswR2S8bpR7fD0YPeS7r2xXExrScVMxg4MedACaWHEtx9ftCF/qHG1xGkolzTPcEmjTavCQgbVzHUIdTMzFGA== + vue-apollo@^3.0.7: version "3.0.7" resolved "https://registry.yarnpkg.com/vue-apollo/-/vue-apollo-3.0.7.tgz#97a031d45641faa4888a6d5a7f71c40834359704" @@ -12445,20 +12482,15 @@ yallist@^4.0.0: resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72" integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A== -yaml-ast-parser-custom-tags@^0.0.43: - version "0.0.43" - resolved "https://registry.yarnpkg.com/yaml-ast-parser-custom-tags/-/yaml-ast-parser-custom-tags-0.0.43.tgz#46968145ce4e24cb03c3312057f0f141b93a7d02" - integrity sha512-R5063FF/JSAN6qXCmylwjt9PcDH6M0ExEme/nJBzLspc6FJDmHHIqM7xh2WfEmsTJqClF79A9VkXjkAqmZw9SQ== - yaml@^1.10.0: version "1.10.2" resolved "https://registry.yarnpkg.com/yaml/-/yaml-1.10.2.tgz#2301c5ffbf12b467de8da2333a459e29e7920e4b" integrity sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg== -yaml@^2.0.0-10: - version "2.0.0-10" - resolved "https://registry.yarnpkg.com/yaml/-/yaml-2.0.0-10.tgz#d5b59e2d14b8683313a534f2bbc648e211a2753e" - integrity sha512-FHV8s5ODFFQXX/enJEU2EkanNl1UDBUz8oa4k5Qo/sR+Iq7VmhCDkRMb0/mjJCNeAWQ31W8WV6PYStDE4d9EIw== +yaml@^2.0.0, yaml@^2.0.0-10: + version "2.1.1" + resolved "https://registry.yarnpkg.com/yaml/-/yaml-2.1.1.tgz#1e06fb4ca46e60d9da07e4f786ea370ed3c3cfec" + integrity sha512-o96x3OPo8GjWeSLF+wOAbrPfhFOGY0W00GNaxCDv+9hkcDJEnev1yh8S7pgHF0ik6zc8sQLuL8hjHjJULZp8bw== yargs-parser@^20.2.2, yargs-parser@^20.2.3: version "20.2.9" |