diff --git a/.gitlab/ci/rails.gitlab-ci.yml b/.gitlab/ci/rails.gitlab-ci.yml index 9aa5c740e2e..f73e0c1d503 100644 --- a/.gitlab/ci/rails.gitlab-ci.yml +++ b/.gitlab/ci/rails.gitlab-ci.yml @@ -355,7 +355,7 @@ rspec foss-impact: - run_timed_command "scripts/gitaly-test-spawn" - source scripts/rspec_helpers.sh - tooling/bin/find_foss_tests tmp/matching_foss_tests.txt - - 'if [[ -n "$(cat tmp/matching_foss_tests.txt)" ]]; then rspec_simple_job "--tag ~quarantine --tag ~geo --tag ~level:migration $(cat tmp/matching_foss_tests.txt)"; fi' + - rspec_matched_tests tmp/matching_foss_tests.txt "--tag ~quarantine --tag ~geo --tag ~level:migration" artifacts: expire_in: 7d paths: diff --git a/.gitlab/ci/rules.gitlab-ci.yml b/.gitlab/ci/rules.gitlab-ci.yml index c41adb18819..fbbb0391ec5 100644 --- a/.gitlab/ci/rules.gitlab-ci.yml +++ b/.gitlab/ci/rules.gitlab-ci.yml @@ -422,6 +422,8 @@ rules: - <<: *if-not-ee when: never + - <<: *if-merge-request-title-as-if-foss + when: never - <<: *if-security-merge-request changes: *code-backstage-patterns - <<: *if-dot-com-gitlab-org-merge-request diff --git a/app/assets/javascripts/avatar_picker.js b/app/assets/javascripts/avatar_picker.js deleted file mode 100644 index d38e0b4abaa..00000000000 --- a/app/assets/javascripts/avatar_picker.js +++ /dev/null @@ -1,16 +0,0 @@ -import $ from 'jquery'; - -export default function initAvatarPicker() { - $('.js-choose-avatar-button').on('click', function onClickAvatar() { - const form = $(this).closest('form'); - return form.find('.js-avatar-input').click(); - }); - - $('.js-avatar-input').on('change', function onChangeAvatarInput() { - const form = $(this).closest('form'); - const filename = $(this) - .val() - .replace(/^.*[\\\/]/, ''); // eslint-disable-line no-useless-escape - return form.find('.js-avatar-filename').text(filename); - }); -} diff --git a/app/assets/javascripts/boards/models/issue.js b/app/assets/javascripts/boards/models/issue.js index dea86ebc414..98eac35b2ed 100644 --- a/app/assets/javascripts/boards/models/issue.js +++ b/app/assets/javascripts/boards/models/issue.js @@ -66,9 +66,7 @@ class ListIssue { } removeMilestone(removeMilestone) { - if (IS_EE && removeMilestone && removeMilestone.id === this.milestone.id) { - this.milestone = {}; - } + boardsStore.removeIssueMilestone(this, removeMilestone); } getLists() { diff --git a/app/assets/javascripts/boards/stores/boards_store.js b/app/assets/javascripts/boards/stores/boards_store.js index e39469b7f49..a930f39189e 100644 --- a/app/assets/javascripts/boards/stores/boards_store.js +++ b/app/assets/javascripts/boards/stores/boards_store.js @@ -817,6 +817,12 @@ const boardsStore = { issue.isFetching[key] = value; }, + removeIssueMilestone(issue, removeMilestone) { + if (IS_EE && removeMilestone && removeMilestone.id === issue.milestone.id) { + issue.milestone = {}; + } + }, + refreshIssueData(issue, obj) { issue.id = obj.id; issue.iid = obj.iid; diff --git a/app/assets/javascripts/file_pickers.js b/app/assets/javascripts/file_pickers.js new file mode 100644 index 00000000000..956a4954afb --- /dev/null +++ b/app/assets/javascripts/file_pickers.js @@ -0,0 +1,21 @@ +export default function initFilePickers() { + const filePickers = document.querySelectorAll('.js-filepicker'); + + filePickers.forEach(filePicker => { + const button = filePicker.querySelector('.js-filepicker-button'); + + button.addEventListener('click', () => { + const form = button.closest('form'); + form.querySelector('.js-filepicker-input').click(); + }); + + const input = filePicker.querySelector('.js-filepicker-input'); + + input.addEventListener('change', () => { + const form = input.closest('form'); + const filename = input.value.replace(/^.*[\\\/]/, ''); // eslint-disable-line no-useless-escape + + form.querySelector('.js-filepicker-filename').textContent = filename; + }); + }); +} diff --git a/app/assets/javascripts/group.js b/app/assets/javascripts/group.js index eda0f5d1d23..ec8a238192a 100644 --- a/app/assets/javascripts/group.js +++ b/app/assets/javascripts/group.js @@ -1,4 +1,3 @@ -import $ from 'jquery'; import { slugify } from './lib/utils/text_utility'; import fetchGroupPathAvailability from '~/pages/groups/new/fetch_group_path_availability'; import flash from '~/flash'; @@ -6,44 +5,69 @@ import { __ } from '~/locale'; export default class Group { constructor() { - this.groupPath = $('#group_path'); - this.groupName = $('#group_name'); - this.parentId = $('#group_parent_id'); + this.groupPaths = Array.from(document.querySelectorAll('.js-autofill-group-path')); + this.groupNames = Array.from(document.querySelectorAll('.js-autofill-group-name')); + this.parentId = document.getElementById('group_parent_id'); this.updateHandler = this.update.bind(this); this.resetHandler = this.reset.bind(this); this.updateGroupPathSlugHandler = this.updateGroupPathSlug.bind(this); - if (this.groupName.val() === '') { - this.groupName.on('keyup', this.updateHandler); - this.groupPath.on('keydown', this.resetHandler); - if (!this.parentId.val()) { - this.groupName.on('blur', this.updateGroupPathSlugHandler); + + this.groupNames.forEach(groupName => { + if (groupName.value === '') { + groupName.addEventListener('keyup', this.updateHandler); + + if (!this.parentId.value) { + groupName.addEventListener('blur', this.updateGroupPathSlugHandler); + } } - } + }); + + this.groupPaths.forEach(groupPath => { + groupPath.addEventListener('keydown', this.resetHandler); + }); } - update() { - const slug = slugify(this.groupName.val()); - this.groupPath.val(slug); + update({ currentTarget: { value: updatedValue } }) { + const slug = slugify(updatedValue); + + this.groupNames.forEach(element => { + element.value = updatedValue; + }); + this.groupPaths.forEach(element => { + element.value = slug; + }); } reset() { - this.groupName.off('keyup', this.updateHandler); - this.groupPath.off('keydown', this.resetHandler); - this.groupName.off('blur', this.checkPathHandler); + this.groupNames.forEach(groupName => { + groupName.removeEventListener('keyup', this.updateHandler); + groupName.removeEventListener('blur', this.checkPathHandler); + }); + + this.groupPaths.forEach(groupPath => { + groupPath.removeEventListener('keydown', this.resetHandler); + }); } - updateGroupPathSlug() { - const slug = this.groupPath.val() || slugify(this.groupName.val()); + updateGroupPathSlug({ currentTarget: { value } = '' } = {}) { + const slug = this.groupPaths[0]?.value || slugify(value); if (!slug) return; fetchGroupPathAvailability(slug) .then(({ data }) => data) - .then(data => { - if (data.exists && data.suggests.length > 0) { - const suggestedSlug = data.suggests[0]; - this.groupPath.val(suggestedSlug); + .then(({ exists, suggests }) => { + if (exists && suggests.length) { + const [suggestedSlug] = suggests; + + this.groupPaths.forEach(element => { + element.value = suggestedSlug; + }); + } else if (exists && !suggests.length) { + flash(__('Unable to suggest a path. Please refresh and try again.')); } }) - .catch(() => flash(__('An error occurred while checking group path'))); + .catch(() => + flash(__('An error occurred while checking group path. Please refresh and try again.')), + ); } } diff --git a/app/assets/javascripts/onboarding_issues/index.js b/app/assets/javascripts/onboarding_issues/index.js index afc3b0c2685..5a6f952ffdf 100644 --- a/app/assets/javascripts/onboarding_issues/index.js +++ b/app/assets/javascripts/onboarding_issues/index.js @@ -1,6 +1,7 @@ import $ from 'jquery'; import { parseBoolean, getCookie, setCookie, removeCookie } from '~/lib/utils/common_utils'; import { __ } from '~/locale'; +import Tracking from '~/tracking'; const COOKIE_NAME = 'onboarding_issues_settings'; @@ -18,6 +19,7 @@ function disposePopover(event) { event.preventDefault(); this.popover('dispose'); removeLearnGitLabCookie(); + Tracking.event('Growth::Conversion::Experiment::OnboardingIssues', 'dismiss_popover'); } const showPopover = (el, path, footer, options) => { diff --git a/app/assets/javascripts/pages/admin/groups/edit/index.js b/app/assets/javascripts/pages/admin/groups/edit/index.js index ad7276132b9..a4e5df559ff 100644 --- a/app/assets/javascripts/pages/admin/groups/edit/index.js +++ b/app/assets/javascripts/pages/admin/groups/edit/index.js @@ -1,3 +1,3 @@ -import initAvatarPicker from '~/avatar_picker'; +import initFilePickers from '~/file_pickers'; -document.addEventListener('DOMContentLoaded', initAvatarPicker); +document.addEventListener('DOMContentLoaded', initFilePickers); diff --git a/app/assets/javascripts/pages/admin/groups/new/index.js b/app/assets/javascripts/pages/admin/groups/new/index.js index 6de740ee9ce..b94c999ed12 100644 --- a/app/assets/javascripts/pages/admin/groups/new/index.js +++ b/app/assets/javascripts/pages/admin/groups/new/index.js @@ -1,9 +1,10 @@ import BindInOut from '../../../../behaviors/bind_in_out'; import Group from '../../../../group'; -import initAvatarPicker from '~/avatar_picker'; +import initFilePickers from '~/file_pickers'; document.addEventListener('DOMContentLoaded', () => { BindInOut.initAll(); - new Group(); // eslint-disable-line no-new - initAvatarPicker(); + initFilePickers(); + + return new Group(); }); diff --git a/app/assets/javascripts/pages/groups/edit/index.js b/app/assets/javascripts/pages/groups/edit/index.js index f32392c9e29..33e552cd1ba 100644 --- a/app/assets/javascripts/pages/groups/edit/index.js +++ b/app/assets/javascripts/pages/groups/edit/index.js @@ -1,4 +1,4 @@ -import initAvatarPicker from '~/avatar_picker'; +import initFilePickers from '~/file_pickers'; import TransferDropdown from '~/groups/transfer_dropdown'; import initConfirmDangerModal from '~/confirm_danger_modal'; import initSettingsPanels from '~/settings_panels'; @@ -10,8 +10,7 @@ import groupsSelect from '~/groups_select'; import projectSelect from '~/project_select'; document.addEventListener('DOMContentLoaded', () => { - initAvatarPicker(); - new TransferDropdown(); // eslint-disable-line no-new + initFilePickers(); initConfirmDangerModal(); initSettingsPanels(); dirtySubmitFactory( @@ -24,4 +23,6 @@ document.addEventListener('DOMContentLoaded', () => { groupsSelect(); projectSelect(); + + return new TransferDropdown(); }); diff --git a/app/assets/javascripts/pages/groups/new/index.js b/app/assets/javascripts/pages/groups/new/index.js index 0710fefe70c..640e64b5d3e 100644 --- a/app/assets/javascripts/pages/groups/new/index.js +++ b/app/assets/javascripts/pages/groups/new/index.js @@ -1,8 +1,8 @@ import $ from 'jquery'; import BindInOut from '~/behaviors/bind_in_out'; import Group from '~/group'; -import initAvatarPicker from '~/avatar_picker'; import GroupPathValidator from './group_path_validator'; +import initFilePickers from '~/file_pickers'; document.addEventListener('DOMContentLoaded', () => { const parentId = $('#group_parent_id'); @@ -10,6 +10,7 @@ document.addEventListener('DOMContentLoaded', () => { new GroupPathValidator(); // eslint-disable-line no-new } BindInOut.initAll(); - new Group(); // eslint-disable-line no-new - initAvatarPicker(); + initFilePickers(); + + return new Group(); }); diff --git a/app/assets/javascripts/pages/projects/edit/index.js b/app/assets/javascripts/pages/projects/edit/index.js index c9dbe576c4b..9fb07917f9b 100644 --- a/app/assets/javascripts/pages/projects/edit/index.js +++ b/app/assets/javascripts/pages/projects/edit/index.js @@ -4,12 +4,12 @@ import setupTransferEdit from '~/transfer_edit'; import initConfirmDangerModal from '~/confirm_danger_modal'; import mountBadgeSettings from '~/pages/shared/mount_badge_settings'; import dirtySubmitFactory from '~/dirty_submit/dirty_submit_factory'; -import initAvatarPicker from '~/avatar_picker'; +import initFilePickers from '~/file_pickers'; import initProjectLoadingSpinner from '../shared/save_project_loader'; import initProjectPermissionsSettings from '../shared/permissions'; document.addEventListener('DOMContentLoaded', () => { - initAvatarPicker(); + initFilePickers(); initConfirmDangerModal(); initSettingsPanels(); mountBadgeSettings(PROJECT_BADGE); diff --git a/app/assets/javascripts/performance_bar/components/performance_bar_app.vue b/app/assets/javascripts/performance_bar/components/performance_bar_app.vue index c4fce020e1c..cccb5e1be06 100644 --- a/app/assets/javascripts/performance_bar/components/performance_bar_app.vue +++ b/app/assets/javascripts/performance_bar/components/performance_bar_app.vue @@ -55,7 +55,7 @@ export default { { metric: 'redis', header: s__('PerformanceBar|Redis calls'), - keys: ['cmd'], + keys: ['cmd', 'instance'], }, { metric: 'es', diff --git a/app/graphql/types/snippets/file_input_action_enum.rb b/app/graphql/types/snippets/file_input_action_enum.rb new file mode 100644 index 00000000000..7785853f3a8 --- /dev/null +++ b/app/graphql/types/snippets/file_input_action_enum.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +module Types + module Snippets + class FileInputActionEnum < BaseEnum + graphql_name 'SnippetFileInputActionEnum' + description 'Type of a snippet file input action' + + value 'create', value: :create + value 'update', value: :update + value 'delete', value: :delete + value 'move', value: :move + end + end +end diff --git a/app/graphql/types/snippets/file_input_type.rb b/app/graphql/types/snippets/file_input_type.rb new file mode 100644 index 00000000000..85a02c8f493 --- /dev/null +++ b/app/graphql/types/snippets/file_input_type.rb @@ -0,0 +1,26 @@ +# frozen_string_literal: true + +module Types + module Snippets + class FileInputType < BaseInputObject # rubocop:disable Graphql/AuthorizeTypes + graphql_name 'SnippetFileInputType' + description 'Represents an action to perform over a snippet file' + + argument :action, Types::Snippets::FileInputActionEnum, + description: 'Type of input action', + required: true + + argument :previous_path, GraphQL::STRING_TYPE, + description: 'Previous path of the snippet file', + required: false + + argument :file_path, GraphQL::STRING_TYPE, + description: 'Path of the snippet file', + required: true + + argument :content, GraphQL::STRING_TYPE, + description: 'Snippet file content', + required: false + end + end +end diff --git a/app/helpers/application_settings_helper.rb b/app/helpers/application_settings_helper.rb index 38c4cccc417..e709d15a946 100644 --- a/app/helpers/application_settings_helper.rb +++ b/app/helpers/application_settings_helper.rb @@ -26,6 +26,17 @@ module ApplicationSettingsHelper end end + def storage_weights + ApplicationSetting.repository_storages_weighted_attributes.map do |attribute| + storage = attribute.to_s.delete_prefix('repository_storages_weighted_') + { + name: attribute, + label: storage, + value: @application_setting.repository_storages_weighted[storage] || 0 + } + end + end + def all_protocols_enabled? Gitlab::CurrentSettings.enabled_git_access_protocol.blank? end diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb index e36a8f36b5b..bda9a69d71f 100644 --- a/app/helpers/projects_helper.rb +++ b/app/helpers/projects_helper.rb @@ -734,7 +734,7 @@ module ProjectsHelper end def native_code_navigation_enabled?(project) - Feature.enabled?(:code_navigation, project) + Feature.enabled?(:code_navigation, project, default_enabled: true) end def show_visibility_confirm_modal?(project) diff --git a/app/models/alert_management/alert.rb b/app/models/alert_management/alert.rb index 51efe350774..af60ddd6f9a 100644 --- a/app/models/alert_management/alert.rb +++ b/app/models/alert_management/alert.rb @@ -156,7 +156,6 @@ module AlertManagement end def execute_services - return unless Feature.enabled?(:alert_slack_event, project) return unless project.has_active_services?(:alert_hooks) project.execute_services(hook_data, :alert_hooks) diff --git a/app/models/ci/bridge.rb b/app/models/ci/bridge.rb index 1e92a47ab49..58c26e8c806 100644 --- a/app/models/ci/bridge.rb +++ b/app/models/ci/bridge.rb @@ -16,6 +16,9 @@ module Ci has_many :sourced_pipelines, class_name: "::Ci::Sources::Pipeline", foreign_key: :source_job_id + has_one :sourced_pipeline, class_name: "::Ci::Sources::Pipeline", foreign_key: :source_job_id + has_one :downstream_pipeline, through: :sourced_pipeline, source: :pipeline + validates :ref, presence: true # rubocop:disable Cop/ActiveRecordSerialize diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb index 7714dc8e10a..497e1a4d74a 100644 --- a/app/models/ci/pipeline.rb +++ b/app/models/ci/pipeline.rb @@ -41,6 +41,7 @@ module Ci has_many :statuses, class_name: 'CommitStatus', foreign_key: :commit_id, inverse_of: :pipeline has_many :latest_statuses_ordered_by_stage, -> { latest.order(:stage_idx, :stage) }, class_name: 'CommitStatus', foreign_key: :commit_id, inverse_of: :pipeline has_many :processables, class_name: 'Ci::Processable', foreign_key: :commit_id, inverse_of: :pipeline + has_many :bridges, class_name: 'Ci::Bridge', foreign_key: :commit_id, inverse_of: :pipeline has_many :builds, foreign_key: :commit_id, inverse_of: :pipeline has_many :job_artifacts, through: :builds has_many :trigger_requests, dependent: :destroy, foreign_key: :commit_id # rubocop:disable Cop/ActiveRecordDependent diff --git a/app/models/ci/pipeline_enums.rb b/app/models/ci/pipeline_enums.rb index 0b971276a4b..2ccd8445aa8 100644 --- a/app/models/ci/pipeline_enums.rb +++ b/app/models/ci/pipeline_enums.rb @@ -30,7 +30,8 @@ module Ci webide: 9, merge_request_event: 10, external_pull_request_event: 11, - parent_pipeline: 12 + parent_pipeline: 12, + ondemand_scan: 13 } end diff --git a/app/models/concerns/ci/contextable.rb b/app/models/concerns/ci/contextable.rb index ccd90ea5900..7ea5382a4fa 100644 --- a/app/models/concerns/ci/contextable.rb +++ b/app/models/concerns/ci/contextable.rb @@ -18,7 +18,7 @@ module Ci variables.concat(deployment_variables(environment: environment)) variables.concat(yaml_variables) variables.concat(user_variables) - variables.concat(dependency_variables) if Feature.enabled?(:ci_dependency_variables, project) + variables.concat(dependency_variables) variables.concat(secret_instance_variables) variables.concat(secret_group_variables) variables.concat(secret_project_variables(environment: environment)) diff --git a/app/models/project.rb b/app/models/project.rb index bd51e4779b8..f1758f1a830 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -1198,14 +1198,6 @@ class Project < ApplicationRecord get_issue(issue_id) end - def default_issue_tracker - gitlab_issue_tracker_service || create_gitlab_issue_tracker_service - end - - def issues_tracker - external_issue_tracker || default_issue_tracker - end - def external_issue_reference_pattern external_issue_tracker.class.reference_pattern(only_long: issues_enabled?) end diff --git a/app/models/project_services/jira_service.rb b/app/models/project_services/jira_service.rb index 754ea70a155..bb4d35cad22 100644 --- a/app/models/project_services/jira_service.rb +++ b/app/models/project_services/jira_service.rb @@ -414,7 +414,7 @@ class JiraService < IssueTrackerService # Handle errors when doing Jira API calls def jira_request yield - rescue Timeout::Error, Errno::EINVAL, Errno::ECONNRESET, Errno::ECONNREFUSED, URI::InvalidURIError, JIRA::HTTPError, OpenSSL::SSL::SSLError => error + rescue => error @error = error log_error("Error sending message", client_url: client_url, error: @error.message) nil diff --git a/app/models/project_services/slack_service.rb b/app/models/project_services/slack_service.rb index 1e7918016ea..79245e84238 100644 --- a/app/models/project_services/slack_service.rb +++ b/app/models/project_services/slack_service.rb @@ -25,7 +25,7 @@ class SlackService < ChatNotificationService def supported_events additional = [] - additional << 'alert' if Feature.enabled?(:alert_slack_event, project) + additional << 'alert' super + additional end diff --git a/app/serializers/diffs_entity.rb b/app/serializers/diffs_entity.rb index 5905ec5cb76..6ef524b5bec 100644 --- a/app/serializers/diffs_entity.rb +++ b/app/serializers/diffs_entity.rb @@ -78,7 +78,7 @@ class DiffsEntity < Grape::Entity options[:merge_request_diffs] end - expose :definition_path_prefix, if: -> (diff_file) { Feature.enabled?(:code_navigation, merge_request.project) } do |diffs| + expose :definition_path_prefix, if: -> (diff_file) { Feature.enabled?(:code_navigation, merge_request.project, default_enabled: true) } do |diffs| project_blob_path(merge_request.project, diffs.diff_refs&.head_sha) end @@ -89,7 +89,7 @@ class DiffsEntity < Grape::Entity private def code_navigation_path(diffs) - return unless Feature.enabled?(:code_navigation, merge_request.project) + return unless Feature.enabled?(:code_navigation, merge_request.project, default_enabled: true) Gitlab::CodeNavigationPath.new(merge_request.project, diffs.diff_refs&.head_sha) end diff --git a/app/serializers/paginated_diff_entity.rb b/app/serializers/paginated_diff_entity.rb index 0ab077d9377..37c48338e55 100644 --- a/app/serializers/paginated_diff_entity.rb +++ b/app/serializers/paginated_diff_entity.rb @@ -37,7 +37,7 @@ class PaginatedDiffEntity < Grape::Entity private def code_navigation_path(diffs) - return unless Feature.enabled?(:code_navigation, merge_request.project) + return unless Feature.enabled?(:code_navigation, merge_request.project, default_enabled: true) Gitlab::CodeNavigationPath.new(merge_request.project, diffs.diff_refs&.head_sha) end diff --git a/app/services/ci/authorize_job_artifact_service.rb b/app/services/ci/authorize_job_artifact_service.rb index a571c34062b..893e92d427c 100644 --- a/app/services/ci/authorize_job_artifact_service.rb +++ b/app/services/ci/authorize_job_artifact_service.rb @@ -36,7 +36,7 @@ module Ci def code_navigation_enabled? strong_memoize(:code_navigation_enabled) do - Feature.enabled?(:code_navigation, job.project) + Feature.enabled?(:code_navigation, job.project, default_enabled: true) end end diff --git a/app/views/admin/application_settings/_issue_limits.html.haml b/app/views/admin/application_settings/_issue_limits.html.haml index 5906358fbb1..b0bdc204f64 100644 --- a/app/views/admin/application_settings/_issue_limits.html.haml +++ b/app/views/admin/application_settings/_issue_limits.html.haml @@ -3,7 +3,7 @@ %fieldset .form-group - = f.label :issues_create_limit, 'Max requests per second per user', class: 'label-bold' + = f.label :issues_create_limit, 'Max requests per minute per user', class: 'label-bold' = f.number_field :issues_create_limit, class: 'form-control' = f.submit 'Save changes', class: "btn btn-success", data: { qa_selector: 'save_changes_button' } diff --git a/app/views/admin/application_settings/_repository_storage.html.haml b/app/views/admin/application_settings/_repository_storage.html.haml index 8f431a95425..ed276da08f2 100644 --- a/app/views/admin/application_settings/_repository_storage.html.haml +++ b/app/views/admin/application_settings/_repository_storage.html.haml @@ -15,12 +15,12 @@ .form-group .form-text %p.text-secondary - = _('Select the configured storage available for new repositories to be placed on.') + = _('Select a weight for the storage new repositories will be placed on.') = link_to icon('question-circle'), help_page_path('administration/repository_storage_paths') .form-check - - @application_setting.repository_storages_weighted.each_key do |storage| - = f.text_field "repository_storages_weighted_#{storage}".to_sym, class: 'form-text-input' - = f.label storage, storage, class: 'label-bold form-check-label' + - storage_weights.each do |attribute| + = f.text_field attribute[:name], class: 'form-text-input', value: attribute[:value] + = f.label attribute[:label], attribute[:label], class: 'label-bold form-check-label' %br = f.submit _('Save changes'), class: "btn btn-success qa-save-changes-button" diff --git a/app/views/groups/_import_group_pane.html.haml b/app/views/groups/_import_group_pane.html.haml new file mode 100644 index 00000000000..adfac7d59a5 --- /dev/null +++ b/app/views/groups/_import_group_pane.html.haml @@ -0,0 +1,52 @@ +- parent = @group.parent +- group_path = root_url +- group_path << parent.full_path + '/' if parent + += form_with url: import_gitlab_group_path, class: 'group-form gl-show-field-errors', multipart: true do |f| + = form_errors(@group) + + .row + .form-group.group-name.col-sm-12 + = f.label :name, _('Group name'), class: 'label-bold' + = f.text_field :name, placeholder: s_('GroupsNew|My Awesome Group'), class: 'js-autofill-group-name form-control input-lg', + required: true, + title: _('Please fill in a descriptive name for your group.'), + autofocus: true + + .row + .form-group.col-xs-12.col-sm-8 + = f.label :path, _('Group URL'), class: 'label-bold' + .input-group.gl-field-error-anchor + .group-root-path.input-group-prepend.has-tooltip{ title: group_path, :'data-placement' => 'bottom' } + .input-group-text + %span + = root_url + - if parent + %strong= parent.full_path + '/' + = f.hidden_field :parent_id, value: parent&.id + = f.text_field :path, placeholder: 'my-awesome-group', class: 'form-control js-validate-group-path js-autofill-group-path', + id: 'import_group_path', + required: true, + pattern: Gitlab::PathRegex::NAMESPACE_FORMAT_REGEX_JS, + title: _('Please choose a group URL with no special characters.'), + "data-bind-in" => "#{'create_chat_team' if Gitlab.config.mattermost.enabled}" + %p.validation-error.gl-field-error.field-validation.hide + = _('Group path is already taken. Suggestions: ') + %span.gl-path-suggestions + %p.validation-success.gl-field-success.field-validation.hide= _('Group path is available.') + %p.validation-pending.gl-field-error-ignore.field-validation.hide= _('Checking group path availability...') + + .row + .form-group.col-md-12 + = s_('GroupsNew|To copy a GitLab group between installations, navigate to the group settings page for the original installation, generate an export file, and upload it here.') + .row + .form-group.col-sm-12 + = f.label :file, s_('GroupsNew|GitLab group export'), class: 'label-bold' + %div + = render 'shared/file_picker_button', f: f, field: :file, help_text: nil + + .row + .form-actions.col-sm-12 + = f.submit s_('GroupsNew|Import group'), class: 'btn btn-success' + = link_to _('Cancel'), new_group_path, class: 'btn btn-cancel' + diff --git a/app/views/groups/_new_group_fields.html.haml b/app/views/groups/_new_group_fields.html.haml new file mode 100644 index 00000000000..d9706556e79 --- /dev/null +++ b/app/views/groups/_new_group_fields.html.haml @@ -0,0 +1,22 @@ += form_errors(@group) += render 'shared/group_form', f: f, autofocus: true + +.row + .form-group.group-description-holder.col-sm-12 + = f.label :avatar, _("Group avatar"), class: 'label-bold' + %div + = render 'shared/choose_avatar_button', f: f + + .form-group.col-sm-12 + %label.label-bold + = _('Visibility level') + %p + = _('Who will be able to see this group?') + = link_to _('View the documentation'), help_page_path("public_access/public_access"), target: '_blank' + = render 'shared/visibility_level', f: f, visibility_level: default_group_visibility, can_change_visibility_level: true, form_model: @group, with_label: false + + = render 'create_chat_team', f: f if Gitlab.config.mattermost.enabled + + .form-actions.col-sm-12 + = f.submit _('Create group'), class: "btn btn-success" + = link_to _('Cancel'), dashboard_groups_path, class: 'btn btn-cancel' diff --git a/app/views/groups/new.html.haml b/app/views/groups/new.html.haml index 376624f4786..ed016206310 100644 --- a/app/views/groups/new.html.haml +++ b/app/views/groups/new.html.haml @@ -2,43 +2,44 @@ - @hide_top_links = true - page_title _('New Group') - header_title _("Groups"), dashboard_groups_path +- active_tab = local_assigns.fetch(:active_tab, 'create') -.page-title-holder.d-flex.align-items-center - %h1.page-title= _('New group') -.row.prepend-top-default - .col-lg-3.profile-settings-sidebar - %p - - group_docs_path = help_page_path('user/group/index') - - group_docs_link_start = ''.html_safe % { url: group_docs_path } - = s_('%{group_docs_link_start}Groups%{group_docs_link_end} allow you to manage and collaborate across multiple projects. Members of a group have access to all of its projects.').html_safe % { group_docs_link_start: group_docs_link_start, group_docs_link_end: ''.html_safe } - %p - - subgroup_docs_path = help_page_path('user/group/subgroups/index') - - subgroup_docs_link_start = ''.html_safe % { url: subgroup_docs_path } - = s_('Groups can also be nested by creating %{subgroup_docs_link_start}subgroups%{subgroup_docs_link_end}.').html_safe % { subgroup_docs_link_start: subgroup_docs_link_start, subgroup_docs_link_end: ''.html_safe } - %p - = _('Projects that belong to a group are prefixed with the group namespace. Existing projects may be moved into a group.') +.group-edit-container.prepend-top-default + .row + .col-lg-3.group-settings-sidebar + %h4.prepend-top-0 + = _('New group') + %p + - group_docs_path = help_page_path('user/group/index') + - group_docs_link_start = ''.html_safe % { url: group_docs_path } + = s_('%{group_docs_link_start}Groups%{group_docs_link_end} allow you to manage and collaborate across multiple projects. Members of a group have access to all of its projects.').html_safe % { group_docs_link_start: group_docs_link_start, group_docs_link_end: ''.html_safe } + %p + - subgroup_docs_path = help_page_path('user/group/subgroups/index') + - subgroup_docs_link_start = ''.html_safe % { url: subgroup_docs_path } + = s_('Groups can also be nested by creating %{subgroup_docs_link_start}subgroups%{subgroup_docs_link_end}.').html_safe % { subgroup_docs_link_start: subgroup_docs_link_start, subgroup_docs_link_end: ''.html_safe } + %p + = _('Projects that belong to a group are prefixed with the group namespace. Existing projects may be moved into a group.') - .col-lg-9 - = form_for @group, html: { class: 'group-form gl-show-field-errors' } do |f| - = form_errors(@group) - = render 'shared/group_form', f: f, autofocus: true + .col-lg-9.js-toggle-container + %ul.nav.nav-tabs.nav-links.gitlab-tabs{ role: 'tablist' } + %li.nav-item{ role: 'presentation' } + %a.nav-link.active{ href: '#create-group-pane', id: 'create-group-tab', role: 'tab', data: { toggle: 'tab', track_label: 'create_group', track_event: 'click_tab', track_value: '' } } + %span.d-none.d-sm-block= s_('GroupsNew|Create group') + %span.d-block.d-sm-none= s_('GroupsNew|Create') + %li.nav-item{ role: 'presentation' } + %a.nav-link{ href: '#import-group-pane', id: 'import-group-tab', role: 'tab', data: { toggle: 'tab', track_label: 'import_group', track_event: 'click_tab', track_value: '' } } + %span.d-none.d-sm-block= s_('GroupsNew|Import group') + %span.d-block.d-sm-none= s_('GroupsNew|Import') - .row - .form-group.group-description-holder.col-sm-12 - = f.label :avatar, _("Group avatar"), class: 'label-bold' - %div - = render 'shared/choose_avatar_button', f: f + .tab-content.gitlab-tab-content + .tab-pane.js-toggle-container{ id: 'create-group-pane', class: active_when(active_tab == 'create'), role: 'tabpanel' } + = form_for @group, html: { class: 'group-form gl-show-field-errors' } do |f| + = render 'new_group_fields', f: f, group_name_id: 'create-group-name' - .form-group.col-sm-12 - %label.label-bold - = _('Visibility level') - %p - = _('Who will be able to see this group?') - = link_to _('View the documentation'), help_page_path("public_access/public_access"), target: '_blank' - = render 'shared/visibility_level', f: f, visibility_level: default_group_visibility, can_change_visibility_level: true, form_model: @group, with_label: false - - = render 'create_chat_team', f: f if Gitlab.config.mattermost.enabled - - .form-actions - = f.submit _('Create group'), class: "btn btn-success" - = link_to _('Cancel'), dashboard_groups_path, class: 'btn btn-cancel' + .tab-pane.js-toggle-container{ id: 'import-group-pane', class: active_when(active_tab) == 'import', role: 'tabpanel' } + - if import_sources_enabled? + = render 'import_group_pane', active_tab: active_tab, autofocus: true + - else + .nothing-here-block + %h4= s_('GroupsNew|No import options available') + %p= s_('GroupsNew|Contact an administrator to enable options for importing your group.') diff --git a/app/views/shared/_choose_avatar_button.html.haml b/app/views/shared/_choose_avatar_button.html.haml index 0d46d047134..caf2bdce899 100644 --- a/app/views/shared/_choose_avatar_button.html.haml +++ b/app/views/shared/_choose_avatar_button.html.haml @@ -1,4 +1 @@ -%button.btn.js-choose-avatar-button{ type: 'button' }= _("Choose file…") -%span.file_name.js-avatar-filename= _("No file chosen") -= f.file_field :avatar, class: "js-avatar-input hidden" -.form-text.text-muted= _("The maximum file size allowed is 200KB.") += render 'shared/file_picker_button', f: f, field: :avatar, help_text: _("The maximum file size allowed is 200KB.") diff --git a/app/views/shared/_file_picker_button.html.haml b/app/views/shared/_file_picker_button.html.haml new file mode 100644 index 00000000000..7c9a3bd3d31 --- /dev/null +++ b/app/views/shared/_file_picker_button.html.haml @@ -0,0 +1,6 @@ +%span.js-filepicker + %button.btn.js-filepicker-button{ type: 'button' }= _("Choose file…") + %span.file_name.js-filepicker-filename= _("No file chosen") + = f.file_field field, class: "js-filepicker-input hidden" + - if help_text.present? + .form-text.text-muted= help_text diff --git a/app/views/shared/_group_form.html.haml b/app/views/shared/_group_form.html.haml index 019b2ef89a4..09b9cd448bb 100644 --- a/app/views/shared/_group_form.html.haml +++ b/app/views/shared/_group_form.html.haml @@ -6,7 +6,7 @@ .form-group.group-name-holder.col-sm-12 = f.label :name, class: 'label-bold' do = _("Group name") - = f.text_field :name, placeholder: _('My Awesome Group'), class: 'form-control input-lg', + = f.text_field :name, placeholder: _('My Awesome Group'), class: 'js-autofill-group-name form-control input-lg', required: true, title: _('Please fill in a descriptive name for your group.'), autofocus: true @@ -22,7 +22,7 @@ - if parent %strong= parent.full_path + '/' = f.hidden_field :parent_id - = f.text_field :path, placeholder: _('my-awesome-group'), class: 'form-control js-validate-group-path', + = f.text_field :path, placeholder: _('my-awesome-group'), class: 'form-control js-validate-group-path js-autofill-group-path', autofocus: local_assigns[:autofocus] || false, required: true, pattern: Gitlab::PathRegex::NAMESPACE_FORMAT_REGEX_JS, title: _('Please choose a group URL with no special characters.'), diff --git a/changelogs/unreleased/214607-ci-jwt-signing-key-check.yml b/changelogs/unreleased/214607-ci-jwt-signing-key-check.yml new file mode 100644 index 00000000000..5814e7f0b43 --- /dev/null +++ b/changelogs/unreleased/214607-ci-jwt-signing-key-check.yml @@ -0,0 +1,5 @@ +--- +title: Add system check for CI JWT signing key +merge_request: 33920 +author: +type: added diff --git a/changelogs/unreleased/217834-remove-FF-ci_dependency_variables.yml b/changelogs/unreleased/217834-remove-FF-ci_dependency_variables.yml new file mode 100644 index 00000000000..d237974389a --- /dev/null +++ b/changelogs/unreleased/217834-remove-FF-ci_dependency_variables.yml @@ -0,0 +1,5 @@ +--- +title: Enable CI Inheriting Env Variables feature +merge_request: 34495 +author: +type: added diff --git a/changelogs/unreleased/219658-add-route-to-ghost-lost-and-found-group.yml b/changelogs/unreleased/219658-add-route-to-ghost-lost-and-found-group.yml new file mode 100644 index 00000000000..d60ab67bee9 --- /dev/null +++ b/changelogs/unreleased/219658-add-route-to-ghost-lost-and-found-group.yml @@ -0,0 +1,5 @@ +--- +title: Add route for the lost-and-found group and update the route of orphaned projects +merge_request: 34285 +author: +type: fixed diff --git a/changelogs/unreleased/220415-feature-flag-enable-alert-slack-notifications.yml b/changelogs/unreleased/220415-feature-flag-enable-alert-slack-notifications.yml new file mode 100644 index 00000000000..fc1198d8344 --- /dev/null +++ b/changelogs/unreleased/220415-feature-flag-enable-alert-slack-notifications.yml @@ -0,0 +1,5 @@ +--- +title: Enable Slack notifications for alerts +merge_request: 34038 +author: +type: added diff --git a/changelogs/unreleased/Remove-removeMilestone-logic-from-issue-model.yml b/changelogs/unreleased/Remove-removeMilestone-logic-from-issue-model.yml new file mode 100644 index 00000000000..ff000f33c53 --- /dev/null +++ b/changelogs/unreleased/Remove-removeMilestone-logic-from-issue-model.yml @@ -0,0 +1,5 @@ +--- +title: Remove removeMilestone logic from issue model +merge_request: 32253 +author: nuwe1 +type: other diff --git a/changelogs/unreleased/feature-api-add-bridge-api-endpoint.yml b/changelogs/unreleased/feature-api-add-bridge-api-endpoint.yml new file mode 100644 index 00000000000..02298d74233 --- /dev/null +++ b/changelogs/unreleased/feature-api-add-bridge-api-endpoint.yml @@ -0,0 +1,5 @@ +--- +title: Add API endpoint for listing bridge jobs. +merge_request: 31370 +author: Abhijith Sivarajan +type: added diff --git a/changelogs/unreleased/fix-typo-issues-limit-settings-template.yml b/changelogs/unreleased/fix-typo-issues-limit-settings-template.yml new file mode 100644 index 00000000000..c0fd8b45692 --- /dev/null +++ b/changelogs/unreleased/fix-typo-issues-limit-settings-template.yml @@ -0,0 +1,5 @@ +--- +title: Update issue limits template to use minutes +merge_request: 34254 +author: +type: fixed diff --git a/changelogs/unreleased/fj-add-snippet-file-input-action.yml b/changelogs/unreleased/fj-add-snippet-file-input-action.yml new file mode 100644 index 00000000000..583e5038633 --- /dev/null +++ b/changelogs/unreleased/fj-add-snippet-file-input-action.yml @@ -0,0 +1,5 @@ +--- +title: Add GraphQL snippet FileInputType +merge_request: 34442 +author: +type: other diff --git a/changelogs/unreleased/id-code-navigation-enable-feature.yml b/changelogs/unreleased/id-code-navigation-enable-feature.yml new file mode 100644 index 00000000000..e1f9a9c1978 --- /dev/null +++ b/changelogs/unreleased/id-code-navigation-enable-feature.yml @@ -0,0 +1,5 @@ +--- +title: Add native code intelligence +merge_request: 34542 +author: +type: added diff --git a/changelogs/unreleased/jc-show-all-storages.yml b/changelogs/unreleased/jc-show-all-storages.yml new file mode 100644 index 00000000000..fac5d5bb813 --- /dev/null +++ b/changelogs/unreleased/jc-show-all-storages.yml @@ -0,0 +1,5 @@ +--- +title: Show all storages in settings +merge_request: 34093 +author: +type: fixed diff --git a/changelogs/unreleased/jh-group_import_ui_frontend.yml b/changelogs/unreleased/jh-group_import_ui_frontend.yml new file mode 100644 index 00000000000..e820607cc76 --- /dev/null +++ b/changelogs/unreleased/jh-group_import_ui_frontend.yml @@ -0,0 +1,5 @@ +--- +title: Create Group import UI for creating new Groups +merge_request: 29271 +author: +type: added diff --git a/changelogs/unreleased/osw-separate-redis-logs.yml b/changelogs/unreleased/osw-separate-redis-logs.yml new file mode 100644 index 00000000000..dbef2557df7 --- /dev/null +++ b/changelogs/unreleased/osw-separate-redis-logs.yml @@ -0,0 +1,6 @@ +--- +title: Add detailed logs of each Redis instance usage during job execution and web + requests +merge_request: 34110 +author: +type: added diff --git a/changelogs/unreleased/rspec-rails-fast-failure-template.yml b/changelogs/unreleased/rspec-rails-fast-failure-template.yml new file mode 100644 index 00000000000..c466c68075a --- /dev/null +++ b/changelogs/unreleased/rspec-rails-fast-failure-template.yml @@ -0,0 +1,5 @@ +--- +title: Add Verify/FailFast CI template +merge_request: 31812 +author: +type: added diff --git a/changelogs/unreleased/show-redis-instance-in-performance-bar.yml b/changelogs/unreleased/show-redis-instance-in-performance-bar.yml new file mode 100644 index 00000000000..80dbd33f110 --- /dev/null +++ b/changelogs/unreleased/show-redis-instance-in-performance-bar.yml @@ -0,0 +1,5 @@ +--- +title: Show Redis instance in performance bar +merge_request: 34377 +author: +type: changed diff --git a/db/migrate/20190315191339_create_merge_request_assignees_table.rb b/db/migrate/20190315191339_create_merge_request_assignees_table.rb index 6fc4463f281..dbd9ea3e35b 100644 --- a/db/migrate/20190315191339_create_merge_request_assignees_table.rb +++ b/db/migrate/20190315191339_create_merge_request_assignees_table.rb @@ -17,6 +17,8 @@ class CreateMergeRequestAssigneesTable < ActiveRecord::Migration[5.0] end def down + # rubocop:disable Migration/DropTable drop_table :merge_request_assignees + # rubocop:enable Migration/DropTable end end diff --git a/db/migrate/20190402150158_backport_enterprise_schema.rb b/db/migrate/20190402150158_backport_enterprise_schema.rb index 694c0feba0a..912da09af9d 100644 --- a/db/migrate/20190402150158_backport_enterprise_schema.rb +++ b/db/migrate/20190402150158_backport_enterprise_schema.rb @@ -193,7 +193,9 @@ class BackportEnterpriseSchema < ActiveRecord::Migration[5.0] end def drop_table_if_exists(table) + # rubocop:disable Migration/DropTable drop_table(table) if table_exists?(table) + # rubocop:enable Migration/DropTable end def add_column_with_default_if_not_exists(table, name, *args) diff --git a/db/migrate/20190722144316_create_milestone_releases_table.rb b/db/migrate/20190722144316_create_milestone_releases_table.rb index 55878bcec41..911ca941a56 100644 --- a/db/migrate/20190722144316_create_milestone_releases_table.rb +++ b/db/migrate/20190722144316_create_milestone_releases_table.rb @@ -15,6 +15,8 @@ class CreateMilestoneReleasesTable < ActiveRecord::Migration[5.2] end def down + # rubocop:disable Migration/DropTable drop_table :milestone_releases + # rubocop:enable Migration/DropTable end end diff --git a/db/migrate/20190927055500_create_description_versions.rb b/db/migrate/20190927055500_create_description_versions.rb index b3082533a6f..9046ebbc499 100644 --- a/db/migrate/20190927055500_create_description_versions.rb +++ b/db/migrate/20190927055500_create_description_versions.rb @@ -24,6 +24,8 @@ class CreateDescriptionVersions < ActiveRecord::Migration[5.2] def down remove_column :system_note_metadata, :description_version_id + # rubocop:disable Migration/DropTable drop_table :description_versions + # rubocop:enable Migration/DropTable end end diff --git a/db/migrate/20191118053631_add_group_deletion_schedules.rb b/db/migrate/20191118053631_add_group_deletion_schedules.rb index 6f3ed27e156..bc18480e5b9 100644 --- a/db/migrate/20191118053631_add_group_deletion_schedules.rb +++ b/db/migrate/20191118053631_add_group_deletion_schedules.rb @@ -23,6 +23,8 @@ class AddGroupDeletionSchedules < ActiveRecord::Migration[5.2] end def down + # rubocop:disable Migration/DropTable drop_table :group_deletion_schedules + # rubocop:enable Migration/DropTable end end diff --git a/db/migrate/20191127151619_create_gitlab_subscription_histories.rb b/db/migrate/20191127151619_create_gitlab_subscription_histories.rb index 718f2c1b313..db2617112a3 100644 --- a/db/migrate/20191127151619_create_gitlab_subscription_histories.rb +++ b/db/migrate/20191127151619_create_gitlab_subscription_histories.rb @@ -23,6 +23,8 @@ class CreateGitlabSubscriptionHistories < ActiveRecord::Migration[5.2] end def down + # rubocop:disable Migration/DropTable drop_table :gitlab_subscription_histories + # rubocop:enable Migration/DropTable end end diff --git a/db/migrate/20200123091622_drop_analytics_repository_files_table.rb b/db/migrate/20200123091622_drop_analytics_repository_files_table.rb index aa31d23920a..ed6746165a8 100644 --- a/db/migrate/20200123091622_drop_analytics_repository_files_table.rb +++ b/db/migrate/20200123091622_drop_analytics_repository_files_table.rb @@ -7,7 +7,9 @@ class DropAnalyticsRepositoryFilesTable < ActiveRecord::Migration[5.2] def up # Requires ExclusiveLock on the table. Not in use, no records, no FKs. + # rubocop:disable Migration/DropTable drop_table :analytics_repository_files + # rubocop:enable Migration/DropTable end def down diff --git a/db/migrate/20200123091734_drop_analytics_repository_file_commits_table.rb b/db/migrate/20200123091734_drop_analytics_repository_file_commits_table.rb index 2d3c1c9a817..2eb10a9056d 100644 --- a/db/migrate/20200123091734_drop_analytics_repository_file_commits_table.rb +++ b/db/migrate/20200123091734_drop_analytics_repository_file_commits_table.rb @@ -7,7 +7,9 @@ class DropAnalyticsRepositoryFileCommitsTable < ActiveRecord::Migration[5.2] def up # Requires ExclusiveLock on the table. Not in use, no records, no FKs. + # rubocop:disable Migration/DropTable drop_table :analytics_repository_file_commits + # rubocop:enable Migration/DropTable end def down diff --git a/db/migrate/20200123091854_drop_analytics_repository_file_edits_table.rb b/db/migrate/20200123091854_drop_analytics_repository_file_edits_table.rb index 59bf2dbdca3..e4bdb6f6ec2 100644 --- a/db/migrate/20200123091854_drop_analytics_repository_file_edits_table.rb +++ b/db/migrate/20200123091854_drop_analytics_repository_file_edits_table.rb @@ -7,7 +7,9 @@ class DropAnalyticsRepositoryFileEditsTable < ActiveRecord::Migration[5.2] def up # Requires ExclusiveLock on the table. Not in use, no records, no FKs. + # rubocop:disable Migration/DropTable drop_table :analytics_repository_file_edits if table_exists?(:analytics_repository_file_edits) # this table might be already dropped on development environment + # rubocop:enable Migration/DropTable end def down diff --git a/db/migrate/20200214025454_add_canonical_emails.rb b/db/migrate/20200214025454_add_canonical_emails.rb index 0732d39169d..70ab7208ade 100644 --- a/db/migrate/20200214025454_add_canonical_emails.rb +++ b/db/migrate/20200214025454_add_canonical_emails.rb @@ -20,7 +20,9 @@ class AddCanonicalEmails < ActiveRecord::Migration[6.0] def down with_lock_retries do + # rubocop:disable Migration/DropTable drop_table(:user_canonical_emails) + # rubocop:enable Migration/DropTable end end end diff --git a/db/migrate/20200215225103_drop_forked_project_links_table.rb b/db/migrate/20200215225103_drop_forked_project_links_table.rb index 9c028d23dbc..6acced2c734 100644 --- a/db/migrate/20200215225103_drop_forked_project_links_table.rb +++ b/db/migrate/20200215225103_drop_forked_project_links_table.rb @@ -8,6 +8,7 @@ class DropForkedProjectLinksTable < ActiveRecord::Migration[6.0] DOWNTIME = false def change + # rubocop:disable Migration/DropTable drop_table "forked_project_links", id: :serial do |t| t.integer "forked_to_project_id", null: false t.integer "forked_from_project_id", null: false @@ -15,5 +16,6 @@ class DropForkedProjectLinksTable < ActiveRecord::Migration[6.0] t.datetime "updated_at" t.index ["forked_to_project_id"], name: "index_forked_project_links_on_forked_to_project_id", unique: true end + # rubocop:enable Migration/DropTable end end diff --git a/db/migrate/20200227165129_create_user_details.rb b/db/migrate/20200227165129_create_user_details.rb index 89258eadb9f..474dc357266 100644 --- a/db/migrate/20200227165129_create_user_details.rb +++ b/db/migrate/20200227165129_create_user_details.rb @@ -20,7 +20,9 @@ class CreateUserDetails < ActiveRecord::Migration[6.0] def down with_lock_retries do + # rubocop:disable Migration/DropTable drop_table :user_details + # rubocop:enable Migration/DropTable end end end diff --git a/db/migrate/20200311093210_create_user_highest_roles.rb b/db/migrate/20200311093210_create_user_highest_roles.rb index 36007f196d1..df2b02b7d91 100644 --- a/db/migrate/20200311093210_create_user_highest_roles.rb +++ b/db/migrate/20200311093210_create_user_highest_roles.rb @@ -19,7 +19,9 @@ class CreateUserHighestRoles < ActiveRecord::Migration[6.0] def down with_lock_retries do + # rubocop:disable Migration/DropTable drop_table :user_highest_roles + # rubocop:enable Migration/DropTable end end end diff --git a/db/migrate/20200326122700_create_diff_note_positions.rb b/db/migrate/20200326122700_create_diff_note_positions.rb index d37f7fef078..6c558516471 100644 --- a/db/migrate/20200326122700_create_diff_note_positions.rb +++ b/db/migrate/20200326122700_create_diff_note_positions.rb @@ -30,6 +30,8 @@ class CreateDiffNotePositions < ActiveRecord::Migration[6.0] # rubocop:enable Migration/AddLimitToTextColumns def down + # rubocop:disable Migration/DropTable drop_table :diff_note_positions + # rubocop:enable Migration/DropTable end end diff --git a/db/migrate/20200330203837_recreate_ci_ref.rb b/db/migrate/20200330203837_recreate_ci_ref.rb index 9576330afd7..5a7bede4dc8 100644 --- a/db/migrate/20200330203837_recreate_ci_ref.rb +++ b/db/migrate/20200330203837_recreate_ci_ref.rb @@ -9,7 +9,9 @@ class RecreateCiRef < ActiveRecord::Migration[6.0] def up with_lock_retries do + # rubocop:disable Migration/DropTable drop_table :ci_refs + # rubocop:enable Migration/DropTable create_table :ci_refs do |t| t.references :project, null: false, index: false, foreign_key: { on_delete: :cascade }, type: :bigint @@ -23,7 +25,9 @@ class RecreateCiRef < ActiveRecord::Migration[6.0] def down with_lock_retries do + # rubocop:disable Migration/DropTable drop_table :ci_refs + # rubocop:enable Migration/DropTable create_table :ci_refs do |t| t.references :project, null: false, index: false, foreign_key: { on_delete: :cascade }, type: :integer diff --git a/db/migrate/20200331132103_add_project_compliance_framework_settings_table.rb b/db/migrate/20200331132103_add_project_compliance_framework_settings_table.rb index 6af8c6db939..169c8602cab 100644 --- a/db/migrate/20200331132103_add_project_compliance_framework_settings_table.rb +++ b/db/migrate/20200331132103_add_project_compliance_framework_settings_table.rb @@ -16,7 +16,9 @@ class AddProjectComplianceFrameworkSettingsTable < ActiveRecord::Migration[6.0] def down with_lock_retries do + # rubocop:disable Migration/DropTable drop_table :project_compliance_framework_settings + # rubocop:enable Migration/DropTable end end end diff --git a/db/migrate/20200407182205_create_partitioned_foreign_keys.rb b/db/migrate/20200407182205_create_partitioned_foreign_keys.rb index aca8116d2dd..59e7d88b238 100644 --- a/db/migrate/20200407182205_create_partitioned_foreign_keys.rb +++ b/db/migrate/20200407182205_create_partitioned_foreign_keys.rb @@ -26,6 +26,8 @@ class CreatePartitionedForeignKeys < ActiveRecord::Migration[6.0] end def down + # rubocop:disable Migration/DropTable drop_table :partitioned_foreign_keys + # rubocop:enable Migration/DropTable end end diff --git a/db/migrate/20200407222647_create_project_repository_storage_moves.rb b/db/migrate/20200407222647_create_project_repository_storage_moves.rb index 402a1cdd4a6..98e44aa2fc6 100644 --- a/db/migrate/20200407222647_create_project_repository_storage_moves.rb +++ b/db/migrate/20200407222647_create_project_repository_storage_moves.rb @@ -26,6 +26,8 @@ class CreateProjectRepositoryStorageMoves < ActiveRecord::Migration[6.0] remove_check_constraint(:project_repository_storage_moves, 'project_repository_storage_moves_source_storage_name') remove_check_constraint(:project_repository_storage_moves, 'project_repository_storage_moves_destination_storage_name') + # rubocop:disable Migration/DropTable drop_table :project_repository_storage_moves + # rubocop:enable Migration/DropTable end end diff --git a/db/migrate/20200408125046_create_ci_freeze_periods.rb b/db/migrate/20200408125046_create_ci_freeze_periods.rb index 42a385150b8..98f0e20b11b 100644 --- a/db/migrate/20200408125046_create_ci_freeze_periods.rb +++ b/db/migrate/20200408125046_create_ci_freeze_periods.rb @@ -25,6 +25,8 @@ class CreateCiFreezePeriods < ActiveRecord::Migration[6.0] end def down + # rubocop:disable Migration/DropTable drop_table :ci_freeze_periods + # rubocop:enable Migration/DropTable end end diff --git a/db/migrate/20200416005331_create_status_page_published_incidents.rb b/db/migrate/20200416005331_create_status_page_published_incidents.rb index 75889cd5bb6..ea2ddcde925 100644 --- a/db/migrate/20200416005331_create_status_page_published_incidents.rb +++ b/db/migrate/20200416005331_create_status_page_published_incidents.rb @@ -15,6 +15,8 @@ class CreateStatusPagePublishedIncidents < ActiveRecord::Migration[6.0] end def down + # rubocop:disable Migration/DropTable drop_table :status_page_published_incidents + # rubocop:enable Migration/DropTable end end diff --git a/db/migrate/20200417044453_create_alert_management_alerts.rb b/db/migrate/20200417044453_create_alert_management_alerts.rb index 6221eeeb24b..3509f4946a7 100644 --- a/db/migrate/20200417044453_create_alert_management_alerts.rb +++ b/db/migrate/20200417044453_create_alert_management_alerts.rb @@ -39,6 +39,8 @@ class CreateAlertManagementAlerts < ActiveRecord::Migration[6.0] end def down + # rubocop:disable Migration/DropTable drop_table :alert_management_alerts + # rubocop:enable Migration/DropTable end end diff --git a/db/migrate/20200420104303_add_group_import_states_table.rb b/db/migrate/20200420104303_add_group_import_states_table.rb index a44a2ea75f3..10b8cd4aa49 100644 --- a/db/migrate/20200420104303_add_group_import_states_table.rb +++ b/db/migrate/20200420104303_add_group_import_states_table.rb @@ -20,6 +20,8 @@ class AddGroupImportStatesTable < ActiveRecord::Migration[6.0] # rubocop:enable Migration/AddLimitToTextColumns def down + # rubocop:disable Migration/DropTable drop_table :group_import_states + # rubocop:enable Migration/DropTable end end diff --git a/db/migrate/20200420115948_create_metrics_users_starred_dashboard.rb b/db/migrate/20200420115948_create_metrics_users_starred_dashboard.rb index 27130136e9d..8e9495f3a83 100644 --- a/db/migrate/20200420115948_create_metrics_users_starred_dashboard.rb +++ b/db/migrate/20200420115948_create_metrics_users_starred_dashboard.rb @@ -20,6 +20,8 @@ class CreateMetricsUsersStarredDashboard < ActiveRecord::Migration[6.0] # rubocop: enable Migration/AddLimitToTextColumns def down + # rubocop:disable Migration/DropTable drop_table :metrics_users_starred_dashboards + # rubocop:enable Migration/DropTable end end diff --git a/db/migrate/20200422091541_create_ci_instance_variables.rb b/db/migrate/20200422091541_create_ci_instance_variables.rb index ab2a4722f89..d1c954b4020 100644 --- a/db/migrate/20200422091541_create_ci_instance_variables.rb +++ b/db/migrate/20200422091541_create_ci_instance_variables.rb @@ -26,6 +26,8 @@ class CreateCiInstanceVariables < ActiveRecord::Migration[6.0] end def down + # rubocop:disable Migration/DropTable drop_table :ci_instance_variables + # rubocop:enable Migration/DropTable end end diff --git a/db/migrate/20200424135319_create_nuget_dependency_link_metadata.rb b/db/migrate/20200424135319_create_nuget_dependency_link_metadata.rb index 1043616a1c7..79adf41f973 100644 --- a/db/migrate/20200424135319_create_nuget_dependency_link_metadata.rb +++ b/db/migrate/20200424135319_create_nuget_dependency_link_metadata.rb @@ -21,6 +21,8 @@ class CreateNugetDependencyLinkMetadata < ActiveRecord::Migration[6.0] end def down + # rubocop:disable Migration/DropTable drop_table :packages_nuget_dependency_link_metadata + # rubocop:enable Migration/DropTable end end diff --git a/db/migrate/20200430130048_create_packages_nuget_metadata.rb b/db/migrate/20200430130048_create_packages_nuget_metadata.rb index 0f0d490c93d..3c89a143932 100644 --- a/db/migrate/20200430130048_create_packages_nuget_metadata.rb +++ b/db/migrate/20200430130048_create_packages_nuget_metadata.rb @@ -29,6 +29,8 @@ class CreatePackagesNugetMetadata < ActiveRecord::Migration[6.0] end def down + # rubocop:disable Migration/DropTable drop_table :packages_nuget_metadata + # rubocop:enable Migration/DropTable end end diff --git a/db/migrate/20200430174637_create_group_deploy_keys.rb b/db/migrate/20200430174637_create_group_deploy_keys.rb index 9771ae013ea..283c8769a80 100644 --- a/db/migrate/20200430174637_create_group_deploy_keys.rb +++ b/db/migrate/20200430174637_create_group_deploy_keys.rb @@ -31,6 +31,8 @@ class CreateGroupDeployKeys < ActiveRecord::Migration[6.0] end def down + # rubocop:disable Migration/DropTable drop_table :group_deploy_keys + # rubocop:enable Migration/DropTable end end diff --git a/db/migrate/20200521225327_create_alert_management_alert_assignees.rb b/db/migrate/20200521225327_create_alert_management_alert_assignees.rb index 99de2646b3d..095b2fdfeee 100644 --- a/db/migrate/20200521225327_create_alert_management_alert_assignees.rb +++ b/db/migrate/20200521225327_create_alert_management_alert_assignees.rb @@ -17,6 +17,8 @@ class CreateAlertManagementAlertAssignees < ActiveRecord::Migration[6.0] end def down + # rubocop:disable Migration/DropTable drop_table :alert_management_alert_assignees + # rubocop:enable Migration/DropTable end end diff --git a/db/migrate/20200604143628_create_project_security_settings.rb b/db/migrate/20200604143628_create_project_security_settings.rb index f972cb509a7..b1a08cf8781 100644 --- a/db/migrate/20200604143628_create_project_security_settings.rb +++ b/db/migrate/20200604143628_create_project_security_settings.rb @@ -21,7 +21,9 @@ class CreateProjectSecuritySettings < ActiveRecord::Migration[6.0] def down with_lock_retries do + # rubocop:disable Migration/DropTable drop_table :project_security_settings + # rubocop:enable Migration/DropTable end end end diff --git a/db/migrate/20200604145731_create_board_user_preferences.rb b/db/migrate/20200604145731_create_board_user_preferences.rb index e83f467d690..36e5014fdbe 100644 --- a/db/migrate/20200604145731_create_board_user_preferences.rb +++ b/db/migrate/20200604145731_create_board_user_preferences.rb @@ -15,6 +15,8 @@ class CreateBoardUserPreferences < ActiveRecord::Migration[6.0] end def down + # rubocop:disable Migration/DropTable drop_table :board_user_preferences + # rubocop:enable Migration/DropTable end end diff --git a/db/post_migrate/20200602143020_update_routes_for_lost_and_found_group_and_orphaned_projects.rb b/db/post_migrate/20200602143020_update_routes_for_lost_and_found_group_and_orphaned_projects.rb new file mode 100644 index 00000000000..1935eaa1237 --- /dev/null +++ b/db/post_migrate/20200602143020_update_routes_for_lost_and_found_group_and_orphaned_projects.rb @@ -0,0 +1,178 @@ +# frozen_string_literal: true + +# This migration adds or updates the routes for all the entities affected by +# post-migration '20200511083541_cleanup_projects_with_missing_namespace' +# - A route is added for the 'lost-and-found' group +# - A route is added for the Ghost user (if not already defined) +# - The routes for all the orphaned projects that were moved under the 'lost-and-found' +# group are updated to reflect the new path +class UpdateRoutesForLostAndFoundGroupAndOrphanedProjects < ActiveRecord::Migration[6.0] + DOWNTIME = false + + class User < ActiveRecord::Base + self.table_name = 'users' + + LOST_AND_FOUND_GROUP = 'lost-and-found' + USER_TYPE_GHOST = 5 + ACCESS_LEVEL_OWNER = 50 + + has_one :namespace, -> { where(type: nil) }, + foreign_key: :owner_id, inverse_of: :owner, autosave: true, + class_name: 'UpdateRoutesForLostAndFoundGroupAndOrphanedProjects::Namespace' + + def lost_and_found_group + # Find the 'lost-and-found' group + # There should only be one Group owned by the Ghost user starting with 'lost-and-found' + Group + .joins('INNER JOIN members ON namespaces.id = members.source_id') + .where('namespaces.type = ?', 'Group') + .where('members.type = ?', 'GroupMember') + .where('members.source_type = ?', 'Namespace') + .where('members.user_id = ?', self.id) + .where('members.access_level = ?', ACCESS_LEVEL_OWNER) + .find_by(Group.arel_table[:name].matches("#{LOST_AND_FOUND_GROUP}%")) + end + + class << self + # Return the ghost user + def ghost + User.find_by(user_type: USER_TYPE_GHOST) + end + end + end + + # Temporary Concern to not repeat the same methods twice + module HasPath + extend ActiveSupport::Concern + + def full_path + if parent && path + parent.full_path + '/' + path + else + path + end + end + + def full_name + if parent && name + parent.full_name + ' / ' + name + else + name + end + end + end + + class Namespace < ActiveRecord::Base + include HasPath + + self.table_name = 'namespaces' + + belongs_to :owner, class_name: 'UpdateRoutesForLostAndFoundGroupAndOrphanedProjects::User' + belongs_to :parent, class_name: "UpdateRoutesForLostAndFoundGroupAndOrphanedProjects::Namespace" + has_many :children, foreign_key: :parent_id, + class_name: "UpdateRoutesForLostAndFoundGroupAndOrphanedProjects::Namespace" + has_many :projects, class_name: "UpdateRoutesForLostAndFoundGroupAndOrphanedProjects::Project" + + def ensure_route! + unless Route.for_source('Namespace', self.id) + Route.create!( + source_id: self.id, + source_type: 'Namespace', + path: self.full_path, + name: self.full_name + ) + end + end + + def generate_unique_path + # Generate a unique path if there is no route for the namespace + # (an existing route guarantees that the path is already unique) + unless Route.for_source('Namespace', self.id) + self.path = Uniquify.new.string(self.path) do |str| + Route.where(path: str).exists? + end + end + end + end + + class Group < Namespace + # Disable STI to allow us to manually set "type = 'Group'" + # Otherwise rails forces "type = CleanupProjectsWithMissingNamespace::Group" + self.inheritance_column = :_type_disabled + end + + class Route < ActiveRecord::Base + self.table_name = 'routes' + + def self.for_source(source_type, source_id) + Route.find_by(source_type: source_type, source_id: source_id) + end + end + + class Project < ActiveRecord::Base + include HasPath + + self.table_name = 'projects' + + belongs_to :group, -> { where(type: 'Group') }, foreign_key: 'namespace_id', + class_name: "UpdateRoutesForLostAndFoundGroupAndOrphanedProjects::Group" + belongs_to :namespace, + class_name: "UpdateRoutesForLostAndFoundGroupAndOrphanedProjects::Namespace" + + alias_method :parent, :namespace + alias_attribute :parent_id, :namespace_id + + def ensure_route! + Route.find_or_initialize_by(source_type: 'Project', source_id: self.id).tap do |record| + record.path = self.full_path + record.name = self.full_name + record.save! + end + end + end + + def up + # Reset the column information of all the models that update the database + # to ensure the Active Record's knowledge of the table structure is current + Namespace.reset_column_information + Route.reset_column_information + + # Find the ghost user, its namespace and the "lost and found" group + ghost_user = User.ghost + return unless ghost_user # No reason to continue if there is no Ghost user + + ghost_namespace = ghost_user.namespace + lost_and_found_group = ghost_user.lost_and_found_group + + # No reason to continue if there is no 'lost-and-found' group + # 1. No orphaned projects were found in this instance, or + # 2. The 'lost-and-found' group and the orphaned projects have been already deleted + return unless lost_and_found_group + + # Update the 'lost-and-found' group description to be more self-explanatory + lost_and_found_group.generate_unique_path + lost_and_found_group.description = + 'Group for storing projects that were not properly deleted. '\ + 'It should be considered as a system level Group with non-working '\ + 'projects inside it. The contents may be deleted with a future update. '\ + 'More info: gitlab.com/gitlab-org/gitlab/-/issues/198603' + lost_and_found_group.save! + + # Update the routes for the Ghost user, the "lost and found" group + # and all the orphaned projects + ghost_namespace.ensure_route! + lost_and_found_group.ensure_route! + + # The following does a fast index scan by namespace_id + # No reason to process in batches: + # - 66 projects in GitLab.com, less than 1ms execution time to fetch them + # with a constant update time for each + lost_and_found_group.projects.each do |project| + project.ensure_route! + end + end + + def down + # no-op + end +end diff --git a/db/structure.sql b/db/structure.sql index 214217b699d..e78b1c41a09 100644 --- a/db/structure.sql +++ b/db/structure.sql @@ -13974,6 +13974,7 @@ COPY "schema_migrations" (version) FROM STDIN; 20200601210148 20200602013900 20200602013901 +20200602143020 20200603073101 20200603180338 20200604143628 diff --git a/doc/administration/logs.md b/doc/administration/logs.md index 55295446968..7d7053a26db 100644 --- a/doc/administration/logs.md +++ b/doc/administration/logs.md @@ -64,13 +64,17 @@ seconds: 1. `queue_duration_s`: total time that the request was queued inside GitLab Workhorse 1. `view_duration_s`: total time taken inside the Rails views 1. `db_duration_s`: total time to retrieve data from PostgreSQL -1. `redis_duration_s`: total time to retrieve data from Redis 1. `cpu_s`: total time spent on CPU 1. `gitaly_duration_s`: total time taken by Gitaly calls 1. `gitaly_calls`: total number of calls made to Gitaly 1. `redis_calls`: total number of calls made to Redis +1. `redis_duration_s`: total time to retrieve data from Redis 1. `redis_read_bytes`: total bytes read from Redis 1. `redis_write_bytes`: total bytes written to Redis +1. `redis__calls`: total number of calls made to a Redis instance +1. `redis__duration_s`: total time to retrieve data from a Redis instance +1. `redis__read_bytes`: total bytes read from a Redis instance +1. `redis__write_bytes`: total bytes written to a Redis instance User clone and fetch activity using HTTP transport appears in this log as `action: git_upload_pack`. diff --git a/doc/administration/pages/index.md b/doc/administration/pages/index.md index c3d496c7a42..a7a3a86de8e 100644 --- a/doc/administration/pages/index.md +++ b/doc/administration/pages/index.md @@ -208,7 +208,6 @@ control over how the Pages daemon runs and serves content in your environment. | `gitlab_secret` | The OAuth application secret. Leave blank to automatically fill when Pages authenticates with GitLab. | `gitlab_server` | Server to use for authentication when access control is enabled; defaults to GitLab `external_url`. | `headers` | Specify any additional http headers that should be sent to the client with each response. -| `http_proxy` | Configure GitLab Pages to use an HTTP Proxy to mediate traffic between Pages and GitLab. Sets an environment variable `http_proxy` when starting Pages daemon. | `inplace_chroot` | On [systems that don't support bind-mounts](index.md#additional-configuration-for-docker-container), this instructs GitLab Pages to chroot into its `pages_path` directory. Some caveats exist when using inplace chroot; refer to the GitLab Pages [README](https://gitlab.com/gitlab-org/gitlab-pages/blob/master/README.md#caveats) for more information. | `insecure_ciphers` | Use default list of cipher suites, may contain insecure ones like 3DES and RC4. | `internal_gitlab_server` | Internal GitLab server address used exclusively for API requests. Useful if you want to send that traffic over an internal load balancer. Defaults to GitLab `external_url`. @@ -226,6 +225,8 @@ control over how the Pages daemon runs and serves content in your environment. | `tls_max_version` | Specifies the maximum SSL/TLS version ("ssl3", "tls1.0", "tls1.1" or "tls1.2"). | `tls_min_version` | Specifies the minimum SSL/TLS version ("ssl3", "tls1.0", "tls1.1" or "tls1.2"). | `use_http2` | Enable HTTP2 support. +| **gitlab_pages['env'][]** | | +| `http_proxy` | Configure GitLab Pages to use an HTTP Proxy to mediate traffic between Pages and GitLab. Sets an environment variable `http_proxy` when starting Pages daemon. | **gitlab_rails[]** | | | `pages_domain_verification_cron_worker` | Schedule for verifying custom GitLab Pages domains. | `pages_domain_ssl_renewal_cron_worker` | Schedule for obtaining and renewing SSL certificates through Let's Encrypt for GitLab Pages domains. @@ -400,7 +401,7 @@ pages: 1. Configure in `/etc/gitlab/gitlab.rb`: ```ruby - gitlab_pages['http_proxy'] = 'http://example:8080' + gitlab_pages['env']['http_proxy'] = 'http://example:8080' ``` 1. [Reconfigure GitLab](../restart_gitlab.md#omnibus-gitlab-reconfigure) for the changes to take effect. diff --git a/doc/administration/raketasks/maintenance.md b/doc/administration/raketasks/maintenance.md index 0b332087080..78094f00a43 100644 --- a/doc/administration/raketasks/maintenance.md +++ b/doc/administration/raketasks/maintenance.md @@ -51,6 +51,40 @@ Hooks: /home/git/gitlab-shell/hooks/ Git: /usr/bin/git ``` +## Show GitLab license information **(STARTER ONLY)** + +> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/20501) in GitLab Starter 12.6. + +This command shows information about your [GitLab license](../../user/admin_area/license.md) and +how many seats are used. It is only available on GitLab Enterprise +installations: a license cannot be installed into GitLab Community Edition. + +These may be useful when raising tickets with Support, or for programmatically +checking your license parameters. + +**Omnibus Installation** + +```shell +sudo gitlab-rake gitlab:license:info +``` + +**Source Installation** + +```shell +bundle exec rake gitlab:license:info RAILS_ENV=production +``` + +Example output: + +```plaintext +Today's Date: 2020-02-29 +Current User Count: 30 +Max Historical Count: 30 +Max Users in License: 40 +License valid from: 2019-11-29 to 2020-11-28 +Email associated with license: user@example.com +``` + ## Check GitLab configuration The `gitlab:check` Rake task runs the following Rake tasks: diff --git a/doc/administration/reference_architectures/1k_users.md b/doc/administration/reference_architectures/1k_users.md index 6e55092e64b..34805a8ac68 100644 --- a/doc/administration/reference_architectures/1k_users.md +++ b/doc/administration/reference_architectures/1k_users.md @@ -7,11 +7,18 @@ For a full list of reference architectures, see > - **Supported users (approximate):** 1,000 > - **High Availability:** False -| Users | Configuration([8](#footnotes)) | GCP | AWS | Azure | -|-------|--------------------------------|-----------------|----------------------|------------------------| -| 100 | 2 vCPU, 7.2GB Memory | `n1-standard-2` | `m5.large` | D2s v3 | -| 500 | 4 vCPU, 15GB Memory | `n1-standard-4` | `m5.xlarge` | D4s v3 | -| 1000 | 8 vCPU, 30GB Memory | `n1-standard-8` | `m5.2xlarge` | D8s v3 | +| Users | Configuration([8](#footnotes)) | GCP | AWS | Azure | +|-------|------------------------------------|----------------|---------------------|------------------------| +| 500 | 4 vCPU, 3.6GB Memory | `n1-highcpu-4` | `c5.xlarge` | F4s v2 | +| 1000 | 8 vCPU, 7.2GB Memory | `n1-highcpu-8` | `c5.2xlarge` | F8s v2 | + +In addition to the above, we recommend having at least +2GB of swap on your server, even if you currently have +enough available RAM. Having swap will help reduce the chance of errors occurring +if your available memory changes. We also recommend +configuring the kernel's swappiness setting +to a low value like `10` to make the most of your RAM while still having the swap +available when needed. For situations where you need to serve up to 1,000 users, a single-node solution with [frequent backups](index.md#automated-backups-core-only) is appropriate diff --git a/doc/administration/reference_architectures/index.md b/doc/administration/reference_architectures/index.md index 3c013f00743..623d7f3f776 100644 --- a/doc/administration/reference_architectures/index.md +++ b/doc/administration/reference_architectures/index.md @@ -171,6 +171,25 @@ column. | [GitLab application services](../../development/architecture.md#unicorn)([1](#footnotes)) | Puma/Unicorn, Workhorse, GitLab Shell - serves front-end requests (UI, API, Git over HTTP/SSH) | [GitLab app scaling configuration](../high_availability/gitlab.md) | Yes | | [Prometheus](../../development/architecture.md#prometheus) and [Grafana](../../development/architecture.md#grafana) | GitLab environment monitoring | [Monitoring node for scaling](../high_availability/monitoring_node.md) | Yes | +### Configuring select components with Cloud Native Helm + +We also provide [Helm charts](https://docs.gitlab.com/charts/) as a Cloud Native installation +method for GitLab. For the reference architectures, select components can be set up in this +way as an alternative if so desired. + +For these kind of setups we support using the charts in an [advanced configuration](https://docs.gitlab.com/charts/#advanced-configuration) +where stateful backend components, such as the database or Gitaly, are run externally - either +via Omnibus or reputable third party services. Note that we don't currently support running the +stateful components via Helm _at large scales_. + +When designing these environments you should refer to the respective [Reference Architecture](#available-reference-architectures) +above for guidance on sizing. Components run via Helm would be similarly scaled to their Omnibus +specs, only translated into Kubernetes resources. + +For example, if you were to set up a 50k installation with the Rails nodes being run in Helm, +then the same amount of resources as given for Omnibus should be given to the Kubernetes +cluster with the Rails nodes broken down into a number of smaller Pods across that cluster. + ## Footnotes 1. In our architectures we run each GitLab Rails node using the Puma webserver diff --git a/doc/api/jobs.md b/doc/api/jobs.md index 3dc1ec9966c..4dc29fc897d 100644 --- a/doc/api/jobs.md +++ b/doc/api/jobs.md @@ -269,6 +269,90 @@ Example of response ] ``` +## List pipeline bridges + +Get a list of bridge jobs for a pipeline. + +```plaintext +GET /projects/:id/pipelines/:pipeline_id/bridges +``` + +| Attribute | Type | Required | Description | +|---------------|--------------------------------|----------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `id` | integer/string | yes | ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user. | +| `pipeline_id` | integer | yes | ID of a pipeline. | +| `scope` | string **or** array of strings | no | Scope of jobs to show. Either one of or an array of the following: `created`, `pending`, `running`, `failed`, `success`, `canceled`, `skipped`, or `manual`. All jobs are returned if `scope` is not provided. | + +```shell +curl --header "PRIVATE-TOKEN: " 'https://gitlab.example.com/api/v4/projects/1/pipelines/6/bridges?scope[]=pending&scope[]=running' +``` + +Example of response + +```json +[ + { + "commit": { + "author_email": "admin@example.com", + "author_name": "Administrator", + "created_at": "2015-12-24T16:51:14.000+01:00", + "id": "0ff3ae198f8601a285adcf5c0fff204ee6fba5fd", + "message": "Test the CI integration.", + "short_id": "0ff3ae19", + "title": "Test the CI integration." + }, + "coverage": null, + "allow_failure": false, + "created_at": "2015-12-24T15:51:21.802Z", + "started_at": "2015-12-24T17:54:27.722Z", + "finished_at": "2015-12-24T17:58:27.895Z", + "duration": 240, + "id": 7, + "name": "teaspoon", + "pipeline": { + "id": 6, + "ref": "master", + "sha": "0ff3ae198f8601a285adcf5c0fff204ee6fba5fd", + "status": "pending", + "created_at": "2015-12-24T15:50:16.123Z", + "updated_at": "2015-12-24T18:00:44.432Z", + "web_url": "https://example.com/foo/bar/pipelines/6" + }, + "ref": "master", + "stage": "test", + "status": "pending", + "tag": false, + "web_url": "https://example.com/foo/bar/-/jobs/7", + "user": { + "id": 1, + "name": "Administrator", + "username": "root", + "state": "active", + "avatar_url": "http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon", + "web_url": "http://gitlab.dev/root", + "created_at": "2015-12-21T13:14:24.077Z", + "bio": null, + "location": null, + "public_email": "", + "skype": "", + "linkedin": "", + "twitter": "", + "website_url": "", + "organization": "" + }, + "downstream_pipeline": { + "id": 5, + "sha": "f62a4b2fb89754372a346f24659212eb8da13601", + "ref": "master", + "status": "pending", + "created_at": "2015-12-24T17:54:27.722Z", + "updated_at": "2015-12-24T17:58:27.896Z", + "web_url": "https://example.com/diaspora/diaspora-client/pipelines/5" + } + } +] +``` + ## Get a single job Get a single job of a project diff --git a/doc/ci/variables/README.md b/doc/ci/variables/README.md index 59a96aa111d..541e739082f 100644 --- a/doc/ci/variables/README.md +++ b/doc/ci/variables/README.md @@ -474,8 +474,8 @@ Feature.enable(:instance_variables_ui) ## Inherit environment variables -> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/22638) in GitLab 13.0. -> - It's deployed behind a feature flag (`ci_dependency_variables`), disabled by default. +> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/22638) in GitLab 13.0 behind a disabled [feature flag](../../administration/feature_flags.md): `ci_dependency_variables`. +> - [Feature flag removed](https://gitlab.com/gitlab-org/gitlab/-/issues/217834) in GitLab 13.1. You can inherit environment variables from dependent jobs. @@ -520,25 +520,6 @@ deploy: artifacts: true ``` -### Enable inherited environment variables **(CORE ONLY)** - -The Inherited Environment Variables feature is under development and not ready for production use. It is -deployed behind a feature flag that is **disabled by default**. -[GitLab administrators with access to the GitLab Rails console](../../administration/feature_flags.md) -can enable it for your instance. - -To enable it: - -```ruby -Feature.enable(:ci_dependency_variables) -``` - -To disable it: - -```ruby -Feature.disable(:ci_dependency_variables) -``` - ## Priority of environment variables Variables of different types can take precedence over other diff --git a/doc/install/requirements.md b/doc/install/requirements.md index 23c61f54375..0673f3e7ea3 100644 --- a/doc/install/requirements.md +++ b/doc/install/requirements.md @@ -92,42 +92,31 @@ NOTE: **Note:** Since file system performance may affect GitLab's overall perfor ### CPU -This is the recommended minimum hardware for a handful of example GitLab user base sizes. Your exact needs may be more, depending on your workload. Your workload is influenced by factors such as - but not limited to - how active your users are, how much automation you use, mirroring, and repository/change size. +CPU requirements are dependent on the number of users and expected workload. Your exact needs may be more, depending on your workload. Your workload is influenced by factors such as - but not limited to - how active your users are, how much automation you use, mirroring, and repo/change size. -- 1 core supports up to 100 users but the application can be a bit slower due to having all workers and background jobs running on the same core -- **2 cores** is the **recommended** minimum number of cores and supports up to 100 users -- 4 cores support up to 500 users -- 8 cores support up to 1,000 users -- 32 cores support up to 5,000 users +The following is the recommended minimum CPU hardware guidance for a handful of example GitLab user base sizes. + +- **4 cores** is the **recommended** minimum number of cores and supports up to 500 users +- 8 cores supports up to 1000 users - More users? Consult the [reference architectures page](../administration/reference_architectures/index.md) ### Memory -This is the recommended minimum hardware for a handful of example GitLab user base sizes. Your exact needs may be more, depending on your workload. Your workload is influenced by factors such as - but not limited to - how active your users are, how much automation you use, mirroring, and the size of repositories as well as changes/commits. +Memory requirements are dependent on the number of users and expected workload. Your exact needs may be more, depending on your workload. Your workload is influenced by factors such as - but not limited to - how active your users are, how much automation you use, mirroring, and repo/change size. -You need at least 8GB of addressable memory (RAM + swap) to install and use GitLab! -The operating system and any other running applications will also be using memory -so keep in mind that you need at least 4GB available before running GitLab. With -less memory GitLab will give strange errors during the reconfigure run and 500 -errors during usage. +The following is the recommended minimum Memory hardware guidance for a handful of example GitLab user base sizes. -- 4GB RAM + 4GB swap supports up to 100 users but it will be very slow -- **8GB RAM** is the **recommended** minimum memory size for all installations and supports up to 100 users -- 16GB RAM supports up to 500 users -- 32GB RAM supports up to 1,000 users -- 128GB RAM supports up to 5,000 users +- **4GB RAM** is the **required** minimum memory size and supports up to 500 users + - Our [Memory Team](https://about.gitlab.com/handbook/engineering/development/enablement/memory/) is working to reduce the memory requirement. +- 8GB RAM supports up to 1000 users - More users? Consult the [reference architectures page](../administration/reference_architectures/index.md) -We recommend having at least [2GB of swap on your server](https://askubuntu.com/a/505344/310789), even if you currently have -enough available RAM. Having swap will help reduce the chance of errors occurring -if your available memory changes. We also recommend [configuring the kernel's swappiness setting](https://askubuntu.com/a/103916) +In addition to the above, we generally recommend having at least 2GB of swap on your server, +even if you currently have enough available RAM. Having swap will help reduce the chance of errors occurring +if your available memory changes. We also recommend configuring the kernel's swappiness setting to a low value like `10` to make the most of your RAM while still having the swap available when needed. -Our [Memory Team](https://about.gitlab.com/handbook/engineering/development/enablement/memory/) is actively working to reduce the memory requirement. - -NOTE: **Note:** The 25 workers of Sidekiq will show up as separate processes in your process overview (such as `top` or `htop`). However, they share the same RAM allocation, as Sidekiq is a multi-threaded application. See the section below about Unicorn workers for information about how many you need for those. - ## Database PostgreSQL is the only supported database, which is bundled with the Omnibus GitLab package. @@ -168,22 +157,6 @@ If you're using [GitLab Geo](../administration/geo/replication/index.md): [can be enabled](https://www.postgresql.org/docs/11/sql-createextension.html) using a PostgreSQL super user. -## Unicorn Workers - -For most instances we recommend using: (CPU cores * 1.5) + 1 = Unicorn workers. -For example a node with 4 cores would have 7 Unicorn workers. - -For all machines that have 2GB and up we recommend a minimum of three Unicorn workers. -If you have a 1GB machine we recommend to configure only two Unicorn workers to prevent excessive -swapping. - -As long as you have enough available CPU and memory capacity, it's okay to increase the number of -Unicorn workers and this will usually help to reduce the response time of the applications and -increase the ability to handle parallel requests. - -To change the Unicorn workers when you have the Omnibus package (which defaults to the -recommendation above) please see [the Unicorn settings in the Omnibus GitLab documentation](https://docs.gitlab.com/omnibus/settings/unicorn.html). - ## Puma settings The recommended settings for Puma are determined by the infrastructure on which it's running. @@ -219,6 +192,22 @@ of [legacy Rugged code](../development/gitaly.md#legacy-rugged-code). higher, due to how [Ruby MRI multi-threading](https://en.wikipedia.org/wiki/Global_interpreter_lock) works. +## Unicorn Workers + +For most instances we recommend using: (CPU cores * 1.5) + 1 = Unicorn workers. +For example a node with 4 cores would have 7 Unicorn workers. + +For all machines that have 2GB and up we recommend a minimum of three Unicorn workers. +If you have a 1GB machine we recommend to configure only two Unicorn workers to prevent excessive +swapping. + +As long as you have enough available CPU and memory capacity, it's okay to increase the number of +Unicorn workers and this will usually help to reduce the response time of the applications and +increase the ability to handle parallel requests. + +To change the Unicorn workers when you have the Omnibus package (which defaults to the +recommendation above) please see [the Unicorn settings in the Omnibus GitLab documentation](https://docs.gitlab.com/omnibus/settings/unicorn.html). + ## Redis and Sidekiq Redis stores all user sessions and the background task queue. diff --git a/doc/user/admin_area/settings/img/rate_limit_on_issues_creation.png b/doc/user/admin_area/settings/img/rate_limit_on_issues_creation.png deleted file mode 100644 index 5aa9b95f835..00000000000 Binary files a/doc/user/admin_area/settings/img/rate_limit_on_issues_creation.png and /dev/null differ diff --git a/doc/user/admin_area/settings/img/rate_limit_on_issues_creation_v13_1.png b/doc/user/admin_area/settings/img/rate_limit_on_issues_creation_v13_1.png new file mode 100644 index 00000000000..9edc916c7fb Binary files /dev/null and b/doc/user/admin_area/settings/img/rate_limit_on_issues_creation_v13_1.png differ diff --git a/doc/user/admin_area/settings/rate_limit_on_issues_creation.md b/doc/user/admin_area/settings/rate_limit_on_issues_creation.md index dc23865e730..bae60aba15f 100644 --- a/doc/user/admin_area/settings/rate_limit_on_issues_creation.md +++ b/doc/user/admin_area/settings/rate_limit_on_issues_creation.md @@ -1,5 +1,8 @@ --- type: reference +stage: Plan +group: Project Management +info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#designated-technical-writers --- # Rate limits on issue creation @@ -14,7 +17,7 @@ For example, requests using the [Projects::IssuesController#create](https://gitlab.com/gitlab-org/gitlab/raw/master/app/controllers/projects/issues_controller.rb) action exceeding a rate of 300 per minute are blocked. Access to the endpoint is allowed after one minute. -![Rate limits on issues creation](img/rate_limit_on_issues_creation.png) +![Rate limits on issues creation](img/rate_limit_on_issues_creation_v13_1.png) This limit is: diff --git a/doc/user/application_security/vulnerabilities/img/standalone_vulnerability_page_download_patch_button_v13_1.png b/doc/user/application_security/vulnerabilities/img/standalone_vulnerability_page_download_patch_button_v13_1.png new file mode 100644 index 00000000000..b925c342a11 Binary files /dev/null and b/doc/user/application_security/vulnerabilities/img/standalone_vulnerability_page_download_patch_button_v13_1.png differ diff --git a/doc/user/application_security/vulnerabilities/img/standalone_vulnerability_page_merge_request_button_dropdown_v13_1.png b/doc/user/application_security/vulnerabilities/img/standalone_vulnerability_page_merge_request_button_dropdown_v13_1.png new file mode 100644 index 00000000000..2063762d3eb Binary files /dev/null and b/doc/user/application_security/vulnerabilities/img/standalone_vulnerability_page_merge_request_button_dropdown_v13_1.png differ diff --git a/doc/user/application_security/vulnerabilities/img/standalone_vulnerability_page_merge_request_button_v13_1.png b/doc/user/application_security/vulnerabilities/img/standalone_vulnerability_page_merge_request_button_v13_1.png new file mode 100644 index 00000000000..ee4e97bcafe Binary files /dev/null and b/doc/user/application_security/vulnerabilities/img/standalone_vulnerability_page_merge_request_button_v13_1.png differ diff --git a/doc/user/application_security/vulnerabilities/index.md b/doc/user/application_security/vulnerabilities/index.md index 6c365dd6dfc..b3128e49980 100644 --- a/doc/user/application_security/vulnerabilities/index.md +++ b/doc/user/application_security/vulnerabilities/index.md @@ -55,14 +55,19 @@ generates for you. GitLab supports the following scanners: is only available for Node.js projects managed with `yarn`. - [Container Scanning](../container_scanning/index.md). +When an automatic solution is available, the button in the header will show "Resolve with merge request": + +![Resolve with Merge Request button](img/standalone_vulnerability_page_merge_request_button_v13_1.png) + +Selecting the button will create a merge request with the automatic solution. + ### Manually applying a suggested patch -To apply a patch automatically generated by GitLab to fix a vulnerability: +To manually apply the patch that was generated by GitLab for a vulnerability, select the dropdown arrow on the "Resolve +with merge request" button, then select the "Download patch to resolve" option: -1. Open the issue created in [Create issue](#creating-an-issue-for-a-vulnerability). -1. In the **Issue description**, scroll to **Solution** and download the linked patch file. -1. Ensure your local project has the same commit checked out that was used to generate the patch. -1. Run `git apply remediation.patch` to apply the patch. -1. Verify and commit the changes to your branch. +![Resolve with Merge Request button dropdown](img/standalone_vulnerability_page_merge_request_button_dropdown_v13_1.png) -![Apply patch for dependency scanning](../img/vulnerability_solution.png) +This will change the button text to "Download patch to resolve". Click on it to download the patch: + +![Download patch button](img/standalone_vulnerability_page_download_patch_button_v13_1.png) diff --git a/doc/user/group/settings/img/export_panel.png b/doc/user/group/settings/img/export_panel.png deleted file mode 100644 index 2a987f04e35..00000000000 Binary files a/doc/user/group/settings/img/export_panel.png and /dev/null differ diff --git a/doc/user/group/settings/img/export_panel_v13_0.png b/doc/user/group/settings/img/export_panel_v13_0.png new file mode 100644 index 00000000000..36549e1f3f5 Binary files /dev/null and b/doc/user/group/settings/img/export_panel_v13_0.png differ diff --git a/doc/user/group/settings/img/import_panel_v13_1.png b/doc/user/group/settings/img/import_panel_v13_1.png new file mode 100644 index 00000000000..ce2eb579446 Binary files /dev/null and b/doc/user/group/settings/img/import_panel_v13_1.png differ diff --git a/doc/user/group/settings/img/new_group_navigation_v13_1.png b/doc/user/group/settings/img/new_group_navigation_v13_1.png new file mode 100644 index 00000000000..98d45a694b6 Binary files /dev/null and b/doc/user/group/settings/img/new_group_navigation_v13_1.png differ diff --git a/doc/user/group/settings/import_export.md b/doc/user/group/settings/import_export.md index 229e2892eb9..cca918d44f3 100644 --- a/doc/user/group/settings/import_export.md +++ b/doc/user/group/settings/import_export.md @@ -67,7 +67,7 @@ For more details on the specific data persisted in a group export, see the 1. In the **Advanced** section, click the **Export Group** button. - ![Export group panel](img/export_panel.png) + ![Export group panel](img/export_panel_v13_0.png) 1. Once the export is generated, you should receive an e-mail with a link to the [exported contents](#exported-contents) in a compressed tar archive, with contents in JSON format. @@ -85,6 +85,27 @@ You can export groups from the [Community Edition to the Enterprise Edition](htt If you're exporting a group from the Enterprise Edition to the Community Edition, you may lose data that is retained only in the Enterprise Edition. For more information, see [downgrading from EE to CE](../../../README.md). +## Importing the group + +1. Navigate to the New Group page, either via the `+` button in the top navigation bar, or the **New subgroup** button +on an existing group's page. + + ![Navigation paths to create a new group](img/new_group_navigation_v13_1.png) + +1. On the New Group page, select the **Import group** tab. + + ![Fill in group details](img/import_panel_v13_1.png) + +1. Enter your group name. + +1. Accept or modify the associated group URL. + +1. Click **Choose file** + +1. Select the file that you exported in the [exporting a group](#exporting-a-group) section. + +1. Click **Import group** to begin importing. Your newly imported group page will appear shortly. + ## Version history GitLab can import bundles that were exported from a different GitLab deployment. @@ -106,3 +127,4 @@ To help avoid abuse, users are rate limited to: | ---------------- | ---------------------------------------- | | Export | 30 groups every 5 minutes | | Download export | 10 downloads per group every 10 minutes | +| Import | 30 groups every 5 minutes | diff --git a/doc/user/project/integrations/img/slack_configuration.png b/doc/user/project/integrations/img/slack_configuration.png deleted file mode 100644 index 4d5e6ae7856..00000000000 Binary files a/doc/user/project/integrations/img/slack_configuration.png and /dev/null differ diff --git a/doc/user/project/integrations/slack.md b/doc/user/project/integrations/slack.md index 419340c14ef..79fc8eceddf 100644 --- a/doc/user/project/integrations/slack.md +++ b/doc/user/project/integrations/slack.md @@ -23,7 +23,23 @@ The Slack Notifications Service allows your GitLab project to send events (e.g. Your Slack team will now start receiving GitLab event notifications as configured. -![Slack configuration](img/slack_configuration.png) +### Triggers available for Slack notifications + +The following triggers are available for Slack notifications: + +- **Push**: Triggered by a push to the repository. +- **Issue**: Triggered when an issue is created, updated, or closed. +- **Confidential issue**: Triggered when a confidential issue is created, + updated, or closed. +- **Merge request**: Triggered when a merge request is created, updated, or + merged. +- **Note**: Triggered when someone adds a comment. +- **Confidential note**: Triggered when someone adds a confidential note. +- **Tag push**: Triggered when a new tag is pushed to the repository. +- **Pipeline**: Triggered when a pipeline status changes. +- **Wiki page**: Triggered when a wiki page is created or updated. +- **Deployment**: Triggered when a deployment finishes. +- **Alert**: Triggered when a new, unique alert is recorded. ## Troubleshooting diff --git a/doc/user/project/operations/alert_management.md b/doc/user/project/operations/alert_management.md index bb4e7b44902..2c5593a8df2 100644 --- a/doc/user/project/operations/alert_management.md +++ b/doc/user/project/operations/alert_management.md @@ -158,3 +158,11 @@ After completing their portion of investigating or fixing the alert, users can unassign their account from the alert when their role is complete. The [alerts status](#alert-management-statuses) can be updated to reflect if the alert has been resolved. + +### Slack Notifications + +> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/216326) in GitLab 13.1. + +You can be alerted via a Slack message when a new alert has been received. + +See the [Slack Notifications Service docs](../integrations/slack.md) for information on how to set this up. diff --git a/lib/api/entities/bridge.rb b/lib/api/entities/bridge.rb new file mode 100644 index 00000000000..8f0ee69399a --- /dev/null +++ b/lib/api/entities/bridge.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +module API + module Entities + class Bridge < Entities::JobBasic + expose :downstream_pipeline, with: Entities::PipelineBasic + end + end +end diff --git a/lib/api/jobs.rb b/lib/api/jobs.rb index 59f0dbe8a9b..61a7fc107ef 100644 --- a/lib/api/jobs.rb +++ b/lib/api/jobs.rb @@ -70,6 +70,32 @@ module API end # rubocop: enable CodeReuse/ActiveRecord + desc 'Get pipeline bridge jobs' do + success Entities::Bridge + end + params do + requires :pipeline_id, type: Integer, desc: 'The pipeline ID' + use :optional_scope + use :pagination + end + # rubocop: disable CodeReuse/ActiveRecord + get ':id/pipelines/:pipeline_id/bridges' do + authorize!(:read_build, user_project) + pipeline = user_project.ci_pipelines.find(params[:pipeline_id]) + authorize!(:read_pipeline, pipeline) + + bridges = pipeline.bridges + bridges = filter_builds(bridges, params[:scope]) + bridges = bridges.preload( + :metadata, + downstream_pipeline: [project: [:route, { namespace: :route }]], + project: [:namespace] + ) + + present paginate(bridges), with: Entities::Bridge + end + # rubocop: enable CodeReuse/ActiveRecord + desc 'Get a specific job of a project' do success Entities::Job end diff --git a/lib/gitlab/ci/templates/Verify/FailFast.gitlab-ci.yml b/lib/gitlab/ci/templates/Verify/FailFast.gitlab-ci.yml new file mode 100644 index 00000000000..77a1b57d92f --- /dev/null +++ b/lib/gitlab/ci/templates/Verify/FailFast.gitlab-ci.yml @@ -0,0 +1,17 @@ +rspec-rails-modified-path-specs: + stage: .pre + rules: + - if: $CI_MERGE_REQUEST_EVENT_TYPE == "merged_result" || $CI_MERGE_REQUEST_EVENT_TYPE == "merge_train" + changes: ["**/*.rb"] + script: + - gem install test_file_finder + - spec_files=$(tff $(git diff --name-only "$CI_MERGE_REQUEST_TARGET_BRANCH_SHA..$CI_MERGE_REQUEST_SOURCE_BRANCH_SHA")) + - | + if [ -n "$spec_files" ] + then + bundle install + bundle exec rspec -- $spec_files + else + echo "No relevant spec files found by tff" + exit 0 + fi diff --git a/lib/gitlab/code_navigation_path.rb b/lib/gitlab/code_navigation_path.rb index 57aeb6c4fb2..faf623faccf 100644 --- a/lib/gitlab/code_navigation_path.rb +++ b/lib/gitlab/code_navigation_path.rb @@ -13,7 +13,7 @@ module Gitlab end def full_json_path_for(path) - return if Feature.disabled?(:code_navigation, project) + return unless Feature.enabled?(:code_navigation, project, default_enabled: true) return unless build raw_project_job_artifacts_path(project, build, path: "lsif/#{path}.json", file_type: :lsif) diff --git a/lib/gitlab/instrumentation/redis.rb b/lib/gitlab/instrumentation/redis.rb index 3ed1b18281e..82b4701872f 100644 --- a/lib/gitlab/instrumentation/redis.rb +++ b/lib/gitlab/instrumentation/redis.rb @@ -15,8 +15,24 @@ module Gitlab QUERY_TIME_BUCKETS = [0.001, 0.0025, 0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1, 2].freeze class << self + include ::Gitlab::Instrumentation::RedisPayload + + def storage_key + nil + end + + def known_payload_keys + super + STORAGES.flat_map(&:known_payload_keys) + end + + def payload + super.merge(*STORAGES.flat_map(&:payload)) + end + def detail_store - STORAGES.flat_map(&:detail_store) + STORAGES.flat_map do |storage| + storage.detail_store.map { |details| details.merge(storage: storage.name.demodulize) } + end end %i[get_request_count query_time read_bytes write_bytes].each do |method| diff --git a/lib/gitlab/instrumentation/redis_base.rb b/lib/gitlab/instrumentation/redis_base.rb index a8fb8f5076b..012543e1645 100644 --- a/lib/gitlab/instrumentation/redis_base.rb +++ b/lib/gitlab/instrumentation/redis_base.rb @@ -7,11 +7,12 @@ module Gitlab class RedisBase class << self include ::Gitlab::Utils::StrongMemoize + include ::Gitlab::Instrumentation::RedisPayload # TODO: To be used by https://gitlab.com/gitlab-com/gl-infra/scalability/-/issues/395 # as a 'label' alias. def storage_key - self.name.underscore + self.name.demodulize.underscore end def add_duration(duration) diff --git a/lib/gitlab/instrumentation/redis_payload.rb b/lib/gitlab/instrumentation/redis_payload.rb new file mode 100644 index 00000000000..69aafffd124 --- /dev/null +++ b/lib/gitlab/instrumentation/redis_payload.rb @@ -0,0 +1,37 @@ +# frozen_string_literal: true + +module Gitlab + module Instrumentation + module RedisPayload + include ::Gitlab::Utils::StrongMemoize + + # Fetches payload keys from the lazy payload (this avoids + # unnecessary processing of the values). + def known_payload_keys + to_lazy_payload.keys + end + + def payload + to_lazy_payload.transform_values do |value| + result = value.call + result if result > 0 + end.compact + end + + private + + def to_lazy_payload + strong_memoize(:to_lazy_payload) do + key_prefix = storage_key ? "redis_#{storage_key}" : 'redis' + + { + "#{key_prefix}_calls": -> { get_request_count }, + "#{key_prefix}_duration_s": -> { query_time }, + "#{key_prefix}_read_bytes": -> { read_bytes }, + "#{key_prefix}_write_bytes": -> { write_bytes } + }.symbolize_keys + end + end + end + end +end diff --git a/lib/gitlab/instrumentation_helper.rb b/lib/gitlab/instrumentation_helper.rb index 5cd0eacbb93..3a29d2e7efa 100644 --- a/lib/gitlab/instrumentation_helper.rb +++ b/lib/gitlab/instrumentation_helper.rb @@ -4,26 +4,22 @@ module Gitlab module InstrumentationHelper extend self - KEYS = %i( - gitaly_calls - gitaly_duration_s - rugged_calls - rugged_duration_s - redis_calls - redis_duration_s - redis_read_bytes - redis_write_bytes - elasticsearch_calls - elasticsearch_duration_s - ).freeze - DURATION_PRECISION = 6 # microseconds + def keys + @keys ||= [:gitaly_calls, + :gitaly_duration_s, + :rugged_calls, + :rugged_duration_s, + :elasticsearch_calls, + :elasticsearch_duration_s, + *::Gitlab::Instrumentation::Redis.known_payload_keys] + end + def add_instrumentation_data(payload) instrument_gitaly(payload) instrument_rugged(payload) instrument_redis(payload) - instrument_redis_bytes(payload) instrument_elasticsearch(payload) end @@ -46,22 +42,7 @@ module Gitlab end def instrument_redis(payload) - redis_calls = Gitlab::Instrumentation::Redis.get_request_count - - return if redis_calls == 0 - - payload[:redis_calls] = redis_calls - payload[:redis_duration_s] = Gitlab::Instrumentation::Redis.query_time - end - - def instrument_redis_bytes(payload) - redis_read_bytes = Gitlab::Instrumentation::Redis.read_bytes - redis_write_bytes = Gitlab::Instrumentation::Redis.write_bytes - - return if redis_read_bytes == 0 && redis_write_bytes == 0 - - payload[:redis_read_bytes] = redis_read_bytes - payload[:redis_write_bytes] = redis_write_bytes + payload.merge! ::Gitlab::Instrumentation::Redis.payload end def instrument_elasticsearch(payload) diff --git a/lib/gitlab/sidekiq_logging/structured_logger.rb b/lib/gitlab/sidekiq_logging/structured_logger.rb index a79c9cff35c..eb845c5ff8d 100644 --- a/lib/gitlab/sidekiq_logging/structured_logger.rb +++ b/lib/gitlab/sidekiq_logging/structured_logger.rb @@ -27,7 +27,7 @@ module Gitlab private def add_instrumentation_keys!(job, output_payload) - output_payload.merge!(job.slice(*::Gitlab::InstrumentationHelper::KEYS)) + output_payload.merge!(job.slice(*::Gitlab::InstrumentationHelper.keys)) end def add_logging_extras!(job, output_payload) diff --git a/lib/peek/views/redis_detailed.rb b/lib/peek/views/redis_detailed.rb index d958f9fec9a..44ec0ec0f68 100644 --- a/lib/peek/views/redis_detailed.rb +++ b/lib/peek/views/redis_detailed.rb @@ -16,7 +16,8 @@ module Peek private def format_call_details(call) - super.merge(cmd: format_command(call[:cmd])) + super.merge(cmd: format_command(call[:cmd]), + instance: call[:storage]) end def format_command(cmd) diff --git a/lib/system_check/app/ci_jwt_signing_key_check.rb b/lib/system_check/app/ci_jwt_signing_key_check.rb new file mode 100644 index 00000000000..2777daf0123 --- /dev/null +++ b/lib/system_check/app/ci_jwt_signing_key_check.rb @@ -0,0 +1,30 @@ +# frozen_string_literal: true + +module SystemCheck + module App + class CiJwtSigningKeyCheck < SystemCheck::BaseCheck + set_name 'Valid CI JWT signing key?' + + def check? + key_data = Rails.application.secrets.ci_jwt_signing_key + return false unless key_data.present? + + OpenSSL::PKey::RSA.new(key_data) + + true + rescue OpenSSL::PKey::RSAError + false + end + + def show_error + $stdout.puts ' Rails.application.secrets.ci_jwt_signing_key is missing or not a valid RSA key.'.color(:red) + $stdout.puts ' CI_JOB_JWT will not be generated for CI jobs.'.color(:red) + + for_more_information( + 'doc/ci/variables/predefined_variables.md', + 'doc/ci/examples/authenticating-with-hashicorp-vault/index.md' + ) + end + end + end +end diff --git a/lib/system_check/rake_task/app_task.rb b/lib/system_check/rake_task/app_task.rb index 99c93edd12d..571283165dc 100644 --- a/lib/system_check/rake_task/app_task.rb +++ b/lib/system_check/rake_task/app_task.rb @@ -33,7 +33,8 @@ module SystemCheck SystemCheck::App::ActiveUsersCheck, SystemCheck::App::AuthorizedKeysPermissionCheck, SystemCheck::App::HashedStorageEnabledCheck, - SystemCheck::App::HashedStorageAllProjectsCheck + SystemCheck::App::HashedStorageAllProjectsCheck, + SystemCheck::App::CiJwtSigningKeyCheck ] end end diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 3adc6bd37f2..91c7c1527f0 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -2233,7 +2233,7 @@ msgstr "" msgid "An error occurred while adding formatted title for epic" msgstr "" -msgid "An error occurred while checking group path" +msgid "An error occurred while checking group path. Please refresh and try again." msgstr "" msgid "An error occurred while committing your changes." @@ -3002,6 +3002,9 @@ msgstr "" msgid "Assignee(s)" msgstr "" +msgid "Assignees" +msgstr "" + msgid "Assigns %{assignee_users_sentence}." msgstr "" @@ -11414,6 +11417,33 @@ msgstr "" msgid "GroupsEmptyState|You can manage your group member’s permissions and access to each project in the group." msgstr "" +msgid "GroupsNew|Contact an administrator to enable options for importing your group." +msgstr "" + +msgid "GroupsNew|Create" +msgstr "" + +msgid "GroupsNew|Create group" +msgstr "" + +msgid "GroupsNew|GitLab group export" +msgstr "" + +msgid "GroupsNew|Import" +msgstr "" + +msgid "GroupsNew|Import group" +msgstr "" + +msgid "GroupsNew|My Awesome Group" +msgstr "" + +msgid "GroupsNew|No import options available" +msgstr "" + +msgid "GroupsNew|To copy a GitLab group between installations, navigate to the group settings page for the original installation, generate an export file, and upload it here." +msgstr "" + msgid "GroupsTree|Are you sure you want to leave the \"%{fullName}\" group?" msgstr "" @@ -19905,6 +19935,9 @@ msgstr "" msgid "Select a timezone" msgstr "" +msgid "Select a weight for the storage new repositories will be placed on." +msgstr "" + msgid "Select all" msgstr "" @@ -19986,9 +20019,6 @@ msgstr "" msgid "Select the branch you want to set as the default for this project. All merge requests and commits will automatically be made against this branch unless you specify a different one." msgstr "" -msgid "Select the configured storage available for new repositories to be placed on." -msgstr "" - msgid "Select the custom project template source group." msgstr "" @@ -23954,6 +23984,9 @@ msgstr "" msgid "Unable to sign you in to the group with SAML due to \"%{reason}\"" msgstr "" +msgid "Unable to suggest a path. Please refresh and try again." +msgstr "" + msgid "Unable to update label prioritization at this time" msgstr "" diff --git a/package.json b/package.json index e06f54fec97..ae50e672dad 100644 --- a/package.json +++ b/package.json @@ -155,6 +155,7 @@ "xterm": "^3.5.0" }, "devDependencies": { + "acorn": "^6.3.0", "@babel/plugin-transform-modules-commonjs": "^7.10.1", "@gitlab/eslint-plugin": "3.1.0", "@vue/test-utils": "^1.0.0-beta.30", @@ -183,7 +184,7 @@ "jasmine-jquery": "^2.1.1", "jest": "^24.1.0", "jest-canvas-mock": "^2.1.2", - "jest-environment-jsdom": "^24.0.0", + "jest-environment-jsdom-sixteen": "^1.0.0", "jest-junit": "^6.3.0", "jest-util": "^24.0.0", "jsdoc": "^3.5.5", diff --git a/qa/qa/page/group/new.rb b/qa/qa/page/group/new.rb index e01b8754d55..88e7121dbe0 100644 --- a/qa/qa/page/group/new.rb +++ b/qa/qa/page/group/new.rb @@ -10,7 +10,7 @@ module QA element :group_description_field, 'text_area :description' # rubocop:disable QA/ElementWithPattern end - view 'app/views/groups/new.html.haml' do + view 'app/views/groups/_new_group_fields.html.haml' do element :create_group_button, "submit _('Create group')" # rubocop:disable QA/ElementWithPattern element :visibility_radios, 'visibility_level:' # rubocop:disable QA/ElementWithPattern end diff --git a/qa/qa/resource/repository/push.rb b/qa/qa/resource/repository/push.rb index 902ae9f3135..1e5399fcc59 100644 --- a/qa/qa/resource/repository/push.rb +++ b/qa/qa/resource/repository/push.rb @@ -60,13 +60,13 @@ module QA repository.use_lfs = use_lfs - username = 'GitLab QA' + name = 'GitLab QA' email = 'root@gitlab.com' if user repository.username = user.username repository.password = user.password - username = user.name + name = user.name email = user.email end @@ -75,7 +75,7 @@ module QA end @output += repository.clone - repository.configure_identity(username, email) + repository.configure_identity(name, email) @output += repository.checkout(branch_name, new_branch: new_branch) diff --git a/rubocop/cop/migration/drop_table.rb b/rubocop/cop/migration/drop_table.rb new file mode 100644 index 00000000000..2a0f57c0c13 --- /dev/null +++ b/rubocop/cop/migration/drop_table.rb @@ -0,0 +1,52 @@ +# frozen_string_literal: true + +require_relative '../../migration_helpers' + +module RuboCop + module Cop + module Migration + # Cop that checks if `drop_table` is called in deployment migrations. + # Calling it in deployment migrations can cause downtimes as there still may be code using the target tables. + class DropTable < RuboCop::Cop::Cop + include MigrationHelpers + + MSG = <<-MESSAGE.delete("\n").squeeze + `drop_table` in deployment migrations requires downtime. + Drop tables in post-deployment migrations instead. + MESSAGE + + def on_def(node) + return unless in_deployment_migration?(node) + + node.each_descendant(:send) do |send_node| + next unless offensible?(send_node) + + add_offense(send_node, location: :selector) + end + end + + private + + def offensible?(node) + drop_table?(node) || drop_table_in_execute?(node) + end + + def drop_table?(node) + node.children[1] == :drop_table + end + + def drop_table_in_execute?(node) + execute?(node) && drop_table_in_execute_sql?(node) + end + + def execute?(node) + node.children[1] == :execute + end + + def drop_table_in_execute_sql?(node) + node.children[2].to_s.match?(/drop\s+table/i) + end + end + end + end +end diff --git a/rubocop/migration_helpers.rb b/rubocop/migration_helpers.rb index 43f6387efd7..355450bbf57 100644 --- a/rubocop/migration_helpers.rb +++ b/rubocop/migration_helpers.rb @@ -23,7 +23,11 @@ module RuboCop # Returns true if the given node originated from the db/migrate directory. def in_migration?(node) - dirname(node).end_with?('db/migrate', 'db/geo/migrate') || in_post_deployment_migration?(node) + in_deployment_migration?(node) || in_post_deployment_migration?(node) + end + + def in_deployment_migration?(node) + dirname(node).end_with?('db/migrate', 'db/geo/migrate') end def in_post_deployment_migration?(node) diff --git a/scripts/rspec_helpers.sh b/scripts/rspec_helpers.sh index 0c9d3505ff3..110567a15be 100644 --- a/scripts/rspec_helpers.sh +++ b/scripts/rspec_helpers.sh @@ -110,3 +110,27 @@ function rspec_paralellized_job() { date } + +function rspec_matched_tests() { + local test_file_count_threshold=20 + local matching_tests_file=${1} + local rspec_opts=${2} + local test_files="$(cat "${matching_tests_file}")" + local test_file_count=$(wc -w "${matching_tests_file}" | awk {'print $1'}) + + if [[ "${test_file_count}" -gt "${test_file_count_threshold}" ]]; then + echo "There are more than ${test_file_count_threshold} FOSS test files matched," + echo "which would take too long to run in this job." + echo "To reduce the likelihood of breaking FOSS pipelines," + echo "please add [RUN AS-IF-FOSS] to the MR title and restart the pipeline." + echo "This would run all as-if-foss jobs in this merge request" + echo "and remove this job from the pipeline." + exit 1 + fi + + if [[ -n $test_files ]]; then + rspec_simple_job "${rspec_opts} ${test_files}" + else + echo "No test files to run" + fi +} diff --git a/spec/features/admin/admin_settings_spec.rb b/spec/features/admin/admin_settings_spec.rb index de37e7b7bf6..2bb93940921 100644 --- a/spec/features/admin/admin_settings_spec.rb +++ b/spec/features/admin/admin_settings_spec.rb @@ -405,7 +405,7 @@ describe 'Admin updates settings', :clean_gitlab_redis_shared_state, :do_not_moc visit network_admin_application_settings_path page.within('.as-issue-limits') do - fill_in 'Max requests per second per user', with: 0 + fill_in 'Max requests per minute per user', with: 0 click_button 'Save changes' end diff --git a/spec/features/groups/import_export/import_file_spec.rb b/spec/features/groups/import_export/import_file_spec.rb new file mode 100644 index 00000000000..577198ef3f1 --- /dev/null +++ b/spec/features/groups/import_export/import_file_spec.rb @@ -0,0 +1,104 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe 'Import/Export - Group Import', :js do + let_it_be(:user) { create(:user) } + let_it_be(:import_path) { "#{Dir.tmpdir}/group_import_spec" } + + before do + allow_next_instance_of(Gitlab::ImportExport) do |import_export| + allow(import_export).to receive(:storage_path).and_return(import_path) + end + + stub_uploads_object_storage(FileUploader) + + gitlab_sign_in(user) + end + + after do + FileUtils.rm_rf(import_path, secure: true) + end + + context 'when the user uploads a valid export file' do + let(:file) { File.join(Rails.root, 'spec', %w[fixtures group_export.tar.gz]) } + + context 'when using the pre-filled path', :sidekiq_inline do + it 'successfully imports the group' do + group_name = 'Test Group Import' + + visit new_group_path + + fill_in :group_name, with: group_name + find('#import-group-tab').click + + expect(page).to have_content 'GitLab group export' + attach_file(file) do + find('.js-filepicker-button').click + end + + expect { click_on 'Import group' }.to change { Group.count }.by 1 + + group = Group.find_by(name: group_name) + + expect(group).not_to be_nil + expect(group.description).to eq 'A voluptate non sequi temporibus quam at.' + expect(group.path).to eq 'test-group-import' + expect(group.import_state.status).to eq GroupImportState.state_machine.states[:finished].value + end + end + + context 'when modifying the pre-filled path' do + it 'successfully imports the group' do + visit new_group_path + + fill_in :group_name, with: 'Test Group Import' + find('#import-group-tab').click + + fill_in :import_group_path, with: 'custom-path' + attach_file(file) do + find('.js-filepicker-button').click + end + + expect { click_on 'Import group' }.to change { Group.count }.by 1 + + group = Group.find_by(name: 'Test Group Import') + expect(group.path).to eq 'custom-path' + end + end + + context 'when the path is already taken' do + before do + create(:group, path: 'test-group-import') + end + + it 'suggests a unique path' do + visit new_group_path + find('#import-group-tab').click + + fill_in :import_group_path, with: 'test-group-import' + expect(page).to have_content 'Group path is already taken. Suggestions: test-group-import1' + end + end + end + + context 'when the user uploads an invalid export file' do + let(:file) { File.join(Rails.root, 'spec', %w[fixtures big-image.png]) } + + it 'displays an error' do + visit new_group_path + + fill_in :group_name, with: 'Test Group Import' + find('#import-group-tab').click + attach_file(file) do + find('.js-filepicker-button').click + end + + expect { click_on 'Import group' }.not_to change { Group.count } + + page.within('.flash-container') do + expect(page).to have_content('Unable to process group import file') + end + end + end +end diff --git a/spec/frontend/create_cluster/eks_cluster/store/actions_spec.js b/spec/frontend/create_cluster/eks_cluster/store/actions_spec.js index 1139f094705..01f7ada9cd6 100644 --- a/spec/frontend/create_cluster/eks_cluster/store/actions_spec.js +++ b/spec/frontend/create_cluster/eks_cluster/store/actions_spec.js @@ -1,4 +1,5 @@ import testAction from 'helpers/vuex_action_helper'; +import { useMockLocationHelper } from 'helpers/mock_window_location_helper'; import MockAdapter from 'axios-mock-adapter'; import createState from '~/create_cluster/eks_cluster/store/state'; import * as actions from '~/create_cluster/eks_cluster/store/actions'; @@ -251,12 +252,7 @@ describe('EKS Cluster Store Actions', () => { }); describe('createClusterSuccess', () => { - beforeEach(() => { - jest.spyOn(window.location, 'assign').mockImplementation(() => {}); - }); - afterEach(() => { - window.location.assign.mockRestore(); - }); + useMockLocationHelper(); it('redirects to the new cluster URL', () => { actions.createClusterSuccess(null, newClusterUrl); diff --git a/spec/frontend/design_management/components/design_notes/design_reply_form_spec.js b/spec/frontend/design_management/components/design_notes/design_reply_form_spec.js index 34b8f1f9fa8..16b34f150b8 100644 --- a/spec/frontend/design_management/components/design_notes/design_reply_form_spec.js +++ b/spec/frontend/design_management/components/design_notes/design_reply_form_spec.js @@ -18,7 +18,7 @@ describe('Design reply form component', () => { const findCancelButton = () => wrapper.find({ ref: 'cancelButton' }); const findModal = () => wrapper.find({ ref: 'cancelCommentModal' }); - function createComponent(props = {}) { + function createComponent(props = {}, mountOptions = {}) { wrapper = mount(DesignReplyForm, { propsData: { value: '', @@ -26,6 +26,7 @@ describe('Design reply form component', () => { ...props, }, stubs: { GlModal }, + ...mountOptions, }); } @@ -34,7 +35,8 @@ describe('Design reply form component', () => { }); it('textarea has focus after component mount', () => { - createComponent(); + // We need to attach to document, so that `document.activeElement` is properly set in jsdom + createComponent({}, { attachToDocument: true }); expect(findTextarea().element).toEqual(document.activeElement); }); diff --git a/spec/frontend/environment.js b/spec/frontend/environment.js index cd4fae60049..08da34aa27a 100644 --- a/spec/frontend/environment.js +++ b/spec/frontend/environment.js @@ -2,7 +2,7 @@ const path = require('path'); const { ErrorWithStack } = require('jest-util'); -const JSDOMEnvironment = require('jest-environment-jsdom'); +const JSDOMEnvironment = require('jest-environment-jsdom-sixteen'); const ROOT_PATH = path.resolve(__dirname, '../..'); diff --git a/spec/frontend/helpers/mock_window_location_helper.js b/spec/frontend/helpers/mock_window_location_helper.js new file mode 100644 index 00000000000..175044d1fce --- /dev/null +++ b/spec/frontend/helpers/mock_window_location_helper.js @@ -0,0 +1,43 @@ +/** + * Manage the instance of a custom `window.location` + * + * This only encapsulates the setup / teardown logic so that it can easily be + * reused with different implementations (i.e. a spy or a [fake][1]) + * + * [1]: https://stackoverflow.com/a/41434763/1708147 + * + * @param {() => any} fn Function that returns the object to use for window.location + */ +const useMockLocation = fn => { + const origWindowLocation = window.location; + let currentWindowLocation; + + Object.defineProperty(window, 'location', { + get: () => currentWindowLocation, + }); + + beforeEach(() => { + currentWindowLocation = fn(); + }); + + afterEach(() => { + currentWindowLocation = origWindowLocation; + }); +}; + +/** + * Create an object with the location interface but `jest.fn()` implementations. + */ +export const createWindowLocationSpy = () => { + return { + assign: jest.fn(), + reload: jest.fn(), + replace: jest.fn(), + toString: jest.fn(), + }; +}; + +/** + * Before each test, overwrite `window.location` with a spy implementation. + */ +export const useMockLocationHelper = () => useMockLocation(createWindowLocationSpy); diff --git a/spec/frontend/helpers/set_window_location_helper_spec.js b/spec/frontend/helpers/set_window_location_helper_spec.js index 2a2c024c824..da609b6bbf0 100644 --- a/spec/frontend/helpers/set_window_location_helper_spec.js +++ b/spec/frontend/helpers/set_window_location_helper_spec.js @@ -33,7 +33,7 @@ describe('setWindowLocation', () => { it.each([null, 1, undefined, false, '', 'gitlab.com'])( 'throws an error when called with an invalid url: "%s"', invalidUrl => { - expect(() => setWindowLocation(invalidUrl)).toThrow(new TypeError('Invalid URL')); + expect(() => setWindowLocation(invalidUrl)).toThrow(/Invalid URL/); expect(window.location).toBe(originalLocation); }, ); diff --git a/spec/frontend/ide/stores/actions/project_spec.js b/spec/frontend/ide/stores/actions/project_spec.js index f71cffb93ad..64024c12903 100644 --- a/spec/frontend/ide/stores/actions/project_spec.js +++ b/spec/frontend/ide/stores/actions/project_spec.js @@ -12,7 +12,8 @@ import { } from '~/ide/stores/actions'; import service from '~/ide/services'; import api from '~/api'; -import testAction from '../../../helpers/vuex_action_helper'; +import testAction from 'helpers/vuex_action_helper'; +import { useMockLocationHelper } from 'helpers/mock_window_location_helper'; const TEST_PROJECT_ID = 'abc/def'; @@ -116,6 +117,8 @@ describe('IDE store project actions', () => { }); describe('createNewBranchFromDefault', () => { + useMockLocationHelper(); + beforeEach(() => { jest.spyOn(api, 'createBranch').mockResolvedValue(); }); @@ -170,8 +173,6 @@ describe('IDE store project actions', () => { }); it('reloads window', done => { - jest.spyOn(window.location, 'reload').mockImplementation(); - createNewBranchFromDefault( { state: { diff --git a/spec/frontend/monitoring/components/duplicate_dashboard_form_spec.js b/spec/frontend/monitoring/components/duplicate_dashboard_form_spec.js index 8ab7c8b9e50..29e4c4514fe 100644 --- a/spec/frontend/monitoring/components/duplicate_dashboard_form_spec.js +++ b/spec/frontend/monitoring/components/duplicate_dashboard_form_spec.js @@ -10,6 +10,8 @@ const createMountedWrapper = (props = {}) => { wrapper = mount(DuplicateDashboardForm, { propsData: { ...props }, sync: false, + // We need to attach to document, so that `document.activeElement` is properly set in jsdom + attachToDocument: true, }); }; diff --git a/spec/frontend/onboarding_issues/index_spec.js b/spec/frontend/onboarding_issues/index_spec.js index f4c08049ba4..b844caa07aa 100644 --- a/spec/frontend/onboarding_issues/index_spec.js +++ b/spec/frontend/onboarding_issues/index_spec.js @@ -2,6 +2,7 @@ import $ from 'jquery'; import { showLearnGitLabIssuesPopover } from '~/onboarding_issues'; import { getCookie, setCookie, removeCookie } from '~/lib/utils/common_utils'; import setWindowLocation from 'helpers/set_window_location_helper'; +import Tracking from '~/tracking'; describe('Onboarding Issues Popovers', () => { const COOKIE_NAME = 'onboarding_issues_settings'; @@ -116,12 +117,20 @@ describe('Onboarding Issues Popovers', () => { describe('when dismissing the popover', () => { beforeEach(() => { + jest.spyOn(Tracking, 'event'); document.querySelector('.learn-gitlab.popover .close').click(); }); it('deletes the cookie', () => { expect(getCookie(COOKIE_NAME)).toBe(undefined); }); + + it('sends a tracking event', () => { + expect(Tracking.event).toHaveBeenCalledWith( + 'Growth::Conversion::Experiment::OnboardingIssues', + 'dismiss_popover', + ); + }); }); }); }); diff --git a/spec/frontend/sidebar/confidential_issue_sidebar_spec.js b/spec/frontend/sidebar/confidential_issue_sidebar_spec.js index e7a64ec5ed9..fe7c3aadeeb 100644 --- a/spec/frontend/sidebar/confidential_issue_sidebar_spec.js +++ b/spec/frontend/sidebar/confidential_issue_sidebar_spec.js @@ -6,11 +6,14 @@ import SidebarService from '~/sidebar/services/sidebar_service'; import createFlash from '~/flash'; import RecaptchaModal from '~/vue_shared/components/recaptcha_modal.vue'; import createStore from '~/notes/stores'; +import { useMockLocationHelper } from 'helpers/mock_window_location_helper'; jest.mock('~/flash'); jest.mock('~/sidebar/services/sidebar_service'); describe('Confidential Issue Sidebar Block', () => { + useMockLocationHelper(); + let wrapper; const findRecaptchaModal = () => wrapper.find(RecaptchaModal); @@ -43,10 +46,6 @@ describe('Confidential Issue Sidebar Block', () => { }); }; - beforeEach(() => { - jest.spyOn(window.location, 'reload').mockImplementation(); - }); - afterEach(() => { wrapper.destroy(); }); diff --git a/spec/frontend/tracking_spec.js b/spec/frontend/tracking_spec.js index 08a26d46618..8acfa655c2c 100644 --- a/spec/frontend/tracking_spec.js +++ b/spec/frontend/tracking_spec.js @@ -121,11 +121,6 @@ describe('Tracking', () => { describe('tracking interface events', () => { let eventSpy; - const trigger = (selector, eventName = 'click') => { - const event = new Event(eventName, { bubbles: true }); - document.querySelector(selector).dispatchEvent(event); - }; - beforeEach(() => { eventSpy = jest.spyOn(Tracking, 'event'); Tracking.bindDocument('_category_'); // only happens once @@ -140,7 +135,7 @@ describe('Tracking', () => { }); it('binds to clicks on elements matching [data-track-event]', () => { - trigger('[data-track-event="click_input1"]'); + document.querySelector('[data-track-event="click_input1"]').click(); expect(eventSpy).toHaveBeenCalledWith('_category_', 'click_input1', { label: '_label_', @@ -149,13 +144,13 @@ describe('Tracking', () => { }); it('does not bind to clicks on elements without [data-track-event]', () => { - trigger('[data-track-eventbogus="click_bogusinput"]'); + document.querySelector('[data-track-eventbogus="click_bogusinput"]').click(); expect(eventSpy).not.toHaveBeenCalled(); }); it('allows value override with the data-track-value attribute', () => { - trigger('[data-track-event="click_input2"]'); + document.querySelector('[data-track-event="click_input2"]').click(); expect(eventSpy).toHaveBeenCalledWith('_category_', 'click_input2', { value: '_value_override_', @@ -163,13 +158,15 @@ describe('Tracking', () => { }); it('handles checkbox values correctly', () => { - trigger('[data-track-event="toggle_checkbox"]'); // checking + const checkbox = document.querySelector('[data-track-event="toggle_checkbox"]'); + + checkbox.click(); // unchecking expect(eventSpy).toHaveBeenCalledWith('_category_', 'toggle_checkbox', { value: false, }); - trigger('[data-track-event="toggle_checkbox"]'); // unchecking + checkbox.click(); // checking expect(eventSpy).toHaveBeenCalledWith('_category_', 'toggle_checkbox', { value: '_value_', @@ -177,17 +174,19 @@ describe('Tracking', () => { }); it('handles bootstrap dropdowns', () => { - trigger('[data-track-event="toggle_dropdown"]', 'show.bs.dropdown'); // showing + const dropdown = document.querySelector('[data-track-event="toggle_dropdown"]'); + + dropdown.dispatchEvent(new Event('show.bs.dropdown', { bubbles: true })); expect(eventSpy).toHaveBeenCalledWith('_category_', 'toggle_dropdown_show', {}); - trigger('[data-track-event="toggle_dropdown"]', 'hide.bs.dropdown'); // hiding + dropdown.dispatchEvent(new Event('hide.bs.dropdown', { bubbles: true })); expect(eventSpy).toHaveBeenCalledWith('_category_', 'toggle_dropdown_hide', {}); }); it('handles nested elements inside an element with tracking', () => { - trigger('span.nested', 'click'); + document.querySelector('span.nested').click(); expect(eventSpy).toHaveBeenCalledWith('_category_', 'nested_event', {}); }); diff --git a/spec/frontend/vue_shared/components/diff_viewer/viewers/renamed_spec.js b/spec/frontend/vue_shared/components/diff_viewer/viewers/renamed_spec.js index 13584d7aeeb..e0e982f4e11 100644 --- a/spec/frontend/vue_shared/components/diff_viewer/viewers/renamed_spec.js +++ b/spec/frontend/vue_shared/components/diff_viewer/viewers/renamed_spec.js @@ -274,7 +274,7 @@ describe('Renamed Diff Viewer', () => { expect(link.text()).toEqual(linkText); expect(link.attributes('href')).toEqual(DIFF_FILE_VIEW_PATH); - link.trigger('click'); + link.vm.$emit('click'); expect(clickMock).toHaveBeenCalled(); }, diff --git a/spec/graphql/types/snippets/file_input_action_enum_spec.rb b/spec/graphql/types/snippets/file_input_action_enum_spec.rb new file mode 100644 index 00000000000..2ccc8b04b8f --- /dev/null +++ b/spec/graphql/types/snippets/file_input_action_enum_spec.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Types::Snippets::FileInputActionEnum do + specify { expect(described_class.graphql_name).to eq('SnippetFileInputActionEnum') } + + it 'exposes all file input action types' do + expect(described_class.values.keys).to eq(%w[create update delete move]) + end +end diff --git a/spec/graphql/types/snippets/file_input_type_spec.rb b/spec/graphql/types/snippets/file_input_type_spec.rb new file mode 100644 index 00000000000..62e5caf20b7 --- /dev/null +++ b/spec/graphql/types/snippets/file_input_type_spec.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Types::Snippets::FileInputType do + specify { expect(described_class.graphql_name).to eq('SnippetFileInputType') } + + it 'has the correct arguments' do + expect(described_class.arguments.keys).to match_array(%w[filePath action previousPath content]) + end + + it 'sets the type of action argument to FileInputActionEnum' do + expect(described_class.arguments['action'].type.of_type).to eq(Types::Snippets::FileInputActionEnum) + end +end diff --git a/spec/helpers/application_settings_helper_spec.rb b/spec/helpers/application_settings_helper_spec.rb index c2f3e26f97b..3fb754f1090 100644 --- a/spec/helpers/application_settings_helper_spec.rb +++ b/spec/helpers/application_settings_helper_spec.rb @@ -123,4 +123,27 @@ describe ApplicationSettingsHelper do end end end + + describe '.storage_weights' do + let(:application_setting) { build(:application_setting) } + + before do + helper.instance_variable_set(:@application_setting, application_setting) + stub_storage_settings({ 'default': {}, 'storage_1': {}, 'storage_2': {} }) + allow(ApplicationSetting).to receive(:repository_storages_weighted_attributes).and_return( + [:repository_storages_weighted_default, + :repository_storages_weighted_storage_1, + :repository_storages_weighted_storage_2]) + + stub_application_setting(repository_storages_weighted: { 'default' => 100, 'storage_1' => 50, 'storage_2' => nil }) + end + + it 'returns storages correctly' do + expect(helper.storage_weights).to eq([ + { name: :repository_storages_weighted_default, label: 'default', value: 100 }, + { name: :repository_storages_weighted_storage_1, label: 'storage_1', value: 50 }, + { name: :repository_storages_weighted_storage_2, label: 'storage_2', value: 0 } + ]) + end + end end diff --git a/spec/lib/banzai/filter/external_issue_reference_filter_spec.rb b/spec/lib/banzai/filter/external_issue_reference_filter_spec.rb index 502ab07baa9..a70c820f97a 100644 --- a/spec/lib/banzai/filter/external_issue_reference_filter_spec.rb +++ b/spec/lib/banzai/filter/external_issue_reference_filter_spec.rb @@ -52,7 +52,7 @@ describe Banzai::Filter::ExternalIssueReferenceFilter do it 'includes a title attribute' do doc = filter("Issue #{reference}") - expect(doc.css('a').first.attr('title')).to include("Issue in #{project.issues_tracker.title}") + expect(doc.css('a').first.attr('title')).to include("Issue in #{project.external_issue_tracker.title}") end it 'escapes the title attribute' do diff --git a/spec/lib/gitlab/import_export/all_models.yml b/spec/lib/gitlab/import_export/all_models.yml index 829fb25e431..ef9321dc1fc 100644 --- a/spec/lib/gitlab/import_export/all_models.yml +++ b/spec/lib/gitlab/import_export/all_models.yml @@ -184,6 +184,7 @@ ci_pipelines: - statuses - latest_statuses_ordered_by_stage - builds +- bridges - processables - trigger_requests - variables diff --git a/spec/lib/gitlab/instrumentation/redis_base_spec.rb b/spec/lib/gitlab/instrumentation/redis_base_spec.rb index b18f1c6dcc1..5ea8f00114e 100644 --- a/spec/lib/gitlab/instrumentation/redis_base_spec.rb +++ b/spec/lib/gitlab/instrumentation/redis_base_spec.rb @@ -4,17 +4,52 @@ require 'spec_helper' describe Gitlab::Instrumentation::RedisBase, :request_store do let(:instrumentation_class_a) do - stub_const('RedisInstanceA', Class.new(described_class)) + stub_const('InstanceA', Class.new(described_class)) end let(:instrumentation_class_b) do - stub_const('RedisInstanceB', Class.new(described_class)) + stub_const('InstanceB', Class.new(described_class)) end describe '.storage_key' do it 'returns the class name with underscore' do - expect(instrumentation_class_a.storage_key).to eq('redis_instance_a') - expect(instrumentation_class_b.storage_key).to eq('redis_instance_b') + expect(instrumentation_class_a.storage_key).to eq('instance_a') + expect(instrumentation_class_b.storage_key).to eq('instance_b') + end + end + + describe '.known_payload_keys' do + it 'returns generated payload keys' do + expect(instrumentation_class_a.known_payload_keys).to eq([:redis_instance_a_calls, + :redis_instance_a_duration_s, + :redis_instance_a_read_bytes, + :redis_instance_a_write_bytes]) + end + + it 'does not call calculation methods' do + expect(instrumentation_class_a).not_to receive(:get_request_count) + expect(instrumentation_class_a).not_to receive(:query_time) + expect(instrumentation_class_a).not_to receive(:read_bytes) + expect(instrumentation_class_a).not_to receive(:write_bytes) + + instrumentation_class_a.known_payload_keys + end + end + + describe '.payload' do + it 'returns values that are higher than 0' do + allow(instrumentation_class_a).to receive(:get_request_count) { 1 } + allow(instrumentation_class_a).to receive(:query_time) { 0.1 } + allow(instrumentation_class_a).to receive(:read_bytes) { 0.0 } + allow(instrumentation_class_a).to receive(:write_bytes) { 123 } + + expected_payload = { + redis_instance_a_calls: 1, + redis_instance_a_write_bytes: 123, + redis_instance_a_duration_s: 0.1 + } + + expect(instrumentation_class_a.payload).to eq(expected_payload) end end diff --git a/spec/lib/gitlab/instrumentation/redis_spec.rb b/spec/lib/gitlab/instrumentation/redis_spec.rb index b63bce42797..8311c4f5bbb 100644 --- a/spec/lib/gitlab/instrumentation/redis_spec.rb +++ b/spec/lib/gitlab/instrumentation/redis_spec.rb @@ -25,4 +25,90 @@ describe Gitlab::Instrumentation::Redis do it_behaves_like 'aggregation of redis storage data', :query_time it_behaves_like 'aggregation of redis storage data', :read_bytes it_behaves_like 'aggregation of redis storage data', :write_bytes + + describe '.known_payload_keys' do + it 'returns all known payload keys' do + expected_keys = [ + :redis_calls, + :redis_duration_s, + :redis_read_bytes, + :redis_write_bytes, + :redis_action_cable_calls, + :redis_action_cable_duration_s, + :redis_action_cable_read_bytes, + :redis_action_cable_write_bytes, + :redis_cache_calls, + :redis_cache_duration_s, + :redis_cache_read_bytes, + :redis_cache_write_bytes, + :redis_queues_calls, + :redis_queues_duration_s, + :redis_queues_read_bytes, + :redis_queues_write_bytes, + :redis_shared_state_calls, + :redis_shared_state_duration_s, + :redis_shared_state_read_bytes, + :redis_shared_state_write_bytes + ] + + expect(described_class.known_payload_keys).to eq(expected_keys) + end + + it 'does not call storage calculation methods' do + described_class::STORAGES.each do |storage| + expect(storage).not_to receive(:get_request_count) + expect(storage).not_to receive(:query_time) + expect(storage).not_to receive(:read_bytes) + expect(storage).not_to receive(:write_bytes) + end + + described_class.known_payload_keys + end + end + + describe '.payload', :request_store do + before do + Gitlab::Redis::Cache.with { |redis| redis.set('cache-test', 321) } + Gitlab::Redis::SharedState.with { |redis| redis.set('shared-state-test', 123) } + end + + it 'returns payload filtering out zeroed values' do + expected_payload = { + # Aggregated results + redis_calls: 2, + redis_duration_s: be >= 0, + redis_read_bytes: be >= 0, + redis_write_bytes: be >= 0, + + # Cache results + redis_cache_calls: 1, + redis_cache_duration_s: be >= 0, + redis_cache_read_bytes: be >= 0, + redis_cache_write_bytes: be >= 0, + + # Shared state results + redis_shared_state_calls: 1, + redis_shared_state_duration_s: be >= 0, + redis_shared_state_read_bytes: be >= 0, + redis_shared_state_write_bytes: be >= 0 + } + + expect(described_class.payload).to include(expected_payload) + expect(described_class.payload.keys).to match_array(expected_payload.keys) + end + end + + describe '.detail_store' do + it 'returns a flat array of detail stores with the storage name added to each item' do + details_row = { cmd: 'GET foo', duration: 1 } + + stub_storages(:detail_store, [details_row]) + + expect(described_class.detail_store) + .to contain_exactly(details_row.merge(storage: 'ActionCable'), + details_row.merge(storage: 'Cache'), + details_row.merge(storage: 'Queues'), + details_row.merge(storage: 'SharedState')) + end + end end diff --git a/spec/lib/gitlab/instrumentation_helper_spec.rb b/spec/lib/gitlab/instrumentation_helper_spec.rb index 144ce0247ed..15d377a16fc 100644 --- a/spec/lib/gitlab/instrumentation_helper_spec.rb +++ b/spec/lib/gitlab/instrumentation_helper_spec.rb @@ -6,6 +6,41 @@ require 'rspec-parameterized' describe Gitlab::InstrumentationHelper do using RSpec::Parameterized::TableSyntax + describe '.keys' do + it 'returns all available payload keys' do + expected_keys = [ + :gitaly_calls, + :gitaly_duration_s, + :rugged_calls, + :rugged_duration_s, + :elasticsearch_calls, + :elasticsearch_duration_s, + :redis_calls, + :redis_duration_s, + :redis_read_bytes, + :redis_write_bytes, + :redis_action_cable_calls, + :redis_action_cable_duration_s, + :redis_action_cable_read_bytes, + :redis_action_cable_write_bytes, + :redis_cache_calls, + :redis_cache_duration_s, + :redis_cache_read_bytes, + :redis_cache_write_bytes, + :redis_queues_calls, + :redis_queues_duration_s, + :redis_queues_read_bytes, + :redis_queues_write_bytes, + :redis_shared_state_calls, + :redis_shared_state_duration_s, + :redis_shared_state_read_bytes, + :redis_shared_state_write_bytes + ] + + expect(described_class.keys).to eq(expected_keys) + end + end + describe '.add_instrumentation_data', :request_store do let(:payload) { {} } @@ -34,14 +69,30 @@ describe Gitlab::InstrumentationHelper do context 'when Redis calls are made' do it 'adds Redis data and omits Gitaly data' do - Gitlab::Redis::Cache.with { |redis| redis.set('test-instrumentation', 123) } + Gitlab::Redis::Cache.with { |redis| redis.set('test-cache', 123) } + Gitlab::Redis::Queues.with { |redis| redis.set('test-queues', 321) } subject - expect(payload[:redis_calls]).to eq(1) + # Aggregated payload + expect(payload[:redis_calls]).to eq(2) expect(payload[:redis_duration_s]).to be >= 0 expect(payload[:redis_read_bytes]).to be >= 0 expect(payload[:redis_write_bytes]).to be >= 0 + + # Shared state payload + expect(payload[:redis_queues_calls]).to eq(1) + expect(payload[:redis_queues_duration_s]).to be >= 0 + expect(payload[:redis_queues_read_bytes]).to be >= 0 + expect(payload[:redis_queues_write_bytes]).to be >= 0 + + # Cache payload + expect(payload[:redis_cache_calls]).to eq(1) + expect(payload[:redis_cache_duration_s]).to be >= 0 + expect(payload[:redis_cache_read_bytes]).to be >= 0 + expect(payload[:redis_cache_write_bytes]).to be >= 0 + + # Gitaly expect(payload[:gitaly_calls]).to be_nil expect(payload[:gitaly_duration]).to be_nil end diff --git a/spec/lib/system_check/app/ci_jwt_signing_key_check_spec.rb b/spec/lib/system_check/app/ci_jwt_signing_key_check_spec.rb new file mode 100644 index 00000000000..b23504487cb --- /dev/null +++ b/spec/lib/system_check/app/ci_jwt_signing_key_check_spec.rb @@ -0,0 +1,29 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe SystemCheck::App::CiJwtSigningKeyCheck do + subject(:system_check) { described_class.new } + + describe '#check?' do + it 'returns false when key is not present' do + expect(Rails.application.secrets).to receive(:ci_jwt_signing_key).and_return(nil) + + expect(system_check.check?).to eq(false) + end + + it 'returns false when key is not valid RSA key' do + invalid_key = OpenSSL::PKey::RSA.new(1024).to_s.delete("\n") + expect(Rails.application.secrets).to receive(:ci_jwt_signing_key).and_return(invalid_key) + + expect(system_check.check?).to eq(false) + end + + it 'returns true when key is valid RSA key' do + valid_key = OpenSSL::PKey::RSA.new(1024).to_s + expect(Rails.application.secrets).to receive(:ci_jwt_signing_key).and_return(valid_key) + + expect(system_check.check?).to eq(true) + end + end +end diff --git a/spec/migrations/cleanup_projects_with_missing_namespace_spec.rb b/spec/migrations/cleanup_projects_with_missing_namespace_spec.rb index 06b6d5e3b46..27c954d2984 100644 --- a/spec/migrations/cleanup_projects_with_missing_namespace_spec.rb +++ b/spec/migrations/cleanup_projects_with_missing_namespace_spec.rb @@ -5,10 +5,6 @@ require 'spec_helper' require Rails.root.join('db', 'post_migrate', '20200511080113_add_projects_foreign_key_to_namespaces.rb') require Rails.root.join('db', 'post_migrate', '20200511083541_cleanup_projects_with_missing_namespace.rb') -LOST_AND_FOUND_GROUP = 'lost-and-found' -USER_TYPE_GHOST = 5 -ACCESS_LEVEL_OWNER = 50 - # In order to test the CleanupProjectsWithMissingNamespace migration, we need # to first create an orphaned project (one with an invalid namespace_id) # and then run the migration to check that the project was properly cleaned up @@ -77,31 +73,39 @@ describe CleanupProjectsWithMissingNamespace, :migration, schema: SchemaVersionF end it 'creates the ghost user' do - expect(users.where(user_type: USER_TYPE_GHOST).count).to eq(0) + expect(users.where(user_type: described_class::User::USER_TYPE_GHOST).count).to eq(0) disable_migrations_output { migrate! } - expect(users.where(user_type: USER_TYPE_GHOST).count).to eq(1) + expect(users.where(user_type: described_class::User::USER_TYPE_GHOST).count).to eq(1) end it 'creates the lost-and-found group, owned by the ghost user' do expect( - Group.where(Group.arel_table[:name].matches("#{LOST_AND_FOUND_GROUP}%")).count + described_class::Group.where( + described_class::Group + .arel_table[:name] + .matches("#{described_class::User::LOST_AND_FOUND_GROUP}%") + ).count ).to eq(0) disable_migrations_output { migrate! } - ghost_user = users.find_by(user_type: USER_TYPE_GHOST) + ghost_user = users.find_by(user_type: described_class::User::USER_TYPE_GHOST) expect( - Group + described_class::Group .joins('INNER JOIN members ON namespaces.id = members.source_id') .where('namespaces.type = ?', 'Group') .where('members.type = ?', 'GroupMember') .where('members.source_type = ?', 'Namespace') .where('members.user_id = ?', ghost_user.id) .where('members.requested_at IS NULL') - .where('members.access_level = ?', ACCESS_LEVEL_OWNER) - .where(Group.arel_table[:name].matches("#{LOST_AND_FOUND_GROUP}%")) + .where('members.access_level = ?', described_class::ACCESS_LEVEL_OWNER) + .where( + described_class::Group + .arel_table[:name] + .matches("#{described_class::User::LOST_AND_FOUND_GROUP}%") + ) .count ).to eq(1) end @@ -114,7 +118,11 @@ describe CleanupProjectsWithMissingNamespace, :migration, schema: SchemaVersionF disable_migrations_output { migrate! } - lost_and_found_group = Group.find_by(Group.arel_table[:name].matches("#{LOST_AND_FOUND_GROUP}%")) + lost_and_found_group = described_class::Group.find_by( + described_class::Group + .arel_table[:name] + .matches("#{described_class::User::LOST_AND_FOUND_GROUP}%") + ) orphaned_project = projects.find_by(id: orphaned_project.id) expect(orphaned_project.visibility_level).to eq(0) diff --git a/spec/migrations/update_routes_for_lost_and_found_group_and_orphaned_projects_spec.rb b/spec/migrations/update_routes_for_lost_and_found_group_and_orphaned_projects_spec.rb new file mode 100644 index 00000000000..4cf096b9df6 --- /dev/null +++ b/spec/migrations/update_routes_for_lost_and_found_group_and_orphaned_projects_spec.rb @@ -0,0 +1,182 @@ +# frozen_string_literal: true + +require 'spec_helper' + +require Rails.root.join('db', 'post_migrate', '20200602143020_update_routes_for_lost_and_found_group_and_orphaned_projects.rb') + +describe UpdateRoutesForLostAndFoundGroupAndOrphanedProjects, :migration do + let(:users) { table(:users) } + let(:namespaces) { table(:namespaces) } + let(:members) { table(:members) } + let(:projects) { table(:projects) } + let(:routes) { table(:routes) } + + before do + # Create a Ghost User and its namnespace, but skip the route + ghost_user = users.create!( + name: 'Ghost User', + username: 'ghost', + email: 'ghost@example.com', + user_type: described_class::User::USER_TYPE_GHOST, + projects_limit: 100, + state: :active, + bio: 'This is a "Ghost User"' + ) + + namespaces.create!( + name: 'Ghost User', + path: 'ghost', + owner_id: ghost_user.id, + visibility_level: 20 + ) + + # Create the 'lost-and-found', owned by the Ghost user, but with no route + lost_and_found_group = namespaces.create!( + name: described_class::User::LOST_AND_FOUND_GROUP, + path: described_class::User::LOST_AND_FOUND_GROUP, + type: 'Group', + description: 'Group to store orphaned projects', + visibility_level: 0 + ) + + members.create!( + type: 'GroupMember', + source_id: lost_and_found_group.id, + user_id: ghost_user.id, + source_type: 'Namespace', + access_level: described_class::User::ACCESS_LEVEL_OWNER, + notification_level: 3 + ) + + # Add an orphaned project under 'lost-and-found' but with the wrong path in its route + orphaned_project = projects.create!( + name: 'orphaned_project', + path: 'orphaned_project', + visibility_level: 20, + archived: false, + namespace_id: lost_and_found_group.id + ) + + routes.create!( + source_id: orphaned_project.id, + source_type: 'Project', + path: 'orphaned_project', + name: 'orphaned_project', + created_at: Time.zone.now, + updated_at: Time.zone.now + ) + + # Create another user named ghost which is not the Ghost User + # Also create a 'lost-and-found' group for them and add projects to it + # Purpose: test that the routes added for the 'lost-and-found' group and + # its projects are unique + fake_ghost_user = users.create!( + name: 'Ghost User', + username: 'ghost1', + email: 'ghost1@example.com', + user_type: nil, + projects_limit: 100, + state: :active, + bio: 'This is NOT a "Ghost User"' + ) + + fake_ghost_user_namespace = namespaces.create!( + name: 'Ghost User', + path: 'ghost1', + owner_id: fake_ghost_user.id, + visibility_level: 20 + ) + + routes.create!( + source_id: fake_ghost_user_namespace.id, + source_type: 'Namespace', + path: 'Ghost User', + name: 'ghost1', + created_at: Time.zone.now, + updated_at: Time.zone.now + ) + + fake_lost_and_found_group = namespaces.create!( + name: 'Lost and Found', + path: described_class::User::LOST_AND_FOUND_GROUP, # same path as the lost-and-found group + type: 'Group', + description: 'Fake lost and found group with the same path as the real one', + visibility_level: 20 + ) + + routes.create!( + source_id: fake_lost_and_found_group.id, + source_type: 'Namespace', + path: described_class::User::LOST_AND_FOUND_GROUP, # same path as the lost-and-found group + name: 'Lost and Found', + created_at: Time.zone.now, + updated_at: Time.zone.now + ) + + members.create!( + type: 'GroupMember', + source_id: fake_lost_and_found_group.id, + user_id: fake_ghost_user.id, + source_type: 'Namespace', + access_level: described_class::User::ACCESS_LEVEL_OWNER, + notification_level: 3 + ) + + normal_project = projects.create!( + name: 'normal_project', + path: 'normal_project', + visibility_level: 20, + archived: false, + namespace_id: fake_lost_and_found_group.id + ) + + routes.create!( + source_id: normal_project.id, + source_type: 'Project', + path: "#{described_class::User::LOST_AND_FOUND_GROUP}/normal_project", + name: 'Lost and Found / normal_project', + created_at: Time.zone.now, + updated_at: Time.zone.now + ) + end + + it 'creates the route for the ghost user namespace' do + expect(routes.where(path: 'ghost').count).to eq(0) + + disable_migrations_output { migrate! } + + expect(routes.where(path: 'ghost').count).to eq(1) + end + + it 'fixes the path for the lost-and-found group by generating a unique one' do + expect(namespaces.where(path: described_class::User::LOST_AND_FOUND_GROUP).count).to eq(2) + + disable_migrations_output { migrate! } + + expect(namespaces.where(path: described_class::User::LOST_AND_FOUND_GROUP).count).to eq(1) + + lost_and_found_group = namespaces.find_by(name: described_class::User::LOST_AND_FOUND_GROUP) + expect(lost_and_found_group.path).to eq('lost-and-found1') + end + + it 'creates the route for the lost-and-found group' do + expect(routes.where(path: described_class::User::LOST_AND_FOUND_GROUP).count).to eq(1) + expect(routes.where(path: 'lost-and-found1').count).to eq(0) + + disable_migrations_output { migrate! } + + expect(routes.where(path: described_class::User::LOST_AND_FOUND_GROUP).count).to eq(1) + expect(routes.where(path: 'lost-and-found1').count).to eq(1) + end + + it 'updates the route for the orphaned project' do + orphaned_project_route = routes.find_by(path: 'orphaned_project') + expect(orphaned_project_route.name).to eq('orphaned_project') + + disable_migrations_output { migrate! } + + updated_route = routes.find_by(id: orphaned_project_route.id) + expect(updated_route.path).to eq('lost-and-found1/orphaned_project') + expect(updated_route.name).to eq("#{described_class::User::LOST_AND_FOUND_GROUP} / orphaned_project") + end +end diff --git a/spec/models/ci/bridge_spec.rb b/spec/models/ci/bridge_spec.rb index 34f89d9cdae..385261e0ee9 100644 --- a/spec/models/ci/bridge_spec.rb +++ b/spec/models/ci/bridge_spec.rb @@ -21,6 +21,11 @@ describe Ci::Bridge do expect(bridge).to have_many(:sourced_pipelines) end + it 'has one downstream pipeline' do + expect(bridge).to have_one(:sourced_pipeline) + expect(bridge).to have_one(:downstream_pipeline) + end + describe '#tags' do it 'only has a bridge tag' do expect(bridge.tags).to eq [:bridge] diff --git a/spec/models/ci/build_spec.rb b/spec/models/ci/build_spec.rb index dfd56ebb0b8..23d29653068 100644 --- a/spec/models/ci/build_spec.rb +++ b/spec/models/ci/build_spec.rb @@ -3123,24 +3123,8 @@ describe Ci::Build do let!(:job_variable) { create(:ci_job_variable, :dotenv_source, job: prepare) } - context 'FF ci_dependency_variables is enabled' do - before do - stub_feature_flags(ci_dependency_variables: true) - end - - it 'inherits dependent variables' do - expect(build.scoped_variables.to_hash).to include(job_variable.key => job_variable.value) - end - end - - context 'FF ci_dependency_variables is disabled' do - before do - stub_feature_flags(ci_dependency_variables: false) - end - - it 'does not inherit dependent variables' do - expect(build.scoped_variables.to_hash).not_to include(job_variable.key => job_variable.value) - end + it 'inherits dependent variables' do + expect(build.scoped_variables.to_hash).to include(job_variable.key => job_variable.value) end end end diff --git a/spec/models/ci/pipeline_spec.rb b/spec/models/ci/pipeline_spec.rb index 409affec5a7..782a4206c36 100644 --- a/spec/models/ci/pipeline_spec.rb +++ b/spec/models/ci/pipeline_spec.rb @@ -26,6 +26,7 @@ describe Ci::Pipeline, :mailer do it { is_expected.to have_many(:trigger_requests) } it { is_expected.to have_many(:variables) } it { is_expected.to have_many(:builds) } + it { is_expected.to have_many(:bridges) } it { is_expected.to have_many(:job_artifacts).through(:builds) } it { is_expected.to have_many(:auto_canceled_pipelines) } it { is_expected.to have_many(:auto_canceled_jobs) } diff --git a/spec/requests/api/jobs_spec.rb b/spec/requests/api/jobs_spec.rb index bd8aeb65d3f..18b5c00d64f 100644 --- a/spec/requests/api/jobs_spec.rb +++ b/spec/requests/api/jobs_spec.rb @@ -271,6 +271,178 @@ describe API::Jobs do end end + describe 'GET /projects/:id/pipelines/:pipeline_id/bridges' do + let!(:bridge) { create(:ci_bridge, pipeline: pipeline) } + let(:downstream_pipeline) { create(:ci_pipeline) } + + let!(:pipeline_source) do + create(:ci_sources_pipeline, + source_pipeline: pipeline, + source_project: project, + source_job: bridge, + pipeline: downstream_pipeline, + project: downstream_pipeline.project) + end + + let(:query) { Hash.new } + + before do |example| + unless example.metadata[:skip_before_request] + get api("/projects/#{project.id}/pipelines/#{pipeline.id}/bridges", api_user), params: query + end + end + + context 'authorized user' do + it 'returns pipeline bridges' do + expect(response).to have_gitlab_http_status(:ok) + expect(response).to include_pagination_headers + expect(json_response).to be_an Array + end + + it 'returns correct values' do + expect(json_response).not_to be_empty + expect(json_response.first['commit']['id']).to eq project.commit.id + expect(json_response.first['id']).to eq bridge.id + expect(json_response.first['name']).to eq bridge.name + expect(json_response.first['stage']).to eq bridge.stage + end + + it 'returns pipeline data' do + json_bridge = json_response.first + + expect(json_bridge['pipeline']).not_to be_empty + expect(json_bridge['pipeline']['id']).to eq bridge.pipeline.id + expect(json_bridge['pipeline']['ref']).to eq bridge.pipeline.ref + expect(json_bridge['pipeline']['sha']).to eq bridge.pipeline.sha + expect(json_bridge['pipeline']['status']).to eq bridge.pipeline.status + end + + it 'returns downstream pipeline data' do + json_bridge = json_response.first + + expect(json_bridge['downstream_pipeline']).not_to be_empty + expect(json_bridge['downstream_pipeline']['id']).to eq downstream_pipeline.id + expect(json_bridge['downstream_pipeline']['ref']).to eq downstream_pipeline.ref + expect(json_bridge['downstream_pipeline']['sha']).to eq downstream_pipeline.sha + expect(json_bridge['downstream_pipeline']['status']).to eq downstream_pipeline.status + end + + context 'filter bridges' do + before do + create_bridge(pipeline, :pending) + create_bridge(pipeline, :running) + end + + context 'with one scope element' do + let(:query) { { 'scope' => 'pending' } } + + it :skip_before_request do + get api("/projects/#{project.id}/pipelines/#{pipeline.id}/bridges", api_user), params: query + + expect(response).to have_gitlab_http_status(:ok) + expect(json_response).to be_an Array + expect(json_response.count).to eq 1 + expect(json_response.first["status"]).to eq "pending" + end + end + + context 'with array of scope elements' do + let(:query) { { scope: %w(pending running) } } + + it :skip_before_request do + get api("/projects/#{project.id}/pipelines/#{pipeline.id}/bridges", api_user), params: query + + expect(response).to have_gitlab_http_status(:ok) + expect(json_response).to be_an Array + expect(json_response.count).to eq 2 + json_response.each { |r| expect(%w(pending running).include?(r['status'])).to be true } + end + end + end + + context 'respond 400 when scope contains invalid state' do + let(:query) { { scope: %w(unknown running) } } + + it { expect(response).to have_gitlab_http_status(:bad_request) } + end + + context 'bridges in different pipelines' do + let!(:pipeline2) { create(:ci_empty_pipeline, project: project) } + let!(:bridge2) { create(:ci_bridge, pipeline: pipeline2) } + + it 'excludes bridges from other pipelines' do + json_response.each { |bridge| expect(bridge['pipeline']['id']).to eq(pipeline.id) } + end + end + + it 'avoids N+1 queries' do + control_count = ActiveRecord::QueryRecorder.new(skip_cached: false) do + get api("/projects/#{project.id}/pipelines/#{pipeline.id}/bridges", api_user), params: query + end.count + + 3.times { create_bridge(pipeline) } + + expect do + get api("/projects/#{project.id}/pipelines/#{pipeline.id}/bridges", api_user), params: query + end.not_to exceed_all_query_limit(control_count) + end + end + + context 'unauthorized user' do + context 'when user is not logged in' do + let(:api_user) { nil } + + it 'does not return bridges' do + expect(response).to have_gitlab_http_status(:unauthorized) + end + end + + context 'when user is guest' do + let(:api_user) { guest } + + it 'does not return bridges' do + expect(response).to have_gitlab_http_status(:forbidden) + end + end + + context 'when user has no read access for pipeline' do + before do + allow(Ability).to receive(:allowed?).and_call_original + allow(Ability).to receive(:allowed?).with(api_user, :read_pipeline, pipeline).and_return(false) + end + + it 'does not return bridges' do + get api("/projects/#{project.id}/pipelines/#{pipeline.id}/bridges", api_user) + expect(response).to have_gitlab_http_status(:forbidden) + end + end + + context 'when user has no read_build access for project' do + before do + allow(Ability).to receive(:allowed?).and_call_original + allow(Ability).to receive(:allowed?).with(api_user, :read_build, project).and_return(false) + end + + it 'does not return bridges' do + get api("/projects/#{project.id}/pipelines/#{pipeline.id}/bridges", api_user) + expect(response).to have_gitlab_http_status(:forbidden) + end + end + end + + def create_bridge(pipeline, status = :created) + create(:ci_bridge, status: status, pipeline: pipeline).tap do |bridge| + downstream_pipeline = create(:ci_pipeline) + create(:ci_sources_pipeline, + source_pipeline: pipeline, + source_project: pipeline.project, + source_job: bridge, + pipeline: downstream_pipeline, + project: downstream_pipeline.project) + end + end + end + describe 'GET /projects/:id/jobs/:job_id' do before do |example| unless example.metadata[:skip_before_request] diff --git a/spec/rubocop/cop/migration/drop_table_spec.rb b/spec/rubocop/cop/migration/drop_table_spec.rb new file mode 100644 index 00000000000..4fe7fc8c5a5 --- /dev/null +++ b/spec/rubocop/cop/migration/drop_table_spec.rb @@ -0,0 +1,62 @@ +# frozen_string_literal: true + +require 'spec_helper' + +require 'rubocop' +require 'rubocop/rspec/support' + +require_relative '../../../../rubocop/cop/migration/drop_table' + +describe RuboCop::Cop::Migration::DropTable do + include CopHelper + + subject(:cop) { described_class.new } + + context 'when in deployment migration' do + before do + allow(cop).to receive(:in_deployment_migration?).and_return(true) + end + + it 'registers an offense' do + expect_offense(<<~PATTERN) + def change + drop_table :table + ^^^^^^^^^^ #{described_class::MSG} + + add_column(:users, :username, :text) + + execute "DROP TABLE table" + ^^^^^^^ #{described_class::MSG} + + execute "CREATE UNIQUE INDEX email_index ON users (email);" + end + PATTERN + end + end + + context 'when in post-deployment migration' do + before do + allow(cop).to receive(:in_post_deployment_migration?).and_return(true) + end + + it 'registers no offense' do + expect_no_offenses(<<~PATTERN) + def change + drop_table :table + execute "DROP TABLE table" + end + PATTERN + end + end + + context 'when outside of migration' do + it 'registers no offense' do + expect_no_offenses(<<~PATTERN) + def change + drop_table :table + execute "DROP TABLE table" + end + PATTERN + end + end +end diff --git a/spec/services/alert_management/process_prometheus_alert_service_spec.rb b/spec/services/alert_management/process_prometheus_alert_service_spec.rb index 7fa6fc98d31..5b4da5e9077 100644 --- a/spec/services/alert_management/process_prometheus_alert_service_spec.rb +++ b/spec/services/alert_management/process_prometheus_alert_service_spec.rb @@ -90,18 +90,6 @@ RSpec.describe AlertManagement::ProcessPrometheusAlertService do expect(ProjectServiceWorker).to have_received(:perform_async).with(slack_service.id, an_instance_of(Hash)) end - - context 'feature flag disabled' do - before do - stub_feature_flags(alert_slack_event: false) - end - - it 'does not execute the alert service hooks' do - subject - - expect(ProjectServiceWorker).not_to have_received(:perform_async) - end - end end context 'when alert cannot be created' do diff --git a/spec/services/projects/alerting/notify_service_spec.rb b/spec/services/projects/alerting/notify_service_spec.rb index 22726d5b0ba..2f8c2049f85 100644 --- a/spec/services/projects/alerting/notify_service_spec.rb +++ b/spec/services/projects/alerting/notify_service_spec.rb @@ -145,18 +145,6 @@ describe Projects::Alerting::NotifyService do expect(ProjectServiceWorker).to have_received(:perform_async).with(slack_service.id, an_instance_of(Hash)) end - context 'feature flag disabled' do - before do - stub_feature_flags(alert_slack_event: false) - end - - it 'does not executes the alert service hooks' do - subject - - expect(ProjectServiceWorker).not_to have_received(:perform_async) - end - end - context 'existing alert with same fingerprint' do let(:fingerprint_sha) { Digest::SHA1.hexdigest(fingerprint) } let!(:existing_alert) { create(:alert_management_alert, project: project, fingerprint: fingerprint_sha) } diff --git a/spec/views/admin/application_settings/_repository_storage.html.haml_spec.rb b/spec/views/admin/application_settings/_repository_storage.html.haml_spec.rb index 26a767eadcc..63236dbb0c4 100644 --- a/spec/views/admin/application_settings/_repository_storage.html.haml_spec.rb +++ b/spec/views/admin/application_settings/_repository_storage.html.haml_spec.rb @@ -4,10 +4,11 @@ require 'spec_helper' describe 'admin/application_settings/_repository_storage.html.haml' do let(:app_settings) { create(:application_setting) } + let(:repository_storages_weighted_attributes) { [:repository_storages_weighted_default, :repository_storages_weighted_mepmep, :repository_storages_weighted_foobar]} let(:repository_storages_weighted) do { - "mepmep" => 100, - "foobar" => 50 + "default" => 100, + "mepmep" => 50 } end @@ -16,15 +17,20 @@ describe 'admin/application_settings/_repository_storage.html.haml' do allow(app_settings).to receive(:repository_storages_weighted_mepmep).and_return(100) allow(app_settings).to receive(:repository_storages_weighted_foobar).and_return(50) assign(:application_setting, app_settings) + allow(ApplicationSetting).to receive(:repository_storages_weighted_attributes).and_return(repository_storages_weighted_attributes) end context 'when multiple storages are available' do it 'lists them all' do render + # lists storages that are saved with weights repository_storages_weighted.each do |storage_name, storage_weight| expect(rendered).to have_content(storage_name) end + + # lists storage not saved with weight + expect(rendered).to have_content('foobar') end end end diff --git a/vendor/project_templates/learn_gitlab.tar.gz b/vendor/project_templates/learn_gitlab.tar.gz index 1f60a36639b..249f25bc85c 100644 Binary files a/vendor/project_templates/learn_gitlab.tar.gz and b/vendor/project_templates/learn_gitlab.tar.gz differ diff --git a/yarn.lock b/yarn.lock index 76e6de4b6e0..d5943bfaf57 100644 --- a/yarn.lock +++ b/yarn.lock @@ -929,6 +929,17 @@ jest-message-util "^24.8.0" jest-mock "^24.8.0" +"@jest/fake-timers@^25.1.0": + version "25.5.0" + resolved "https://registry.yarnpkg.com/@jest/fake-timers/-/fake-timers-25.5.0.tgz#46352e00533c024c90c2bc2ad9f2959f7f114185" + integrity sha512-9y2+uGnESw/oyOI3eww9yaxdZyHq7XvprfP/eeoCsjqKYts2yRlsHS/SgjPDV8FyMfn2nbMy8YzUk6nyvdLOpQ== + dependencies: + "@jest/types" "^25.5.0" + jest-message-util "^25.5.0" + jest-mock "^25.5.0" + jest-util "^25.5.0" + lolex "^5.0.0" + "@jest/reporters@^24.8.0": version "24.8.0" resolved "https://registry.yarnpkg.com/@jest/reporters/-/reporters-24.8.0.tgz#075169cd029bddec54b8f2c0fc489fd0b9e05729" @@ -1014,6 +1025,16 @@ "@types/istanbul-reports" "^1.1.1" "@types/yargs" "^12.0.9" +"@jest/types@^25.5.0": + version "25.5.0" + resolved "https://registry.yarnpkg.com/@jest/types/-/types-25.5.0.tgz#4d6a4793f7b9599fc3680877b856a97dbccf2a9d" + integrity sha512-OXD0RgQ86Tu3MazKo8bnrkDRaDXXMGUqd+kTtLtK1Zb7CRzQcaSRPPPV37SvYTdevXEBVxe0HXylEjs8ibkmCw== + dependencies: + "@types/istanbul-lib-coverage" "^2.0.0" + "@types/istanbul-reports" "^1.1.1" + "@types/yargs" "^15.0.0" + chalk "^3.0.0" + "@mrmlnc/readdir-enhanced@^2.2.1": version "2.2.1" resolved "https://registry.yarnpkg.com/@mrmlnc/readdir-enhanced/-/readdir-enhanced-2.2.1.tgz#524af240d1a360527b730475ecfa1344aa540dde" @@ -1098,6 +1119,13 @@ resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-0.14.0.tgz#9fb3a3cf3132328151f353de4632e01e52102bea" integrity sha512-9NET910DNaIPngYnLLPeg+Ogzqsi9uM4mSboU5y6p8S5DzMTVEsJZrawi+BoDNUVBa2DhJqQYUFvMDfgU062LQ== +"@sinonjs/commons@^1.7.0": + version "1.8.0" + resolved "https://registry.yarnpkg.com/@sinonjs/commons/-/commons-1.8.0.tgz#c8d68821a854c555bba172f3b06959a0039b236d" + integrity sha512-wEj54PfsZ5jGSwMX68G8ZXFawcSglQSXqCftWX3ec8MDUzQdHgcKvw97awHbY0efQEL5iKUOAmmVtoYgmrSG4Q== + dependencies: + type-detect "4.0.8" + "@sourcegraph/code-host-integration@0.0.48": version "0.0.48" resolved "https://registry.yarnpkg.com/@sourcegraph/code-host-integration/-/code-host-integration-0.0.48.tgz#327bd04182e8a9a8daabebc6f2b50c8186b19514" @@ -1291,11 +1319,23 @@ "@types/uglify-js" "*" source-map "^0.6.0" +"@types/yargs-parser@*": + version "15.0.0" + resolved "https://registry.yarnpkg.com/@types/yargs-parser/-/yargs-parser-15.0.0.tgz#cb3f9f741869e20cce330ffbeb9271590483882d" + integrity sha512-FA/BWv8t8ZWJ+gEOnLLd8ygxH/2UFbAvgEonyfN6yWGLKc7zVjbpl2Y4CTjid9h2RfgPP6SEt6uHwEOply00yw== + "@types/yargs@^12.0.2", "@types/yargs@^12.0.9": version "12.0.12" resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-12.0.12.tgz#45dd1d0638e8c8f153e87d296907659296873916" integrity sha512-SOhuU4wNBxhhTHxYaiG5NY4HBhDIDnJF60GU+2LqHAdKKer86//e4yg69aENCtQ04n0ovz+tq2YPME5t5yp4pw== +"@types/yargs@^15.0.0": + version "15.0.5" + resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-15.0.5.tgz#947e9a6561483bdee9adffc983e91a6902af8b79" + integrity sha512-Dk/IDOPtOgubt/IaevIUbTgV7doaKkoorvOyYM2CMwuDyP89bekI7H4xLIwunNYiK9jhCkmc6pUrJk3cj2AB9w== + dependencies: + "@types/yargs-parser" "*" + "@types/zen-observable@^0.8.0": version "0.8.0" resolved "https://registry.yarnpkg.com/@types/zen-observable/-/zen-observable-0.8.0.tgz#8b63ab7f1aa5321248aad5ac890a485656dcea4d" @@ -1539,10 +1579,10 @@ resolved "https://registry.yarnpkg.com/@yarnpkg/lockfile/-/lockfile-1.1.0.tgz#e77a97fbd345b76d83245edcd17d393b1b41fb31" integrity sha512-GpSwvyXOcOOlV70vbnzjj4fW5xW/FdUF6nQEt1ENy7m4ZCczi1+/buVUPAqmGfqznsORNFzUMjctTIp8a9tuCQ== -abab@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/abab/-/abab-2.0.0.tgz#aba0ab4c5eee2d4c79d3487d85450fb2376ebb0f" - integrity sha512-sY5AXXVZv4Y1VACTtR11UJCPHHudgY5i26Qj5TypE6DKlIApbwb5uqhXcJ5UUGbvZNRh7EeIoW+LrJumBsKp7w== +abab@^2.0.0, abab@^2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/abab/-/abab-2.0.3.tgz#623e2075e02eb2d3f2475e49f99c91846467907a" + integrity sha512-tsFzPpcttalNjFBCFMqsKYQcWxxen1pgJR56by//QwvJc4/OUS3kPOOttx2tSIfjsylB0pYu7f5D3K1RCxUnUg== abbrev@1: version "1.0.9" @@ -1565,6 +1605,14 @@ acorn-globals@^4.1.0: acorn "^6.0.1" acorn-walk "^6.0.1" +acorn-globals@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/acorn-globals/-/acorn-globals-6.0.0.tgz#46cdd39f0f8ff08a876619b55f5ac8a6dc770b45" + integrity sha512-ZQl7LOWaF5ePqqcX4hLuv/bLXYQNfNWw2c0/yX/TsPRKamzHcTGQnlCjHT3TsmkOUVEPS3crCxiPfdzE/Trlhg== + dependencies: + acorn "^7.1.1" + acorn-walk "^7.1.1" + acorn-jsx@^5.1.0: version "5.1.0" resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.1.0.tgz#294adb71b57398b0680015f0a38c563ee1db5384" @@ -1575,20 +1623,25 @@ acorn-walk@^6.0.1, acorn-walk@^6.1.1: resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-6.2.0.tgz#123cb8f3b84c2171f1f7fb252615b1c78a6b1a8c" integrity sha512-7evsyfH1cLOCdAzZAd43Cic04yKydNx0cF+7tiA19p1XnLLPU4dpCQOqpjqwokFe//vS0QqfqqjCS2JkiIs0cA== +acorn-walk@^7.1.1: + version "7.1.1" + resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-7.1.1.tgz#345f0dffad5c735e7373d2fec9a1023e6a44b83e" + integrity sha512-wdlPY2tm/9XBr7QkKlq0WQVgiuGTX6YWPyRyBviSoScBuLfTVQhvwg6wJ369GJ/1nPfTLMfnrFIfjqVg6d+jQQ== + acorn@^5.5.3: version "5.7.3" resolved "https://registry.yarnpkg.com/acorn/-/acorn-5.7.3.tgz#67aa231bf8812974b85235a96771eb6bd07ea279" integrity sha512-T/zvzYRfbVojPWahDsE5evJdHb3oJoQfFbsrKM7w5Zcs++Tr257tia3BmMP8XYVjp1S9RZXQMh7gao96BlqZOw== -acorn@^6.0.1, acorn@^6.0.7, acorn@^6.2.1: +acorn@^6.0.1, acorn@^6.0.7, acorn@^6.2.1, acorn@^6.3.0: version "6.3.0" resolved "https://registry.yarnpkg.com/acorn/-/acorn-6.3.0.tgz#0087509119ffa4fc0a0041d1e93a417e68cb856e" integrity sha512-/czfa8BwS88b9gWQVhc8eknunSA2DoJpJyTQkhheIf5E48u1N0R4q/YxxsAeqRrmK9TQ/uYfgLDfZo91UlANIA== -acorn@^7.1.0: - version "7.1.0" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.1.0.tgz#949d36f2c292535da602283586c2477c57eb2d6c" - integrity sha512-kL5CuoXA/dgxlBbVrflsflzQ3PAas7RYZB52NOm/6839iVYJgKMJ3cQJD+t2i5+qFa8h3MDpEOJiS64E8JLnSQ== +acorn@^7.1.0, acorn@^7.1.1: + version "7.2.0" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.2.0.tgz#17ea7e40d7c8640ff54a694c889c26f31704effe" + integrity sha512-apwXVmYVpQ34m/i71vrApRrRKCWQnZZF1+npOD0WV5xZFfwWOmKGQ2RWlfdy9vWITsenisM8M0Qeq8agcFHNiQ== after@0.8.2: version "0.8.2" @@ -2332,10 +2385,10 @@ brorand@^1.0.1: resolved "https://registry.yarnpkg.com/brorand/-/brorand-1.1.0.tgz#12c25efe40a45e3c323eb8675a0a0ce57b22371f" integrity sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8= -browser-process-hrtime@^0.1.2: - version "0.1.3" - resolved "https://registry.yarnpkg.com/browser-process-hrtime/-/browser-process-hrtime-0.1.3.tgz#616f00faef1df7ec1b5bf9cfe2bdc3170f26c7b4" - integrity sha512-bRFnI4NnjO6cnyLmOV/7PVoDEMJChlcfN0z4s1YMBY989/SvlfMI1lgCnkFUs53e9gQF+w7qu7XdllSTiSl8Aw== +browser-process-hrtime@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/browser-process-hrtime/-/browser-process-hrtime-1.0.0.tgz#3c9b4b7d782c8121e56f10106d84c0d0ffc94626" + integrity sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow== browser-resolve@^1.11.3: version "1.11.3" @@ -3375,10 +3428,15 @@ cssfontparser@^1.2.1: resolved "https://registry.yarnpkg.com/cssfontparser/-/cssfontparser-1.2.1.tgz#f4022fc8f9700c68029d542084afbaf425a3f3e3" integrity sha1-9AIvyPlwDGgCnVQghK+69CWj8+M= -cssom@0.3.x, "cssom@>= 0.3.2 < 0.4.0": - version "0.3.4" - resolved "https://registry.yarnpkg.com/cssom/-/cssom-0.3.4.tgz#8cd52e8a3acfd68d3aed38ee0a640177d2f9d797" - integrity sha512-+7prCSORpXNeR4/fUP3rL+TzqtiFfhMvTd7uEqMdgPvLPt4+uzFUeufx5RHjGTACCargg/DiEt/moMQmvnfkog== +cssom@0.3.x, "cssom@>= 0.3.2 < 0.4.0", cssom@~0.3.6: + version "0.3.8" + resolved "https://registry.yarnpkg.com/cssom/-/cssom-0.3.8.tgz#9f1276f5b2b463f2114d3f2c75250af8c1a36f4a" + integrity sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg== + +cssom@^0.4.4: + version "0.4.4" + resolved "https://registry.yarnpkg.com/cssom/-/cssom-0.4.4.tgz#5a66cf93d2d0b661d80bf6a44fb65f5c2e4e0a10" + integrity sha512-p3pvU7r1MyyqbTk+WbNJIgJjG2VmTIaB10rI93LzVPrmDJKkzKYMtxxyAvQXR/NS6otuzveI7+7BBq3SjBS2mw== cssstyle@^1.0.0: version "1.1.1" @@ -3387,6 +3445,13 @@ cssstyle@^1.0.0: dependencies: cssom "0.3.x" +cssstyle@^2.2.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/cssstyle/-/cssstyle-2.3.0.tgz#ff665a0ddbdc31864b09647f34163443d90b0852" + integrity sha512-AZL67abkUzIuvcHqk7c09cezpGNcxUxU4Ioi/05xHk4DQeTkWmGYftIE6ctU6AEt+Gn4n1lDStOtj7FKycP71A== + dependencies: + cssom "~0.3.6" + currently-unhandled@^0.4.1: version "0.4.1" resolved "https://registry.yarnpkg.com/currently-unhandled/-/currently-unhandled-0.4.1.tgz#988df33feab191ef799a61369dd76c17adf957ea" @@ -3699,6 +3764,15 @@ data-urls@^1.0.0: whatwg-mimetype "^2.2.0" whatwg-url "^7.0.0" +data-urls@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/data-urls/-/data-urls-2.0.0.tgz#156485a72963a970f5d5821aaf642bef2bf2db9b" + integrity sha512-X5eWTSXO/BJmpdIKCRuKUgSCgAN0OwliVK3yPKbwIWU1Tdw5BRajxlzMidvh+gwko9AfQ9zIj52pzF91Q3YAvQ== + dependencies: + abab "^2.0.3" + whatwg-mimetype "^2.3.0" + whatwg-url "^8.0.0" + date-format@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/date-format/-/date-format-2.1.0.tgz#31d5b5ea211cf5fd764cd38baf9d033df7e125cf" @@ -3760,6 +3834,11 @@ decamelize@^1.1.0, decamelize@^1.1.1, decamelize@^1.1.2, decamelize@^1.2.0: resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290" integrity sha1-9lNNFRSCabIDUue+4m9QH5oZEpA= +decimal.js@^10.2.0: + version "10.2.0" + resolved "https://registry.yarnpkg.com/decimal.js/-/decimal.js-10.2.0.tgz#39466113a9e036111d02f82489b5fd6b0b5ed231" + integrity sha512-vDPw+rDgn3bZe1+F/pyEwb1oMG2XTlRVgAa6B4KccTEpYgF8w6eQllVbQcfIJnZyvzFtFpxnpGtx8dd7DJp/Rw== + deckar01-task_list@^2.3.1: version "2.3.1" resolved "https://registry.yarnpkg.com/deckar01-task_list/-/deckar01-task_list-2.3.1.tgz#f3ffd5319d7b9e27c596dc8d823b13f617ed7db7" @@ -4040,6 +4119,13 @@ domexception@^1.0.1: dependencies: webidl-conversions "^4.0.2" +domexception@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/domexception/-/domexception-2.0.1.tgz#fb44aefba793e1574b0af6aed2801d057529f304" + integrity sha512-yxJ2mFy/sibVQlu5qHjOkf9J3K6zgmCxgJ94u2EdvDOV09H+32LtRswEcUsmUWN72pVLOEnTSRaIVVzVQgS0dg== + dependencies: + webidl-conversions "^5.0.0" + domhandler@^2.3.0: version "2.4.1" resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-2.4.1.tgz#892e47000a99be55bbf3774ffea0561d8879c259" @@ -4350,12 +4436,12 @@ escaper@^2.5.3: resolved "https://registry.yarnpkg.com/escaper/-/escaper-2.5.3.tgz#8b8fe90ba364054151ab7eff18b4ce43b1e13ab5" integrity sha512-QGb9sFxBVpbzMggrKTX0ry1oiI4CSDAl9vIL702hzl1jGW8VZs7qfqTRX7WDOjoNDoEVGcEtu1ZOQgReSfT2kQ== -escodegen@^1.9.1: - version "1.11.0" - resolved "https://registry.yarnpkg.com/escodegen/-/escodegen-1.11.0.tgz#b27a9389481d5bfd5bec76f7bb1eb3f8f4556589" - integrity sha512-IeMV45ReixHS53K/OmfKAIztN/igDHzTJUhZM3k1jMhIZWjk45SMwAtBsEXiJp3vSPmTcu6CXn7mDvFHRN66fw== +escodegen@^1.14.1, escodegen@^1.9.1: + version "1.14.2" + resolved "https://registry.yarnpkg.com/escodegen/-/escodegen-1.14.2.tgz#14ab71bf5026c2aa08173afba22c6f3173284a84" + integrity sha512-InuOIiKk8wwuOFg6x9BQXbzjrQhtyXh46K9bqVTPzSo2FnyMBaYGBMC6PhQy7yxxil9vIedFBweQBMK74/7o8A== dependencies: - esprima "^3.1.3" + esprima "^4.0.1" estraverse "^4.2.0" esutils "^2.0.2" optionator "^0.8.1" @@ -4577,12 +4663,7 @@ espree@^6.1.2: acorn-jsx "^5.1.0" eslint-visitor-keys "^1.1.0" -esprima@^3.1.3: - version "3.1.3" - resolved "https://registry.yarnpkg.com/esprima/-/esprima-3.1.3.tgz#fdca51cee6133895e3c88d535ce49dbff62a4633" - integrity sha1-/cpRzuYTOJXjyI1TXOSdv/YqRjM= - -esprima@^4.0.0: +esprima@^4.0.0, esprima@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== @@ -5461,10 +5542,10 @@ got@^9.6.0: to-readable-stream "^1.0.0" url-parse-lax "^3.0.0" -graceful-fs@^4.1.11, graceful-fs@^4.1.15, graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.1.9, graceful-fs@^4.2.2: - version "4.2.3" - resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.3.tgz#4a12ff1b60376ef09862c2093edd908328be8423" - integrity sha512-a30VEBm4PEdx1dRB7MFK7BejejvCvBronbLjht+sHuGYj8PHs7M/5Z+rt5lw551vZ7yfTCj4Vuyy3mSJytDWRQ== +graceful-fs@^4.1.11, graceful-fs@^4.1.15, graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.1.9, graceful-fs@^4.2.2, graceful-fs@^4.2.4: + version "4.2.4" + resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.4.tgz#2256bde14d3632958c465ebc96dc467ca07a29fb" + integrity sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw== "graceful-readlink@>= 1.0.0": version "1.0.1" @@ -5524,7 +5605,7 @@ har-schema@^2.0.0: resolved "https://registry.yarnpkg.com/har-schema/-/har-schema-2.0.0.tgz#a94c2224ebcac04782a0d9035521f24735b7ec92" integrity sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI= -har-validator@~5.1.0: +har-validator@~5.1.3: version "5.1.3" resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-5.1.3.tgz#1ef89ebd3e4996557675eed9893110dc350fa080" integrity sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g== @@ -5695,6 +5776,13 @@ html-encoding-sniffer@^1.0.2: dependencies: whatwg-encoding "^1.0.1" +html-encoding-sniffer@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/html-encoding-sniffer/-/html-encoding-sniffer-2.0.1.tgz#42a6dc4fd33f00281176e8b23759ca4e4fa185f3" + integrity sha512-D5JbOMBIR/TVZkubHT+OyT2705QvogUW4IBn6nHd756OwieSF9aDYFj4dv6HHEVGYbHaLETa3WggZYWWMyy3ZQ== + dependencies: + whatwg-encoding "^1.0.5" + html-entities@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/html-entities/-/html-entities-1.2.1.tgz#0df29351f0721163515dfb9e5543e5f6eed5162f" @@ -6293,6 +6381,11 @@ is-plain-object@^2.0.3, is-plain-object@^2.0.4: dependencies: isobject "^3.0.1" +is-potential-custom-element-name@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.0.tgz#0c52e54bcca391bb2c494b21e8626d7336c6e397" + integrity sha1-DFLlS8yjkbssSUsh6GJtczbG45c= + is-promise@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/is-promise/-/is-promise-2.1.0.tgz#79a2a9ece7f096e80f36d2b2f3bc16c1ff4bf3fa" @@ -6620,7 +6713,17 @@ jest-each@^24.8.0: jest-util "^24.8.0" pretty-format "^24.8.0" -jest-environment-jsdom@^24.0.0, jest-environment-jsdom@^24.8.0: +jest-environment-jsdom-sixteen@^1.0.0: + version "1.0.3" + resolved "https://registry.yarnpkg.com/jest-environment-jsdom-sixteen/-/jest-environment-jsdom-sixteen-1.0.3.tgz#e222228fac537ef15cca5ad470b19b47d9690165" + integrity sha512-CwMqDUUfSl808uGPWXlNA1UFkWFgRmhHvyAjhCmCry6mYq4b/nn80MMN7tglqo5XgrANIs/w+mzINPzbZ4ZZrQ== + dependencies: + "@jest/fake-timers" "^25.1.0" + jest-mock "^25.1.0" + jest-util "^25.1.0" + jsdom "^16.2.1" + +jest-environment-jsdom@^24.8.0: version "24.8.0" resolved "https://registry.yarnpkg.com/jest-environment-jsdom/-/jest-environment-jsdom-24.8.0.tgz#300f6949a146cabe1c9357ad9e9ecf9f43f38857" integrity sha512-qbvgLmR7PpwjoFjM/sbuqHJt/NCkviuq9vus9NBn/76hhSidO+Z6Bn9tU8friecegbJL8gzZQEMZBQlFWDCwAQ== @@ -6730,6 +6833,20 @@ jest-message-util@^24.8.0: slash "^2.0.0" stack-utils "^1.0.1" +jest-message-util@^25.5.0: + version "25.5.0" + resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-25.5.0.tgz#ea11d93204cc7ae97456e1d8716251185b8880ea" + integrity sha512-ezddz3YCT/LT0SKAmylVyWWIGYoKHOFOFXx3/nA4m794lfVUskMcwhip6vTgdVrOtYdjeQeis2ypzes9mZb4EA== + dependencies: + "@babel/code-frame" "^7.0.0" + "@jest/types" "^25.5.0" + "@types/stack-utils" "^1.0.1" + chalk "^3.0.0" + graceful-fs "^4.2.4" + micromatch "^4.0.2" + slash "^3.0.0" + stack-utils "^1.0.1" + jest-mock@^24.8.0: version "24.8.0" resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-24.8.0.tgz#2f9d14d37699e863f1febf4e4d5a33b7fdbbde56" @@ -6737,6 +6854,13 @@ jest-mock@^24.8.0: dependencies: "@jest/types" "^24.8.0" +jest-mock@^25.1.0, jest-mock@^25.5.0: + version "25.5.0" + resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-25.5.0.tgz#a91a54dabd14e37ecd61665d6b6e06360a55387a" + integrity sha512-eXWuTV8mKzp/ovHc5+3USJMYsTBhyQ+5A1Mak35dey/RG8GlM4YWVylZuGgVXinaW6tpvk/RSecmF37FKUlpXA== + dependencies: + "@jest/types" "^25.5.0" + jest-pnp-resolver@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/jest-pnp-resolver/-/jest-pnp-resolver-1.2.1.tgz#ecdae604c077a7fbc70defb6d517c3c1c898923a" @@ -6867,6 +6991,17 @@ jest-util@^24.0.0, jest-util@^24.8.0: slash "^2.0.0" source-map "^0.6.0" +jest-util@^25.1.0, jest-util@^25.5.0: + version "25.5.0" + resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-25.5.0.tgz#31c63b5d6e901274d264a4fec849230aa3fa35b0" + integrity sha512-KVlX+WWg1zUTB9ktvhsg2PXZVdkI1NBevOJSkTKYAyXyH4QSvh+Lay/e/v+bmaFfrkfx43xD8QTfgobzlEXdIA== + dependencies: + "@jest/types" "^25.5.0" + chalk "^3.0.0" + graceful-fs "^4.2.4" + is-ci "^2.0.0" + make-dir "^3.0.0" + jest-validate@^24.0.0, jest-validate@^24.8.0: version "24.8.0" resolved "https://registry.yarnpkg.com/jest-validate/-/jest-validate-24.8.0.tgz#624c41533e6dfe356ffadc6e2423a35c2d3b4849" @@ -7036,6 +7171,38 @@ jsdom@^11.5.1: ws "^5.2.0" xml-name-validator "^3.0.0" +jsdom@^16.2.1: + version "16.2.2" + resolved "https://registry.yarnpkg.com/jsdom/-/jsdom-16.2.2.tgz#76f2f7541646beb46a938f5dc476b88705bedf2b" + integrity sha512-pDFQbcYtKBHxRaP55zGXCJWgFHkDAYbKcsXEK/3Icu9nKYZkutUXfLBwbD+09XDutkYSHcgfQLZ0qvpAAm9mvg== + dependencies: + abab "^2.0.3" + acorn "^7.1.1" + acorn-globals "^6.0.0" + cssom "^0.4.4" + cssstyle "^2.2.0" + data-urls "^2.0.0" + decimal.js "^10.2.0" + domexception "^2.0.1" + escodegen "^1.14.1" + html-encoding-sniffer "^2.0.1" + is-potential-custom-element-name "^1.0.0" + nwsapi "^2.2.0" + parse5 "5.1.1" + request "^2.88.2" + request-promise-native "^1.0.8" + saxes "^5.0.0" + symbol-tree "^3.2.4" + tough-cookie "^3.0.1" + w3c-hr-time "^1.0.2" + w3c-xmlserializer "^2.0.0" + webidl-conversions "^6.0.0" + whatwg-encoding "^1.0.5" + whatwg-mimetype "^2.3.0" + whatwg-url "^8.0.0" + ws "^7.2.3" + xml-name-validator "^3.0.0" + jsesc@^2.5.1: version "2.5.2" resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-2.5.2.tgz#80564d2e483dacf6e8ef209650a67df3f0c283a4" @@ -7489,7 +7656,7 @@ lodash.upperfirst@4.3.1: resolved "https://registry.yarnpkg.com/lodash.upperfirst/-/lodash.upperfirst-4.3.1.tgz#1365edf431480481ef0d1c68957a5ed99d49f7ce" integrity sha1-E2Xt9DFIBIHvDRxolXpe2Z1J984= -lodash@^4.0.0, lodash@^4.13.1, lodash@^4.17.10, lodash@^4.17.11, lodash@^4.17.13, lodash@^4.17.14, lodash@^4.17.15, lodash@~4.17.10: +lodash@^4.0.0, lodash@^4.17.10, lodash@^4.17.11, lodash@^4.17.13, lodash@^4.17.14, lodash@^4.17.15, lodash@~4.17.10: version "4.17.15" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.15.tgz#b447f6670a0455bbfeedd11392eff330ea097548" integrity sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A== @@ -7524,6 +7691,13 @@ loglevel@^1.6.6: resolved "https://registry.yarnpkg.com/loglevel/-/loglevel-1.6.7.tgz#b3e034233188c68b889f5b862415306f565e2c56" integrity sha512-cY2eLFrQSAfVPhCgH1s7JI73tMbg9YC3v3+ZHVW67sBS7UxWzNEk/ZBbSfLykBWHp33dqqtOv82gjhKEi81T/A== +lolex@^5.0.0: + version "5.1.2" + resolved "https://registry.yarnpkg.com/lolex/-/lolex-5.1.2.tgz#953694d098ce7c07bc5ed6d0e42bc6c0c6d5a367" + integrity sha512-h4hmjAvHTmd+25JSwrtTIuwbKdwg5NzZVRMLn9saij4SZaepCrTCxPr35H/3bjwfMJtN+t3CX8672UIkglz28A== + dependencies: + "@sinonjs/commons" "^1.7.0" + longest-streak@^2.0.1: version "2.0.2" resolved "https://registry.yarnpkg.com/longest-streak/-/longest-streak-2.0.2.tgz#2421b6ba939a443bb9ffebf596585a50b4c38e2e" @@ -7870,7 +8044,7 @@ micromatch@^3.0.4, micromatch@^3.1.10, micromatch@^3.1.4, micromatch@^3.1.6: snapdragon "^0.8.1" to-regex "^3.0.2" -micromatch@^4.0.0: +micromatch@^4.0.0, micromatch@^4.0.2: version "4.0.2" resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.2.tgz#4fcb0999bf9fbc2fcbdd212f6d629b9a56c39259" integrity sha512-y7FpHSbMUMoyPbYUSzO6PaZ6FyRnQOpHuKwbo1G+Knck95XVU4QAiKdGEnj5wwoS7PlOgthX/09u5iFJ+aYf5Q== @@ -8366,10 +8540,10 @@ number-is-nan@^1.0.0: resolved "https://registry.yarnpkg.com/number-is-nan/-/number-is-nan-1.0.1.tgz#097b602b53422a522c1afb8790318336941a011d" integrity sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0= -nwsapi@^2.0.7: - version "2.0.9" - resolved "https://registry.yarnpkg.com/nwsapi/-/nwsapi-2.0.9.tgz#77ac0cdfdcad52b6a1151a84e73254edc33ed016" - integrity sha512-nlWFSCTYQcHk/6A9FFnfhKc14c3aFhfdNBXgo8Qgi9QTBu/qg3Ww+Uiz9wMzXd1T8GFxPc2QIHB6Qtf2XFryFQ== +nwsapi@^2.0.7, nwsapi@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/nwsapi/-/nwsapi-2.2.0.tgz#204879a9e3d068ff2a55139c2c772780681a38b7" + integrity sha512-h2AatdwYH+JHiZpv7pt/gSX1XoRGb7L/qSIeuqA6GwYoF9w1vP1cw42TO0aI2pNyshRK5893hNSl+1//vHK7hQ== oauth-sign@~0.9.0: version "0.9.0" @@ -8767,10 +8941,10 @@ parse5@4.0.0: resolved "https://registry.yarnpkg.com/parse5/-/parse5-4.0.0.tgz#6d78656e3da8d78b4ec0b906f7c08ef1dfe3f608" integrity sha512-VrZ7eOd3T1Fk4XWNXMgiGBK/z0MG48BWG2uQNU4I72fkQuKUTZpl+u9k+CxEG0twMVzSmXEEz12z5Fnw1jIQFA== -parse5@^5: - version "5.0.0" - resolved "https://registry.yarnpkg.com/parse5/-/parse5-5.0.0.tgz#4d02710d44f3c3846197a11e205d4ef17842b81a" - integrity sha512-0ywuiUOnpWWeil5grH2rxjyTJoeQVwyBuO2si6QIU9dWtj2npjuyK1HaY1RbLnVfDhEbhyAPNUBKRK0Xj2xE0w== +parse5@5.1.1, parse5@^5: + version "5.1.1" + resolved "https://registry.yarnpkg.com/parse5/-/parse5-5.1.1.tgz#f68e4e5ba1852ac2cadc00f4555fff6c2abb6178" + integrity sha512-ugq4DFI0Ptb+WWjAdOK16+u/nHfiIrcE+sh8kZMaM0WllQKLI9rOUq6c2b7cwPkXdzfQESqvoqK6ug7U/Yyzug== parseqs@0.0.5: version "0.0.5" @@ -9390,10 +9564,10 @@ pseudomap@^1.0.2: resolved "https://registry.yarnpkg.com/pseudomap/-/pseudomap-1.0.2.tgz#f052a28da70e618917ef0a8ac34c1ae5a68286b3" integrity sha1-8FKijacOYYkX7wqKw0wa5aaChrM= -psl@^1.1.24: - version "1.1.29" - resolved "https://registry.yarnpkg.com/psl/-/psl-1.1.29.tgz#60f580d360170bb722a797cc704411e6da850c67" - integrity sha512-AeUmQ0oLN02flVHXWh9sSJF7mcdFq0ppid/JkErufc3hGIV/AMa8Fo9VgDo/cT2jFdOWoFvHp90qqBH54W+gjQ== +psl@^1.1.28: + version "1.8.0" + resolved "https://registry.yarnpkg.com/psl/-/psl-1.8.0.tgz#9326f8bcfb013adcc005fdff056acce020e51c24" + integrity sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ== pstree.remy@^1.1.7: version "1.1.8" @@ -9436,17 +9610,12 @@ pumpify@^1.3.3: inherits "^2.0.3" pump "^2.0.0" -punycode@1.3.2: +punycode@1.3.2, punycode@^1.2.4: version "1.3.2" resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.3.2.tgz#9653a036fb7c1ee42342f2325cceefea3926c48d" integrity sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0= -punycode@^1.2.4, punycode@^1.4.1: - version "1.4.1" - resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.4.1.tgz#c0d5a63b2718800ad8e1eb0fa5269c84dd41845e" - integrity sha1-wNWmOycYgArY4esPpSachN1BhF4= - -punycode@^2.1.0: +punycode@^2.1.0, punycode@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec" integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A== @@ -9842,26 +10011,26 @@ replace-ext@1.0.0: resolved "https://registry.yarnpkg.com/replace-ext/-/replace-ext-1.0.0.tgz#de63128373fcbf7c3ccfa4de5a480c45a67958eb" integrity sha1-3mMSg3P8v3w8z6TeWkgMRaZ5WOs= -request-promise-core@1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/request-promise-core/-/request-promise-core-1.1.1.tgz#3eee00b2c5aa83239cfb04c5700da36f81cd08b6" - integrity sha1-Pu4AssWqgyOc+wTFcA2jb4HNCLY= +request-promise-core@1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/request-promise-core/-/request-promise-core-1.1.3.tgz#e9a3c081b51380dfea677336061fea879a829ee9" + integrity sha512-QIs2+ArIGQVp5ZYbWD5ZLCY29D5CfWizP8eWnm8FoGD1TX61veauETVQbrV60662V0oFBkrDOuaBI8XgtuyYAQ== dependencies: - lodash "^4.13.1" + lodash "^4.17.15" -request-promise-native@^1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/request-promise-native/-/request-promise-native-1.0.5.tgz#5281770f68e0c9719e5163fd3fab482215f4fda5" - integrity sha1-UoF3D2jgyXGeUWP9P6tIIhX0/aU= +request-promise-native@^1.0.5, request-promise-native@^1.0.8: + version "1.0.8" + resolved "https://registry.yarnpkg.com/request-promise-native/-/request-promise-native-1.0.8.tgz#a455b960b826e44e2bf8999af64dff2bfe58cb36" + integrity sha512-dapwLGqkHtwL5AEbfenuzjTYg35Jd6KPytsC2/TLkVMz8rm+tNt72MGUWT1RP/aYawMpN6HqbNGBQaRcBtjQMQ== dependencies: - request-promise-core "1.1.1" - stealthy-require "^1.1.0" - tough-cookie ">=2.3.3" + request-promise-core "1.1.3" + stealthy-require "^1.1.1" + tough-cookie "^2.3.3" -request@^2.87.0, request@^2.88.0: - version "2.88.0" - resolved "https://registry.yarnpkg.com/request/-/request-2.88.0.tgz#9c2fca4f7d35b592efe57c7f0a55e81052124fef" - integrity sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg== +request@^2.87.0, request@^2.88.0, request@^2.88.2: + version "2.88.2" + resolved "https://registry.yarnpkg.com/request/-/request-2.88.2.tgz#d73c918731cb5a87da047e207234146f664d12b3" + integrity sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw== dependencies: aws-sign2 "~0.7.0" aws4 "^1.8.0" @@ -9870,7 +10039,7 @@ request@^2.87.0, request@^2.88.0: extend "~3.0.2" forever-agent "~0.6.1" form-data "~2.3.2" - har-validator "~5.1.0" + har-validator "~5.1.3" http-signature "~1.2.0" is-typedarray "~1.0.0" isstream "~0.1.2" @@ -9880,7 +10049,7 @@ request@^2.87.0, request@^2.88.0: performance-now "^2.1.0" qs "~6.5.2" safe-buffer "^5.1.2" - tough-cookie "~2.4.3" + tough-cookie "~2.5.0" tunnel-agent "^0.6.0" uuid "^3.3.2" @@ -10124,6 +10293,13 @@ sax@>=0.6.0, sax@^1.2.4: resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9" integrity sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw== +saxes@^5.0.0: + version "5.0.1" + resolved "https://registry.yarnpkg.com/saxes/-/saxes-5.0.1.tgz#eebab953fa3b7608dbe94e5dadb15c888fa6696d" + integrity sha512-5LBh1Tls8c9xgGjw3QrMwETmTMVk0oFgvrFSvWx62llR2hcEInrKNZ2GZCCuuy2lvWrdl5jhbpeqc5hRYKFOcw== + dependencies: + xmlchars "^2.2.0" + schema-utils@^0.4.0: version "0.4.5" resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-0.4.5.tgz#21836f0608aac17b78f9e3e24daff14a5ca13a3e" @@ -10666,7 +10842,7 @@ stdout-stream@^1.4.0: dependencies: readable-stream "^2.0.1" -stealthy-require@^1.1.0: +stealthy-require@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/stealthy-require/-/stealthy-require-1.1.1.tgz#35b09875b4ff49f26a777e509b3090a3226bf24b" integrity sha1-NbCYdbT/SfJqd35QmzCQoyJr8ks= @@ -11015,10 +11191,10 @@ symbol-observable@^1.0.2: resolved "https://registry.yarnpkg.com/symbol-observable/-/symbol-observable-1.2.0.tgz#c22688aed4eab3cdc2dfeacbb561660560a00804" integrity sha512-e900nM8RRtGhlV36KGEU9k65K3mPb1WV70OdjfxlG2EAuM1noi/E/BaW/uMhL7bPEssK8QV57vN3esixjUvcXQ== -symbol-tree@^3.2.2: - version "3.2.2" - resolved "https://registry.yarnpkg.com/symbol-tree/-/symbol-tree-3.2.2.tgz#ae27db38f660a7ae2e1c3b7d1bc290819b8519e6" - integrity sha1-rifbOPZgp64uHDt9G8KQgZuFGeY= +symbol-tree@^3.2.2, symbol-tree@^3.2.4: + version "3.2.4" + resolved "https://registry.yarnpkg.com/symbol-tree/-/symbol-tree-3.2.4.tgz#430637d248ba77e078883951fb9aa0eed7c63fa2" + integrity sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw== table@^5.2.3: version "5.4.4" @@ -11300,13 +11476,22 @@ touch@^3.1.0: dependencies: nopt "~1.0.10" -tough-cookie@>=2.3.3, tough-cookie@^2.3.4, tough-cookie@~2.4.3: - version "2.4.3" - resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.4.3.tgz#53f36da3f47783b0925afa06ff9f3b165280f781" - integrity sha512-Q5srk/4vDM54WJsJio3XNn6K2sCG+CQ8G5Wz6bZhRZoAe/+TxjWB/GlFAnYEbkYVlON9FMk/fE3h2RLpPXo4lQ== +tough-cookie@^2.3.3, tough-cookie@^2.3.4, tough-cookie@~2.5.0: + version "2.5.0" + resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.5.0.tgz#cd9fb2a0aa1d5a12b473bd9fb96fa3dcff65ade2" + integrity sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g== dependencies: - psl "^1.1.24" - punycode "^1.4.1" + psl "^1.1.28" + punycode "^2.1.1" + +tough-cookie@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-3.0.1.tgz#9df4f57e739c26930a018184887f4adb7dca73b2" + integrity sha512-yQyJ0u4pZsv9D4clxO69OEjLWYw+jbgspjTue4lTQZLfV0c5l1VmK2y1JK8E9ahdpltPOaAThPcp5nKPUgSnsg== + dependencies: + ip-regex "^2.1.0" + psl "^1.1.28" + punycode "^2.1.1" tr46@^1.0.1: version "1.0.1" @@ -11315,6 +11500,13 @@ tr46@^1.0.1: dependencies: punycode "^2.1.0" +tr46@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/tr46/-/tr46-2.0.2.tgz#03273586def1595ae08fedb38d7733cee91d2479" + integrity sha512-3n1qG+/5kg+jrbTzwAykB5yRYtQCTqOGKq5U5PE3b0a1/mzo6snDhjGS0zJVJunO0NrT3Dg1MLy5TjWP/UJppg== + dependencies: + punycode "^2.1.1" + tributejs@4.1.3: version "4.1.3" resolved "https://registry.yarnpkg.com/tributejs/-/tributejs-4.1.3.tgz#2e1be7d9a1e403ed4c394f91d859812267e4691c" @@ -11432,6 +11624,11 @@ type-check@~0.3.2: dependencies: prelude-ls "~1.1.2" +type-detect@4.0.8: + version "4.0.8" + resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-4.0.8.tgz#7646fb5f18871cfbb7749e69bd39a6388eb7450c" + integrity sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g== + type-fest@^0.5.2: version "0.5.2" resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.5.2.tgz#d6ef42a0356c6cd45f49485c3b6281fc148e48a2" @@ -11958,18 +12155,25 @@ vuex@^3.1.0: resolved "https://registry.yarnpkg.com/vuex/-/vuex-3.1.0.tgz#634b81515cf0cfe976bd1ffe9601755e51f843b9" integrity sha512-mdHeHT/7u4BncpUZMlxNaIdcN/HIt1GsGG5LKByArvYG/v6DvHcOxvDCts+7SRdCoIRGllK8IMZvQtQXLppDYg== -w3c-hr-time@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/w3c-hr-time/-/w3c-hr-time-1.0.1.tgz#82ac2bff63d950ea9e3189a58a65625fedf19045" - integrity sha1-gqwr/2PZUOqeMYmlimViX+3xkEU= +w3c-hr-time@^1.0.1, w3c-hr-time@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/w3c-hr-time/-/w3c-hr-time-1.0.2.tgz#0a89cdf5cc15822df9c360543676963e0cc308cd" + integrity sha512-z8P5DvDNjKDoFIHK7q8r8lackT6l+jo/Ye3HOle7l9nICP9lf1Ci25fy9vHd0JOWewkIFzXIEig3TdKT7JQ5fQ== dependencies: - browser-process-hrtime "^0.1.2" + browser-process-hrtime "^1.0.0" w3c-keyname@^1.1.8: version "1.1.8" resolved "https://registry.yarnpkg.com/w3c-keyname/-/w3c-keyname-1.1.8.tgz#4e2219663760fd6535b7a1550f1552d71fc9372c" integrity sha512-2HAdug8GTiu3b4NYhssdtY8PXRue3ICnh1IlxvZYl+hiINRq0GfNWei3XOPDg8L0PsxbmYjWVLuLj6BMRR/9vA== +w3c-xmlserializer@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/w3c-xmlserializer/-/w3c-xmlserializer-2.0.0.tgz#3e7104a05b75146cc60f564380b7f683acf1020a" + integrity sha512-4tzD0mF8iSiMiNs30BiLO3EpfGLZUT2MSX/G+o7ZywDzliWQ3OPtTZ0PTC3B3ca1UAf4cJMHB+2Bf56EriJuRA== + dependencies: + xml-name-validator "^3.0.0" + walker@^1.0.7, walker@~1.0.5: version "1.0.7" resolved "https://registry.yarnpkg.com/walker/-/walker-1.0.7.tgz#2f7f9b8fd10d677262b18a884e28d19618e028fb" @@ -12007,6 +12211,16 @@ webidl-conversions@^4.0.2: resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-4.0.2.tgz#a855980b1f0b6b359ba1d5d9fb39ae941faa63ad" integrity sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg== +webidl-conversions@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-5.0.0.tgz#ae59c8a00b121543a2acc65c0434f57b0fc11aff" + integrity sha512-VlZwKPCkYKxQgeSbH5EyngOmRp7Ww7I9rQLERETtf5ofd9pGeswWiOtogpEO850jziPRarreGxn5QIiTqpb2wA== + +webidl-conversions@^6.0.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-6.1.0.tgz#9111b4d7ea80acd40f5270d666621afa78b69514" + integrity sha512-qBIvFLGiBpLjfwmYAaHPXsn+ho5xZnGvyGvsarywGNc8VyQJUMHJ8OBKGGrPER0okBeMDaan4mNBlgBROxuI8w== + webpack-bundle-analyzer@^3.6.0: version "3.6.0" resolved "https://registry.yarnpkg.com/webpack-bundle-analyzer/-/webpack-bundle-analyzer-3.6.0.tgz#39b3a8f829ca044682bc6f9e011c95deb554aefd" @@ -12155,14 +12369,14 @@ websocket-extensions@>=0.1.1: resolved "https://registry.yarnpkg.com/websocket-extensions/-/websocket-extensions-0.1.1.tgz#76899499c184b6ef754377c2dbb0cd6cb55d29e7" integrity sha1-domUmcGEtu91Q3fC27DNbLVdKec= -whatwg-encoding@^1.0.1, whatwg-encoding@^1.0.3: +whatwg-encoding@^1.0.1, whatwg-encoding@^1.0.3, whatwg-encoding@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/whatwg-encoding/-/whatwg-encoding-1.0.5.tgz#5abacf777c32166a51d085d6b4f3e7d27113ddb0" integrity sha512-b5lim54JOPN9HtzvK9HFXvBma/rnfFeqsic0hSpjtDbVxR3dJKLc+KB4V6GgiGOvl7CY/KNh8rxSo9DKQrnUEw== dependencies: iconv-lite "0.4.24" -whatwg-mimetype@^2.1.0, whatwg-mimetype@^2.2.0: +whatwg-mimetype@^2.1.0, whatwg-mimetype@^2.2.0, whatwg-mimetype@^2.3.0: version "2.3.0" resolved "https://registry.yarnpkg.com/whatwg-mimetype/-/whatwg-mimetype-2.3.0.tgz#3d4b1e0312d2079879f826aff18dbeeca5960fbf" integrity sha512-M4yMwr6mAnQz76TbJm914+gPpB/nCwvZbJU28cUD6dR004SAxDLOOSUaB1JDRqLtaOV/vi0IC5lEAGFgrjGv/g== @@ -12185,6 +12399,15 @@ whatwg-url@^7.0.0: tr46 "^1.0.1" webidl-conversions "^4.0.2" +whatwg-url@^8.0.0: + version "8.1.0" + resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-8.1.0.tgz#c628acdcf45b82274ce7281ee31dd3c839791771" + integrity sha512-vEIkwNi9Hqt4TV9RdnaBPNt+E2Sgmo3gePebCRgZ1R7g6d23+53zCTnuB0amKI4AXq6VM8jj2DUAa0S1vjJxkw== + dependencies: + lodash.sortby "^4.7.0" + tr46 "^2.0.2" + webidl-conversions "^5.0.0" + which-module@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/which-module/-/which-module-1.0.0.tgz#bba63ca861948994ff307736089e3b96026c2a4f" @@ -12303,6 +12526,11 @@ ws@^6.0.0, ws@^6.2.1: dependencies: async-limiter "~1.0.0" +ws@^7.2.3: + version "7.3.0" + resolved "https://registry.yarnpkg.com/ws/-/ws-7.3.0.tgz#4b2f7f219b3d3737bc1a2fbf145d825b94d38ffd" + integrity sha512-iFtXzngZVXPGgpTlP1rBqsUK82p9tKqsWRPg5L56egiljujJT3vGAYnHANvFxBieXrTFavhzhxW52jnaWV+w2w== + ws@~3.3.1: version "3.3.3" resolved "https://registry.yarnpkg.com/ws/-/ws-3.3.3.tgz#f1cf84fe2d5e901ebce94efaece785f187a228f2" @@ -12358,6 +12586,11 @@ xmlbuilder@~9.0.1: resolved "https://registry.yarnpkg.com/xmlbuilder/-/xmlbuilder-9.0.7.tgz#132ee63d2ec5565c557e20f4c22df9aca686b10d" integrity sha1-Ey7mPS7FVlxVfiD0wi35rKaGsQ0= +xmlchars@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/xmlchars/-/xmlchars-2.2.0.tgz#060fe1bcb7f9c76fe2a17db86a9bc3ab894210cb" + integrity sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw== + xmlcreate@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/xmlcreate/-/xmlcreate-1.0.2.tgz#fa6bf762a60a413fb3dd8f4b03c5b269238d308f"