diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2020-08-14 00:10:04 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2020-08-14 00:10:04 +0300 |
commit | 3825437c53474e3d6f3a7d82ef4f26583212531e (patch) | |
tree | 1bb207713ff4e28c10a9a9e3f7c9d3e509001733 | |
parent | c787c1559e9e558b83e78354823eb54b9fe8c718 (diff) |
Add latest changes from gitlab-org/gitlab@master
64 files changed, 1050 insertions, 268 deletions
diff --git a/GITALY_SERVER_VERSION b/GITALY_SERVER_VERSION index a78697498ca..c415db06b25 100644 --- a/GITALY_SERVER_VERSION +++ b/GITALY_SERVER_VERSION @@ -1 +1 @@ -187f550dce577f1fb39503976a30a73a58b2b8e1 +1fc116768ec27739965f6394d2fd2eddea22f621 diff --git a/app/assets/javascripts/layout_nav.js b/app/assets/javascripts/layout_nav.js index 4314e5e1afb..0ecf3301250 100644 --- a/app/assets/javascripts/layout_nav.js +++ b/app/assets/javascripts/layout_nav.js @@ -1,6 +1,7 @@ import $ from 'jquery'; import ContextualSidebar from './contextual_sidebar'; import initFlyOutNav from './fly_out_nav'; +import initWhatsNew from '~/whats_new'; function hideEndFade($scrollingTabs) { $scrollingTabs.each(function scrollTabsLoop() { @@ -20,6 +21,7 @@ export default function initLayoutNav() { contextualSidebar.bindEvents(); initFlyOutNav(); + initWhatsNew(); // We need to init it on DomContentLoaded as others could also call it $(document).on('init.scrolling-tabs', () => { diff --git a/app/assets/javascripts/whats_new/components/app.vue b/app/assets/javascripts/whats_new/components/app.vue new file mode 100644 index 00000000000..d974556cb9e --- /dev/null +++ b/app/assets/javascripts/whats_new/components/app.vue @@ -0,0 +1,29 @@ +<script> +import { mapState, mapActions } from 'vuex'; +import { GlDrawer } from '@gitlab/ui'; + +export default { + components: { + GlDrawer, + }, + computed: { + ...mapState(['open']), + }, + methods: { + ...mapActions(['closeDrawer']), + }, +}; +</script> + +<template> + <div> + <gl-drawer class="mt-6" :open="open" @close="closeDrawer"> + <template #header> + <h4>{{ __("What's new at GitLab") }}</h4> + </template> + <template> + <div></div> + </template> + </gl-drawer> + </div> +</template> diff --git a/app/assets/javascripts/whats_new/components/trigger.vue b/app/assets/javascripts/whats_new/components/trigger.vue new file mode 100644 index 00000000000..e6c48e92888 --- /dev/null +++ b/app/assets/javascripts/whats_new/components/trigger.vue @@ -0,0 +1,19 @@ +<script> +import { mapActions } from 'vuex'; +import { GlButton } from '@gitlab/ui'; + +export default { + components: { + GlButton, + }, + methods: { + ...mapActions(['openDrawer']), + }, +}; +</script> + +<template> + <li> + <gl-button variant="link" @click="openDrawer">{{ __("See what's new at GitLab") }}</gl-button> + </li> +</template> diff --git a/app/assets/javascripts/whats_new/index.js b/app/assets/javascripts/whats_new/index.js new file mode 100644 index 00000000000..c9ee3404d2a --- /dev/null +++ b/app/assets/javascripts/whats_new/index.js @@ -0,0 +1,32 @@ +import Vue from 'vue'; +import App from './components/app.vue'; +import Trigger from './components/trigger.vue'; +import store from './store'; + +export default () => { + // eslint-disable-next-line no-new + new Vue({ + el: document.getElementById('whats-new-app'), + store, + components: { + App, + }, + + render(createElement) { + return createElement('app'); + }, + }); + + // eslint-disable-next-line no-new + new Vue({ + el: document.getElementById('whats-new-trigger'), + store, + components: { + Trigger, + }, + + render(createElement) { + return createElement('trigger'); + }, + }); +}; diff --git a/app/assets/javascripts/whats_new/store/actions.js b/app/assets/javascripts/whats_new/store/actions.js new file mode 100644 index 00000000000..53488413d9e --- /dev/null +++ b/app/assets/javascripts/whats_new/store/actions.js @@ -0,0 +1,10 @@ +import * as types from './mutation_types'; + +export default { + closeDrawer({ commit }) { + commit(types.CLOSE_DRAWER); + }, + openDrawer({ commit }) { + commit(types.OPEN_DRAWER); + }, +}; diff --git a/app/assets/javascripts/whats_new/store/index.js b/app/assets/javascripts/whats_new/store/index.js new file mode 100644 index 00000000000..aea980060aa --- /dev/null +++ b/app/assets/javascripts/whats_new/store/index.js @@ -0,0 +1,13 @@ +import Vue from 'vue'; +import Vuex from 'vuex'; +import actions from './actions'; +import mutations from './mutations'; +import state from './state'; + +Vue.use(Vuex); + +export default new Vuex.Store({ + actions, + mutations, + state, +}); diff --git a/app/assets/javascripts/whats_new/store/mutation_types.js b/app/assets/javascripts/whats_new/store/mutation_types.js new file mode 100644 index 00000000000..daa65230101 --- /dev/null +++ b/app/assets/javascripts/whats_new/store/mutation_types.js @@ -0,0 +1,2 @@ +export const CLOSE_DRAWER = 'CLOSE_DRAWER'; +export const OPEN_DRAWER = 'OPEN_DRAWER'; diff --git a/app/assets/javascripts/whats_new/store/mutations.js b/app/assets/javascripts/whats_new/store/mutations.js new file mode 100644 index 00000000000..f7e84ee81a9 --- /dev/null +++ b/app/assets/javascripts/whats_new/store/mutations.js @@ -0,0 +1,10 @@ +import * as types from './mutation_types'; + +export default { + [types.CLOSE_DRAWER](state) { + state.open = false; + }, + [types.OPEN_DRAWER](state) { + state.open = true; + }, +}; diff --git a/app/assets/javascripts/whats_new/store/state.js b/app/assets/javascripts/whats_new/store/state.js new file mode 100644 index 00000000000..97089a095f1 --- /dev/null +++ b/app/assets/javascripts/whats_new/store/state.js @@ -0,0 +1,3 @@ +export default { + open: false, +}; diff --git a/app/assets/stylesheets/framework/dropdowns.scss b/app/assets/stylesheets/framework/dropdowns.scss index 32c276ea6d2..6b742853f8f 100644 --- a/app/assets/stylesheets/framework/dropdowns.scss +++ b/app/assets/stylesheets/framework/dropdowns.scss @@ -312,6 +312,7 @@ > a, button, + .gl-button.btn-link, .menu-item { @include dropdown-link; } diff --git a/app/graphql/mutations/boards/lists/update.rb b/app/graphql/mutations/boards/lists/update.rb new file mode 100644 index 00000000000..7efed3058b3 --- /dev/null +++ b/app/graphql/mutations/boards/lists/update.rb @@ -0,0 +1,52 @@ +# frozen_string_literal: true + +module Mutations + module Boards + module Lists + class Update < BaseMutation + graphql_name 'UpdateBoardList' + + argument :list_id, GraphQL::ID_TYPE, + required: true, + loads: Types::BoardListType, + description: 'Global ID of the list.' + + argument :position, GraphQL::INT_TYPE, + required: false, + description: 'Position of list within the board' + + argument :collapsed, GraphQL::BOOLEAN_TYPE, + required: false, + description: 'Indicates if list is collapsed for this user' + + field :list, + Types::BoardListType, + null: true, + description: 'Mutated list' + + def resolve(list: nil, **args) + raise_resource_not_available_error! unless can_read_list?(list) + update_result = update_list(list, args) + + { + list: update_result[:list], + errors: list.errors.full_messages + } + end + + private + + def update_list(list, args) + service = ::Boards::Lists::UpdateService.new(list.board, current_user, args) + service.execute(list) + end + + def can_read_list?(list) + return false unless list.present? + + Ability.allowed?(current_user, :read_list, list.board) + end + end + end + end +end diff --git a/app/graphql/resolvers/ci_configuration/sast_resolver.rb b/app/graphql/resolvers/ci_configuration/sast_resolver.rb deleted file mode 100644 index e8c42076ea2..00000000000 --- a/app/graphql/resolvers/ci_configuration/sast_resolver.rb +++ /dev/null @@ -1,17 +0,0 @@ -# frozen_string_literal: true - -require "json" - -module Resolvers - module CiConfiguration - class SastResolver < BaseResolver - SAST_UI_SCHEMA_PATH = 'app/validators/json_schemas/security_ci_configuration_schemas/sast_ui_schema.json' - - type ::Types::CiConfiguration::Sast::Type, null: true - - def resolve(**args) - Gitlab::Json.parse(File.read(Rails.root.join(SAST_UI_SCHEMA_PATH))) - end - end - end -end diff --git a/app/graphql/types/board_list_type.rb b/app/graphql/types/board_list_type.rb index 781fd55c41e..70c0794fc90 100644 --- a/app/graphql/types/board_list_type.rb +++ b/app/graphql/types/board_list_type.rb @@ -3,6 +3,8 @@ module Types # rubocop: disable Graphql/AuthorizeTypes class BoardListType < BaseObject + include Gitlab::Utils::StrongMemoize + graphql_name 'BoardList' description 'Represents a list for an issue board' @@ -19,10 +21,31 @@ module Types field :collapsed, GraphQL::BOOLEAN_TYPE, null: true, description: 'Indicates if list is collapsed for this user', resolve: -> (list, _args, ctx) { list.collapsed?(ctx[:current_user]) } + field :issues_count, GraphQL::INT_TYPE, null: true, + description: 'Count of issues in the list' field :issues, ::Types::IssueType.connection_type, null: true, description: 'Board issues', resolver: ::Resolvers::BoardListIssuesResolver + + def issues_count + metadata[:size] + end + + def total_weight + metadata[:total_weight] + end + + def metadata + strong_memoize(:metadata) do + list = self.object + user = context[:current_user] + + Boards::Issues::ListService + .new(list.board.resource_parent, user, board_id: list.board_id, id: list.id) + .metadata + end + end end # rubocop: enable Graphql/AuthorizeTypes end diff --git a/app/graphql/types/mutation_type.rb b/app/graphql/types/mutation_type.rb index ea0e3a8adb7..e143d14676e 100644 --- a/app/graphql/types/mutation_type.rb +++ b/app/graphql/types/mutation_type.rb @@ -16,6 +16,7 @@ module Types mount_mutation Mutations::AwardEmojis::Toggle mount_mutation Mutations::Boards::Issues::IssueMoveList mount_mutation Mutations::Boards::Lists::Create + mount_mutation Mutations::Boards::Lists::Update mount_mutation Mutations::Branches::Create, calls_gitaly: true mount_mutation Mutations::Commits::Create, calls_gitaly: true mount_mutation Mutations::Discussions::ToggleResolve diff --git a/app/graphql/types/project_type.rb b/app/graphql/types/project_type.rb index 523e019b955..5562db69de6 100644 --- a/app/graphql/types/project_type.rb +++ b/app/graphql/types/project_type.rb @@ -175,10 +175,6 @@ module Types description: 'A single environment of the project', resolver: Resolvers::EnvironmentsResolver.single - field :sast_ci_configuration, ::Types::CiConfiguration::Sast::Type, null: true, - description: 'SAST CI configuration for the project', - resolver: ::Resolvers::CiConfiguration::SastResolver - field :issue, Types::IssueType, null: true, diff --git a/app/models/project_services/prometheus_service.rb b/app/models/project_services/prometheus_service.rb index 997c6eba91a..950cd4f6859 100644 --- a/app/models/project_services/prometheus_service.rb +++ b/app/models/project_services/prometheus_service.rb @@ -97,7 +97,13 @@ class PrometheusService < MonitoringService def prometheus_client return unless should_return_client? - options = { allow_local_requests: allow_local_api_url? } + options = { + allow_local_requests: allow_local_api_url?, + # We should choose more conservative timeouts, but some queries we run are now busting our + # default timeouts, which are stricter. We should make those queries faster instead. + # See https://gitlab.com/gitlab-org/gitlab/-/issues/233109 + timeout: 60 + } if behind_iap? # Adds the Authorization header diff --git a/app/services/projects/update_pages_configuration_service.rb b/app/services/projects/update_pages_configuration_service.rb index 1f07d52ed4e..88c17d502df 100644 --- a/app/services/projects/update_pages_configuration_service.rb +++ b/app/services/projects/update_pages_configuration_service.rb @@ -11,6 +11,11 @@ module Projects end def execute + # If the pages were never deployed, we can't write out the config, as the + # directory would not exist. + # https://gitlab.com/gitlab-org/gitlab/-/issues/235139 + return success unless project.pages_deployed? + unless file_equals?(pages_config_file, pages_config_json) update_file(pages_config_file, pages_config_json) reload_daemon diff --git a/app/services/projects/update_service.rb b/app/services/projects/update_service.rb index 53c8489474b..c9ba7cde199 100644 --- a/app/services/projects/update_service.rb +++ b/app/services/projects/update_service.rb @@ -142,6 +142,8 @@ module Projects end def update_pages_config + return unless project.pages_deployed? + if Feature.enabled?(:async_update_pages_config, project) PagesUpdateConfigurationWorker.perform_async(project.id) else diff --git a/app/services/web_hook_service.rb b/app/services/web_hook_service.rb index 792b71b577f..d6cb0729d6f 100644 --- a/app/services/web_hook_service.rb +++ b/app/services/web_hook_service.rb @@ -55,16 +55,17 @@ class WebHookService message: response.to_s } rescue SocketError, OpenSSL::SSL::SSLError, Errno::ECONNRESET, Errno::ECONNREFUSED, Errno::EHOSTUNREACH, Net::OpenTimeout, Net::ReadTimeout, Gitlab::HTTP::BlockedUrlError, Gitlab::HTTP::RedirectionTooDeep, Gitlab::Json::LimitedEncoder::LimitExceeded => e + execution_duration = Gitlab::Metrics::System.monotonic_time - start_time log_execution( trigger: hook_name, url: hook.url, request_data: data, response: InternalErrorResponse.new, - execution_duration: Gitlab::Metrics::System.monotonic_time - start_time, + execution_duration: execution_duration, error_message: e.to_s ) - Gitlab::AppLogger.error("WebHook Error => #{e}") + Gitlab::AppLogger.error("WebHook Error after #{execution_duration.to_i.seconds}s => #{e}") { status: :error, diff --git a/app/validators/json_schemas/security_ci_configuration_schemas/sast_ui_schema.json b/app/validators/json_schemas/security_ci_configuration_schemas/sast_ui_schema.json index cce2b28529f..aa4dd60a9fb 100644 --- a/app/validators/json_schemas/security_ci_configuration_schemas/sast_ui_schema.json +++ b/app/validators/json_schemas/security_ci_configuration_schemas/sast_ui_schema.json @@ -4,83 +4,43 @@ "field" : "SECURE_ANALYZERS_PREFIX", "label" : "Image prefix", "type": "string", - "default_value": "registry.gitlab.com/gitlab-org/security-products/analyzers", - "value": "" + "default_value": "", + "value": "", + "description": "Analyzer image's registry prefix (or Name of the registry providing the analyzers' image)" }, { "field" : "SAST_EXCLUDED_PATHS", "label" : "Excluded Paths", "type": "string", - "default_value": "spec, test, tests, tmp", - "value": "" + "default_value": "", + "value": "", + "description": "Comma-separated list of paths to be excluded from analyzer output. Patterns can be globs, file paths, or folder paths." }, { "field" : "SAST_ANALYZER_IMAGE_TAG", "label" : "Image tag", "type": "string", - "options": [], - "default_value": "2", - "value": "" - }, - { - "field" : "SAST_DISABLED", - "label" : "Disable SAST", - "type": "options", - "options": [ - { - "value" :"true", - "label" : "true (disables SAST)" - }, - { - "value":"false", - "label":"false (enables SAST)" - } - ], - "default_value": "false", - "value": "" + "default_value": "", + "value": "", + "description": "Analyzer image's tag" } ], "pipeline": [ { "field" : "stage", "label" : "Stage", - "type": "dropdown", - "options": [ - { - "value" :"test", - "label" : "test" - }, - { - "value":"build", - "label":"build" - } - ], - "default_value": "test", - "value": "" - }, - { - "field" : "allow_failure", - "label" : "Allow Failure", - "type": "options", - "options": [ - { - "value" :"true", - "label" : "Allows pipeline failure" - }, - { - "value": "false", - "label": "Does not allow pipeline failure" - } - ], - "default_value": "true", - "value": "" + "type": "string", + "default_value": "", + "value": "", + "description": "Pipeline stage in which the scan jobs run" }, { - "field" : "rules", - "label" : "Rules", - "type": "multiline", + "field" : "SEARCH_MAX_DEPTH", + "label" : "Search maximum depth", + "type": "string", "default_value": "", - "value": "" + "value": "", + "description": "Maximum depth of language and framework detection" } ], "analyzers": [ diff --git a/app/views/layouts/header/_default.html.haml b/app/views/layouts/header/_default.html.haml index a191eb8cbdb..ea5a663a654 100644 --- a/app/views/layouts/header/_default.html.haml +++ b/app/views/layouts/header/_default.html.haml @@ -84,5 +84,8 @@ = sprite_icon('ellipsis_h', size: 12, css_class: 'more-icon js-navbar-toggle-right') = sprite_icon('close', size: 12, css_class: 'close-icon js-navbar-toggle-left') +- if ::Feature.enabled?(:whats_new_drawer) + #whats-new-app + - if can?(current_user, :update_user_status, current_user) .js-set-status-modal-wrapper{ data: { current_emoji: current_user.status.present? ? current_user.status.emoji : '', current_message: current_user.status.present? ? current_user.status.message : '' } } diff --git a/app/views/shared/web_hooks/_form.html.haml b/app/views/shared/web_hooks/_form.html.haml index ce85cbd7f07..0f6188fa334 100644 --- a/app/views/shared/web_hooks/_form.html.haml +++ b/app/views/shared/web_hooks/_form.html.haml @@ -72,6 +72,12 @@ %strong Wiki Page events %p.text-muted.ml-1 This URL will be triggered when a wiki page is created/updated + %li + = form.check_box :deployment_events, class: 'form-check-input' + = form.label :deployment_events, class: 'list-label form-check-label ml-1' do + %strong= s_('Webhooks|Deployment events') + %p.text-muted.ml-1 + = s_('Webhooks|This URL will be triggered when a deployment is finished/failed/canceled') .form-group = form.label :enable_ssl_verification, 'SSL verification', class: 'label-bold checkbox' .form-check diff --git a/changelogs/unreleased/229764-graphql-reorder-lists-within-a-board.yml b/changelogs/unreleased/229764-graphql-reorder-lists-within-a-board.yml new file mode 100644 index 00000000000..8b0276614fa --- /dev/null +++ b/changelogs/unreleased/229764-graphql-reorder-lists-within-a-board.yml @@ -0,0 +1,5 @@ +--- +title: Add GraphQL mutation for updating board list position and collapsed/expanded state. +merge_request: 38942 +author: +type: added diff --git a/changelogs/unreleased/233109-http-timeout-handling-improvements.yml b/changelogs/unreleased/233109-http-timeout-handling-improvements.yml new file mode 100644 index 00000000000..253f6eb4514 --- /dev/null +++ b/changelogs/unreleased/233109-http-timeout-handling-improvements.yml @@ -0,0 +1,5 @@ +--- +title: Set longer Prometheus timeouts in PrometheusService +merge_request: 39318 +author: +type: other diff --git a/changelogs/unreleased/235106-board-list-graphql.yml b/changelogs/unreleased/235106-board-list-graphql.yml new file mode 100644 index 00000000000..a9f758e9f52 --- /dev/null +++ b/changelogs/unreleased/235106-board-list-graphql.yml @@ -0,0 +1,5 @@ +--- +title: Add total_weight and issues_count fields to the board list graphQL endpoint +merge_request: 39110 +author: +type: added diff --git a/changelogs/unreleased/25344-fire-webhook-on-new-deployment-2.yml b/changelogs/unreleased/25344-fire-webhook-on-new-deployment-2.yml new file mode 100644 index 00000000000..a50b2fcc0ce --- /dev/null +++ b/changelogs/unreleased/25344-fire-webhook-on-new-deployment-2.yml @@ -0,0 +1,5 @@ +--- +title: Add webhooks for deployments +merge_request: 38902 +author: +type: added diff --git a/changelogs/unreleased/ajk-enable-design_management_reference_filter_gfm_pipeline.yml b/changelogs/unreleased/ajk-enable-design_management_reference_filter_gfm_pipeline.yml new file mode 100644 index 00000000000..2e2af0e9713 --- /dev/null +++ b/changelogs/unreleased/ajk-enable-design_management_reference_filter_gfm_pipeline.yml @@ -0,0 +1,5 @@ +--- +title: Enable design management reference filter by default +merge_request: 39113 +author: +type: changed diff --git a/changelogs/unreleased/coverage_fuzzing_usage_ping.yml b/changelogs/unreleased/coverage_fuzzing_usage_ping.yml new file mode 100644 index 00000000000..d93ac51cfc9 --- /dev/null +++ b/changelogs/unreleased/coverage_fuzzing_usage_ping.yml @@ -0,0 +1,5 @@ +--- +title: Add usage ping for coverage_fuzzing +merge_request: 36960 +author: +type: added diff --git a/changelogs/unreleased/jarv-env-for-disabling-unstructured-log.yml b/changelogs/unreleased/jarv-env-for-disabling-unstructured-log.yml new file mode 100644 index 00000000000..f1b68a90e78 --- /dev/null +++ b/changelogs/unreleased/jarv-env-for-disabling-unstructured-log.yml @@ -0,0 +1,5 @@ +--- +title: Adds an environment variable override to disable unstructured logs +merge_request: 39109 +author: +type: other diff --git a/changelogs/unreleased/update-ado-image-to-v1-0-0.yml b/changelogs/unreleased/update-ado-image-to-v1-0-0.yml new file mode 100644 index 00000000000..41cff3e6d8b --- /dev/null +++ b/changelogs/unreleased/update-ado-image-to-v1-0-0.yml @@ -0,0 +1,5 @@ +--- +title: Update auto-deploy-image to v1.0.0, including a locally vendored auto-deploy-app chart instead of charts.gitlab.io +merge_request: 39272 +author: +type: changed diff --git a/config/initializers/lograge.rb b/config/initializers/lograge.rb index 42c97e4aebd..e3601a9538e 100644 --- a/config/initializers/lograge.rb +++ b/config/initializers/lograge.rb @@ -5,7 +5,7 @@ unless Gitlab::Runtime.sidekiq? Rails.application.configure do config.lograge.enabled = true # Store the lograge JSON files in a separate file - config.lograge.keep_original_rails_log = true + config.lograge.keep_original_rails_log = Gitlab::Utils.to_boolean(ENV.fetch('UNSTRUCTURED_RAILS_LOG', 'true')) # Don't use the Logstash formatter since this requires logstash-event, an # unmaintained gem that monkey patches `Time` config.lograge.formatter = Lograge::Formatters::Json.new diff --git a/db/post_migrate/20200721140507_update_index_for_coverage_fuzzing_telemetry.rb b/db/post_migrate/20200721140507_update_index_for_coverage_fuzzing_telemetry.rb new file mode 100644 index 00000000000..4f884319125 --- /dev/null +++ b/db/post_migrate/20200721140507_update_index_for_coverage_fuzzing_telemetry.rb @@ -0,0 +1,38 @@ +# frozen_string_literal: true + +class UpdateIndexForCoverageFuzzingTelemetry < ActiveRecord::Migration[6.0] + include Gitlab::Database::MigrationHelpers + + DOWNTIME = false + disable_ddl_transaction! + + OLD_INDEX_NAME = 'index_security_ci_builds_on_name_and_id' + NEW_INDEX_NAME = 'index_security_ci_builds_on_name_and_id_parser_features' + + OLD_CLAUSE = "((name)::text = ANY (ARRAY[('container_scanning'::character varying)::text, + ('dast'::character varying)::text, + ('dependency_scanning'::character varying)::text, + ('license_management'::character varying)::text, + ('sast'::character varying)::text, + ('secret_detection'::character varying)::text, + ('license_scanning'::character varying)::text])) AND ((type)::text = 'Ci::Build'::text)" + + NEW_CLAUSE = "((name)::text = ANY (ARRAY[('container_scanning'::character varying)::text, + ('dast'::character varying)::text, + ('dependency_scanning'::character varying)::text, + ('license_management'::character varying)::text, + ('sast'::character varying)::text, + ('secret_detection'::character varying)::text, + ('coverage_fuzzing'::character varying)::text, + ('license_scanning'::character varying)::text])) AND ((type)::text = 'Ci::Build'::text)" + + def up + add_concurrent_index :ci_builds, [:name, :id], name: NEW_INDEX_NAME, where: NEW_CLAUSE + remove_concurrent_index_by_name :ci_builds, OLD_INDEX_NAME + end + + def down + add_concurrent_index :ci_builds, [:name, :id], name: OLD_INDEX_NAME, where: OLD_CLAUSE + remove_concurrent_index_by_name :ci_builds, NEW_INDEX_NAME + end +end diff --git a/db/schema_migrations/20200721140507 b/db/schema_migrations/20200721140507 new file mode 100644 index 00000000000..3fbd67734c7 --- /dev/null +++ b/db/schema_migrations/20200721140507 @@ -0,0 +1 @@ +9a395ab591542ec49fc17c22747d9bbf085fd5f61ae71f49493dfe34f0389059
\ No newline at end of file diff --git a/db/structure.sql b/db/structure.sql index 50e6e4d9dd4..853c686ccd5 100644 --- a/db/structure.sql +++ b/db/structure.sql @@ -20634,7 +20634,7 @@ CREATE UNIQUE INDEX index_scim_oauth_access_tokens_on_group_id_and_token_encrypt CREATE INDEX index_secure_ci_builds_on_user_id_created_at ON public.ci_builds USING btree (user_id, created_at) WHERE (((type)::text = 'Ci::Build'::text) AND ((name)::text = ANY (ARRAY[('container_scanning'::character varying)::text, ('dast'::character varying)::text, ('dependency_scanning'::character varying)::text, ('license_management'::character varying)::text, ('license_scanning'::character varying)::text, ('sast'::character varying)::text, ('secret_detection'::character varying)::text]))); -CREATE INDEX index_security_ci_builds_on_name_and_id ON public.ci_builds USING btree (name, id) WHERE (((name)::text = ANY (ARRAY[('container_scanning'::character varying)::text, ('dast'::character varying)::text, ('dependency_scanning'::character varying)::text, ('license_management'::character varying)::text, ('sast'::character varying)::text, ('secret_detection'::character varying)::text, ('license_scanning'::character varying)::text])) AND ((type)::text = 'Ci::Build'::text)); +CREATE INDEX index_security_ci_builds_on_name_and_id_parser_features ON public.ci_builds USING btree (name, id) WHERE (((name)::text = ANY (ARRAY[('container_scanning'::character varying)::text, ('dast'::character varying)::text, ('dependency_scanning'::character varying)::text, ('license_management'::character varying)::text, ('sast'::character varying)::text, ('secret_detection'::character varying)::text, ('coverage_fuzzing'::character varying)::text, ('license_scanning'::character varying)::text])) AND ((type)::text = 'Ci::Build'::text)); CREATE INDEX index_self_managed_prometheus_alert_events_on_environment_id ON public.self_managed_prometheus_alert_events USING btree (environment_id); diff --git a/doc/administration/environment_variables.md b/doc/administration/environment_variables.md index ecf9464eb91..ebc3848017f 100644 --- a/doc/administration/environment_variables.md +++ b/doc/administration/environment_variables.md @@ -33,6 +33,7 @@ Variable | Type | Description `GITLAB_UNICORN_MEMORY_MIN` | integer | The minimum memory threshold (in bytes) for the Unicorn worker killer `GITLAB_UNICORN_MEMORY_MAX` | integer | The maximum memory threshold (in bytes) for the Unicorn worker killer `GITLAB_SHARED_RUNNERS_REGISTRATION_TOKEN` | string | Sets the initial registration token used for GitLab Runners +`UNSTRUCTURED_RAILS_LOG` | string | Enables the unstructured log in addition to JSON logs (defaults to `true`) ## Complete database variables diff --git a/doc/api/graphql/reference/gitlab_schema.graphql b/doc/api/graphql/reference/gitlab_schema.graphql index 92766ab68e4..78026a82a36 100644 --- a/doc/api/graphql/reference/gitlab_schema.graphql +++ b/doc/api/graphql/reference/gitlab_schema.graphql @@ -1225,6 +1225,11 @@ type BoardList { ): IssueConnection """ + Count of issues in the list + """ + issuesCount: Int + + """ Label of the list """ label: Label @@ -1263,6 +1268,11 @@ type BoardList { Title of the list """ title: String! + + """ + Total weight of all issues in the list + """ + totalWeight: Int } """ @@ -4961,7 +4971,7 @@ Counts of descendent epics. """ type EpicDescendantCount { """ - Number of closed sub-epics + Number of closed child epics """ closedEpics: Int @@ -4971,7 +4981,7 @@ type EpicDescendantCount { closedIssues: Int """ - Number of opened sub-epics + Number of opened child epics """ openedEpics: Int @@ -7355,9 +7365,9 @@ input IssueSetEpicInput { clientMutationId: String """ - Global ID of the epic to be assigned to the issue + Global ID of the epic to be assigned to the issue, epic will be removed if absent or set to null """ - epicId: ID! + epicId: ID """ The IID of the issue to mutate @@ -7710,6 +7720,11 @@ type Iteration { description: String """ + The GitLab Flavored Markdown rendering of `description` + """ + descriptionHtml: String + + """ Timestamp of the iteration due date """ dueDate: Time @@ -9561,6 +9576,7 @@ type Mutation { toggleAwardEmoji(input: ToggleAwardEmojiInput!): ToggleAwardEmojiPayload @deprecated(reason: "Use awardEmojiToggle. Deprecated in 13.2") updateAlertStatus(input: UpdateAlertStatusInput!): UpdateAlertStatusPayload updateBoard(input: UpdateBoardInput!): UpdateBoardPayload + updateBoardList(input: UpdateBoardListInput!): UpdateBoardListPayload updateContainerExpirationPolicy(input: UpdateContainerExpirationPolicyInput!): UpdateContainerExpirationPolicyPayload updateEpic(input: UpdateEpicInput!): UpdateEpicPayload @@ -15574,6 +15590,51 @@ input UpdateBoardInput { } """ +Autogenerated input type of UpdateBoardList +""" +input UpdateBoardListInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + Indicates if list is collapsed for this user + """ + collapsed: Boolean + + """ + Global ID of the list. + """ + listId: ID! + + """ + Position of list within the board + """ + position: Int +} + +""" +Autogenerated return type of UpdateBoardList +""" +type UpdateBoardListPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + Errors encountered during execution of the mutation. + """ + errors: [String!]! + + """ + Mutated list + """ + list: BoardList +} + +""" Autogenerated return type of UpdateBoard """ type UpdateBoardPayload { diff --git a/doc/api/graphql/reference/gitlab_schema.json b/doc/api/graphql/reference/gitlab_schema.json index 719d448b5f4..2a05368e331 100644 --- a/doc/api/graphql/reference/gitlab_schema.json +++ b/doc/api/graphql/reference/gitlab_schema.json @@ -3255,6 +3255,20 @@ "deprecationReason": null }, { + "name": "issuesCount", + "description": "Count of issues in the list", + "args": [ + + ], + "type": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { "name": "label", "description": "Label of the list", "args": [ @@ -3373,6 +3387,20 @@ }, "isDeprecated": false, "deprecationReason": null + }, + { + "name": "totalWeight", + "description": "Total weight of all issues in the list", + "args": [ + + ], + "type": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null } ], "inputFields": null, @@ -13827,7 +13855,7 @@ "fields": [ { "name": "closedEpics", - "description": "Number of closed sub-epics", + "description": "Number of closed child epics", "args": [ ], @@ -13855,7 +13883,7 @@ }, { "name": "openedEpics", - "description": "Number of opened sub-epics", + "description": "Number of opened child epics", "args": [ ], @@ -20359,15 +20387,11 @@ }, { "name": "epicId", - "description": "Global ID of the epic to be assigned to the issue", + "description": "Global ID of the epic to be assigned to the issue, epic will be removed if absent or set to null", "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "SCALAR", - "name": "ID", - "ofType": null - } + "kind": "SCALAR", + "name": "ID", + "ofType": null }, "defaultValue": null }, @@ -21221,6 +21245,20 @@ "deprecationReason": null }, { + "name": "descriptionHtml", + "description": "The GitLab Flavored Markdown rendering of `description`", + "args": [ + + ], + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { "name": "dueDate", "description": "Timestamp of the iteration due date", "args": [ @@ -28404,6 +28442,33 @@ "deprecationReason": null }, { + "name": "updateBoardList", + "description": null, + "args": [ + { + "name": "input", + "description": null, + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "INPUT_OBJECT", + "name": "UpdateBoardListInput", + "ofType": null + } + }, + "defaultValue": null + } + ], + "type": { + "kind": "OBJECT", + "name": "UpdateBoardListPayload", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { "name": "updateContainerExpirationPolicy", "description": null, "args": [ @@ -45858,6 +45923,128 @@ "possibleTypes": null }, { + "kind": "INPUT_OBJECT", + "name": "UpdateBoardListInput", + "description": "Autogenerated input type of UpdateBoardList", + "fields": null, + "inputFields": [ + { + "name": "listId", + "description": "Global ID of the list.", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "ID", + "ofType": null + } + }, + "defaultValue": null + }, + { + "name": "position", + "description": "Position of list within the board", + "type": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + }, + "defaultValue": null + }, + { + "name": "collapsed", + "description": "Indicates if list is collapsed for this user", + "type": { + "kind": "SCALAR", + "name": "Boolean", + "ofType": null + }, + "defaultValue": null + }, + { + "name": "clientMutationId", + "description": "A unique identifier for the client performing the mutation.", + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "defaultValue": null + } + ], + "interfaces": null, + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "OBJECT", + "name": "UpdateBoardListPayload", + "description": "Autogenerated return type of UpdateBoardList", + "fields": [ + { + "name": "clientMutationId", + "description": "A unique identifier for the client performing the mutation.", + "args": [ + + ], + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "errors", + "description": "Errors encountered during execution of the mutation.", + "args": [ + + ], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + } + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "list", + "description": "Mutated list", + "args": [ + + ], + "type": { + "kind": "OBJECT", + "name": "BoardList", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [ + + ], + "enumValues": null, + "possibleTypes": null + }, + { "kind": "OBJECT", "name": "UpdateBoardPayload", "description": "Autogenerated return type of UpdateBoard", diff --git a/doc/api/graphql/reference/index.md b/doc/api/graphql/reference/index.md index cadcacb7f48..fac5357dc78 100644 --- a/doc/api/graphql/reference/index.md +++ b/doc/api/graphql/reference/index.md @@ -207,6 +207,7 @@ Represents a list for an issue board | `assignee` | User | Assignee in the list | | `collapsed` | Boolean | Indicates if list is collapsed for this user | | `id` | ID! | ID (global ID) of the list | +| `issuesCount` | Int | Count of issues in the list | | `label` | Label | Label of the list | | `limitMetric` | ListLimitMetric | The current limit metric for the list | | `listType` | String! | Type of the list | @@ -215,6 +216,7 @@ Represents a list for an issue board | `milestone` | Milestone | Milestone of the list | | `position` | Int | Position of list within the board | | `title` | String! | Title of the list | +| `totalWeight` | Int | Total weight of all issues in the list | ## BoardListCreatePayload @@ -805,9 +807,9 @@ Counts of descendent epics. | Name | Type | Description | | --- | ---- | ---------- | -| `closedEpics` | Int | Number of closed sub-epics | +| `closedEpics` | Int | Number of closed child epics | | `closedIssues` | Int | Number of closed epic issues | -| `openedEpics` | Int | Number of opened sub-epics | +| `openedEpics` | Int | Number of opened child epics | | `openedIssues` | Int | Number of opened epic issues | ## EpicDescendantWeights @@ -1165,6 +1167,7 @@ Represents an iteration object. | --- | ---- | ---------- | | `createdAt` | Time! | Timestamp of iteration creation | | `description` | String | Description of the iteration | +| `descriptionHtml` | String | The GitLab Flavored Markdown rendering of `description` | | `dueDate` | Time | Timestamp of the iteration due date | | `id` | ID! | ID of the iteration | | `iid` | ID! | Internal ID of the iteration | @@ -2305,6 +2308,16 @@ Autogenerated return type of UpdateAlertStatus | `issue` | Issue | The issue created after mutation | | `todo` | Todo | The todo after mutation | +## UpdateBoardListPayload + +Autogenerated return type of UpdateBoardList + +| Name | Type | Description | +| --- | ---- | ---------- | +| `clientMutationId` | String | A unique identifier for the client performing the mutation. | +| `errors` | String! => Array | Errors encountered during execution of the mutation. | +| `list` | BoardList | Mutated list | + ## UpdateBoardPayload Autogenerated return type of UpdateBoard diff --git a/doc/user/compliance/compliance_dashboard/img/compliance_dashboard_v13_3_1.png b/doc/user/compliance/compliance_dashboard/img/compliance_dashboard_v13_3_1.png Binary files differnew file mode 100644 index 00000000000..a06f8812b41 --- /dev/null +++ b/doc/user/compliance/compliance_dashboard/img/compliance_dashboard_v13_3_1.png diff --git a/doc/user/compliance/compliance_dashboard/index.md b/doc/user/compliance/compliance_dashboard/index.md index e26a223e47a..5c05725d95b 100644 --- a/doc/user/compliance/compliance_dashboard/index.md +++ b/doc/user/compliance/compliance_dashboard/index.md @@ -17,7 +17,7 @@ for merging into production. To access the Compliance Dashboard for a group, navigate to **{shield}** **Security & Compliance > Compliance** on the group's menu. -![Compliance Dashboard](img/compliance_dashboard_v13_3.png) +![Compliance Dashboard](img/compliance_dashboard_v13_3_1.png) NOTE: **Note:** The Compliance Dashboard shows only the latest MR on each project. @@ -62,3 +62,15 @@ This column has four states: If you do not see the success icon in your Compliance dashboard; please review the above criteria for the Merge Requests project to make sure it complies with the separation of duties described above. + +## Chain of Custody report + +The Chain of Custody report allows customers to export a list of merge commits within the group. +The data provides a comprehensive view with respect to merge commits. It includes the merge commit SHA, +merge request author, merge request ID, merge user, pipeline ID, group name, project name, and merge request approvers. + +To download the Chain of Custody report, navigate to **{shield}** **Security & Compliance > Compliance** on the group's menu and click **List of all merge commits** + +NOTE: **Note:** +The Chain of Custody report download is a CSV file, with a maximum size of 15 MB. +The remaining records are truncated when this limit is reached. diff --git a/doc/user/project/issues/design_management.md b/doc/user/project/issues/design_management.md index 371469a6ed6..5e456c7986c 100644 --- a/doc/user/project/issues/design_management.md +++ b/doc/user/project/issues/design_management.md @@ -276,21 +276,21 @@ This will be rendered as: ### Enable or disable design references **(CORE ONLY)** -Design reference parsing is under development and not ready for production use. It is -deployed behind a feature flag that is **disabled by default**. +Design reference parsing is +deployed behind a feature flag that is **enabled by default**. [GitLab administrators with access to the GitLab Rails console](../../../administration/feature_flags.md) -can enable it for your instance. +can disable it for your instance. -To enable it: +To disable it: ```ruby -Feature.enable(:design_management_reference_filter_gfm_pipeline) +Feature.disable(:design_management_reference_filter_gfm_pipeline) ``` -To disable it: +To re-enable it: ```ruby -Feature.disable(:design_management_reference_filter_gfm_pipeline) +Feature.enable(:design_management_reference_filter_gfm_pipeline) ``` ## Design activity records diff --git a/lib/banzai/filter/design_reference_filter.rb b/lib/banzai/filter/design_reference_filter.rb index 7455dfe00ef..2ab47c5c6db 100644 --- a/lib/banzai/filter/design_reference_filter.rb +++ b/lib/banzai/filter/design_reference_filter.rb @@ -114,7 +114,7 @@ module Banzai end def enabled? - Feature.enabled?(FEATURE_FLAG, parent) + Feature.enabled?(FEATURE_FLAG, parent, default_enabled: true) end end end diff --git a/lib/gitlab/app_logger.rb b/lib/gitlab/app_logger.rb index 3f5e9adf925..a39e7f31886 100644 --- a/lib/gitlab/app_logger.rb +++ b/lib/gitlab/app_logger.rb @@ -5,7 +5,11 @@ module Gitlab LOGGERS = [Gitlab::AppTextLogger, Gitlab::AppJsonLogger].freeze def self.loggers - LOGGERS + if Gitlab::Utils.to_boolean(ENV.fetch('UNSTRUCTURED_RAILS_LOG', 'true')) + LOGGERS + else + [Gitlab::AppJsonLogger] + end end def self.primary_logger diff --git a/lib/gitlab/ci/templates/Jobs/DAST-Default-Branch-Deploy.gitlab-ci.yml b/lib/gitlab/ci/templates/Jobs/DAST-Default-Branch-Deploy.gitlab-ci.yml index 39e27ed14e3..f234008dad4 100644 --- a/lib/gitlab/ci/templates/Jobs/DAST-Default-Branch-Deploy.gitlab-ci.yml +++ b/lib/gitlab/ci/templates/Jobs/DAST-Default-Branch-Deploy.gitlab-ci.yml @@ -1,5 +1,5 @@ .dast-auto-deploy: - image: "registry.gitlab.com/gitlab-org/cluster-integration/auto-deploy-image:v0.17.2" + image: "registry.gitlab.com/gitlab-org/cluster-integration/auto-deploy-image:v1.0.0" dast_environment_deploy: extends: .dast-auto-deploy diff --git a/lib/gitlab/ci/templates/Jobs/Deploy.gitlab-ci.yml b/lib/gitlab/ci/templates/Jobs/Deploy.gitlab-ci.yml index 66c60e85892..76fb2948144 100644 --- a/lib/gitlab/ci/templates/Jobs/Deploy.gitlab-ci.yml +++ b/lib/gitlab/ci/templates/Jobs/Deploy.gitlab-ci.yml @@ -1,5 +1,5 @@ .auto-deploy: - image: "registry.gitlab.com/gitlab-org/cluster-integration/auto-deploy-image:v0.17.2" + image: "registry.gitlab.com/gitlab-org/cluster-integration/auto-deploy-image:v1.0.0" dependencies: [] include: diff --git a/lib/gitlab/prometheus_client.rb b/lib/gitlab/prometheus_client.rb index e2a13610dc0..56e1154a672 100644 --- a/lib/gitlab/prometheus_client.rb +++ b/lib/gitlab/prometheus_client.rb @@ -132,7 +132,7 @@ module Gitlab def http_options strong_memoize(:http_options) do - { follow_redirects: false, open_timeout: 5, read_timeout: 10 }.merge(mapped_options) + { follow_redirects: false }.merge(mapped_options) end end diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 38120b51b0f..299c5cc1008 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -868,6 +868,9 @@ msgstr "" msgid "(line: %{startLine})" msgstr "" +msgid "(max size 15 MB)" +msgstr "" + msgid "(removed)" msgstr "" @@ -1156,6 +1159,9 @@ msgstr "" msgid "A new impersonation token has been created." msgstr "" +msgid "A non-confidential epic cannot be assigned to a confidential parent epic" +msgstr "" + msgid "A plain HTML site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features." msgstr "" @@ -4343,10 +4349,10 @@ msgstr "" msgid "Cannot import because issues are not available in this project." msgstr "" -msgid "Cannot make epic confidential if it contains not-confidential issues" +msgid "Cannot make the epic confidential if it contains non-confidential child epics" msgstr "" -msgid "Cannot make epic confidential if it contains not-confidential sub-epics" +msgid "Cannot make the epic confidential if it contains non-confidential issues" msgstr "" msgid "Cannot merge" @@ -4367,7 +4373,7 @@ msgstr "" msgid "Cannot refer to a group %{timebox_type} by an internal id!" msgstr "" -msgid "Cannot set confidential epic for not-confidential issue" +msgid "Cannot set confidential epic for a non-confidential issue" msgstr "" msgid "Cannot show preview. For previews on sketch files, they must have the file format introduced by Sketch version 43 and above." @@ -14387,6 +14393,9 @@ msgstr "" msgid "List available repositories" msgstr "" +msgid "List of all merge commits" +msgstr "" + msgid "List settings" msgstr "" @@ -16499,9 +16508,6 @@ msgstr "" msgid "Not started" msgstr "" -msgid "Not-confidential epic cannot be assigned to a confidential parent epic" -msgstr "" - msgid "Note" msgstr "" @@ -27373,6 +27379,12 @@ msgstr "" msgid "Webhooks have moved. They can now be found under the Settings menu." msgstr "" +msgid "Webhooks|Deployment events" +msgstr "" + +msgid "Webhooks|This URL will be triggered when a deployment is finished/failed/canceled" +msgstr "" + msgid "Wednesday" msgstr "" @@ -27418,6 +27430,9 @@ msgstr "" msgid "What describes you best?" msgstr "" +msgid "What's new at GitLab" +msgstr "" + msgid "What’s your experience level?" msgstr "" diff --git a/spec/controllers/projects/hooks_controller_spec.rb b/spec/controllers/projects/hooks_controller_spec.rb index 440e6b2a74c..85d036486ee 100644 --- a/spec/controllers/projects/hooks_controller_spec.rb +++ b/spec/controllers/projects/hooks_controller_spec.rb @@ -36,7 +36,8 @@ RSpec.describe Projects::HooksController do note_events: true, job_events: true, pipeline_events: true, - wiki_page_events: true + wiki_page_events: true, + deployment_events: true } post :create, params: { namespace_id: project.namespace, project_id: project, hook: hook_params } diff --git a/spec/features/registrations/experience_level_spec.rb b/spec/features/registrations/experience_level_spec.rb new file mode 100644 index 00000000000..d08bcc76ac8 --- /dev/null +++ b/spec/features/registrations/experience_level_spec.rb @@ -0,0 +1,35 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe 'Experience level screen' do + let_it_be(:user) { create(:user) } + let_it_be(:group) { create(:group) } + + before do + group.add_owner(user) + gitlab_sign_in(user) + stub_experiment_for_user(onboarding_issues: true) + visit users_sign_up_experience_level_path(namespace_path: group.to_param) + end + + subject { page } + + it 'shows the intro content' do + is_expected.to have_content('Hello there') + is_expected.to have_content('Welcome to the guided GitLab tour') + is_expected.to have_content('What describes you best?') + end + + it 'shows the option for novice' do + is_expected.to have_content('Novice') + is_expected.to have_content('I’m not very familiar with the basics of project management and DevOps') + is_expected.to have_content('Show me everything') + end + + it 'shows the option for experienced' do + is_expected.to have_content('Experienced') + is_expected.to have_content('I’m familiar with the basics of project management and DevOps') + is_expected.to have_content('Show me more advanced stuff') + end +end diff --git a/spec/frontend/whats_new/components/app_spec.js b/spec/frontend/whats_new/components/app_spec.js new file mode 100644 index 00000000000..a349aad9f1c --- /dev/null +++ b/spec/frontend/whats_new/components/app_spec.js @@ -0,0 +1,57 @@ +import { createLocalVue, mount } from '@vue/test-utils'; +import Vuex from 'vuex'; +import { GlDrawer } from '@gitlab/ui'; +import App from '~/whats_new/components/app.vue'; + +const localVue = createLocalVue(); +localVue.use(Vuex); + +describe('App', () => { + let wrapper; + let store; + let actions; + let state; + + beforeEach(() => { + actions = { + closeDrawer: jest.fn(), + }; + + state = { + open: true, + }; + + store = new Vuex.Store({ + actions, + state, + }); + + wrapper = mount(App, { + localVue, + store, + }); + }); + + afterEach(() => { + wrapper.destroy(); + }); + + const getDrawer = () => wrapper.find(GlDrawer); + + it('contains a drawer', () => { + expect(getDrawer().exists()).toBe(true); + }); + + it('dispatches closeDrawer when clicking close', () => { + getDrawer().vm.$emit('close'); + expect(actions.closeDrawer).toHaveBeenCalled(); + }); + + it.each([true, false])('passes open property', async openState => { + wrapper.vm.$store.state.open = openState; + + await wrapper.vm.$nextTick(); + + expect(getDrawer().props('open')).toBe(openState); + }); +}); diff --git a/spec/frontend/whats_new/components/trigger_spec.js b/spec/frontend/whats_new/components/trigger_spec.js new file mode 100644 index 00000000000..7961957e077 --- /dev/null +++ b/spec/frontend/whats_new/components/trigger_spec.js @@ -0,0 +1,43 @@ +import { createLocalVue, mount } from '@vue/test-utils'; +import Vuex from 'vuex'; +import { GlButton } from '@gitlab/ui'; +import Trigger from '~/whats_new/components/trigger.vue'; + +const localVue = createLocalVue(); +localVue.use(Vuex); + +describe('Trigger', () => { + let wrapper; + let store; + let actions; + let state; + + beforeEach(() => { + actions = { + openDrawer: jest.fn(), + }; + + state = { + open: true, + }; + + store = new Vuex.Store({ + actions, + state, + }); + + wrapper = mount(Trigger, { + localVue, + store, + }); + }); + + afterEach(() => { + wrapper.destroy(); + }); + + it('dispatches openDrawer when clicking close', () => { + wrapper.find(GlButton).vm.$emit('click'); + expect(actions.openDrawer).toHaveBeenCalled(); + }); +}); diff --git a/spec/frontend/whats_new/store/actions_spec.js b/spec/frontend/whats_new/store/actions_spec.js new file mode 100644 index 00000000000..d95453c9175 --- /dev/null +++ b/spec/frontend/whats_new/store/actions_spec.js @@ -0,0 +1,17 @@ +import testAction from 'helpers/vuex_action_helper'; +import actions from '~/whats_new/store/actions'; +import * as types from '~/whats_new/store/mutation_types'; + +describe('whats new actions', () => { + describe('openDrawer', () => { + it('should commit openDrawer', () => { + testAction(actions.openDrawer, {}, {}, [{ type: types.OPEN_DRAWER }]); + }); + }); + + describe('closeDrawer', () => { + it('should commit closeDrawer', () => { + testAction(actions.closeDrawer, {}, {}, [{ type: types.CLOSE_DRAWER }]); + }); + }); +}); diff --git a/spec/frontend/whats_new/store/mutations_spec.js b/spec/frontend/whats_new/store/mutations_spec.js new file mode 100644 index 00000000000..3c33364fed3 --- /dev/null +++ b/spec/frontend/whats_new/store/mutations_spec.js @@ -0,0 +1,25 @@ +import mutations from '~/whats_new/store/mutations'; +import createState from '~/whats_new/store/state'; +import * as types from '~/whats_new/store/mutation_types'; + +describe('whats new mutations', () => { + let state; + + beforeEach(() => { + state = createState; + }); + + describe('openDrawer', () => { + it('sets open to true', () => { + mutations[types.OPEN_DRAWER](state); + expect(state.open).toBe(true); + }); + }); + + describe('closeDrawer', () => { + it('sets open to false', () => { + mutations[types.CLOSE_DRAWER](state); + expect(state.open).toBe(false); + }); + }); +}); diff --git a/spec/graphql/mutations/boards/lists/update_spec.rb b/spec/graphql/mutations/boards/lists/update_spec.rb new file mode 100644 index 00000000000..d5d8a2af6bf --- /dev/null +++ b/spec/graphql/mutations/boards/lists/update_spec.rb @@ -0,0 +1,56 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Mutations::Boards::Lists::Update do + let_it_be(:group) { create(:group, :private) } + let_it_be(:board) { create(:board, group: group) } + let_it_be(:reporter) { create(:user) } + let_it_be(:guest) { create(:user) } + let_it_be(:list) { create(:list, board: board, position: 0) } + let_it_be(:list2) { create(:list, board: board) } + let(:mutation) { described_class.new(object: nil, context: { current_user: current_user }, field: nil) } + let(:list_update_params) { { position: 1, collapsed: true } } + + before_all do + group.add_reporter(reporter) + group.add_guest(guest) + list.update_preferences_for(reporter, collapsed: false) + end + + subject { mutation.resolve(list: list, **list_update_params) } + + describe '#resolve' do + context 'with permission to admin board lists' do + let(:current_user) { reporter } + + it 'updates the list position and collapsed state as expected' do + subject + + reloaded_list = list.reload + expect(reloaded_list.position).to eq(1) + expect(reloaded_list.collapsed?(current_user)).to eq(true) + end + end + + context 'with permission to read board lists' do + let(:current_user) { guest } + + it 'updates the list collapsed state but not the list position' do + subject + + reloaded_list = list.reload + expect(reloaded_list.position).to eq(0) + expect(reloaded_list.collapsed?(current_user)).to eq(true) + end + end + + context 'without permission to read board lists' do + let(:current_user) { create(:user) } + + it 'raises Resource Not Found error' do + expect { subject }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable) + end + end + end +end diff --git a/spec/graphql/resolvers/ci_configuration/sast_resolver_spec.rb b/spec/graphql/resolvers/ci_configuration/sast_resolver_spec.rb deleted file mode 100644 index de69ad5d450..00000000000 --- a/spec/graphql/resolvers/ci_configuration/sast_resolver_spec.rb +++ /dev/null @@ -1,28 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -RSpec.describe Resolvers::CiConfiguration::SastResolver do - include GraphqlHelpers - - let_it_be(:user) { create(:user) } - let_it_be(:project) { create(:project) } - - describe '#resolve' do - subject(:sast_config) { resolve(described_class, ctx: { current_user: user }, obj: project) } - - it 'returns global variable informations related to SAST' do - expect(sast_config['global'].first['field']).to eql("SECURE_ANALYZERS_PREFIX") - expect(sast_config['global'].first['label']).to eql("Image prefix") - expect(sast_config['global'].first['type']).to eql("string") - - expect(sast_config['pipeline'].first['field']).to eql("stage") - expect(sast_config['pipeline'].first['label']).to eql("Stage") - expect(sast_config['pipeline'].first['type']).to eql("dropdown") - - expect(sast_config['analyzers'].first['name']).to eql("brakeman") - expect(sast_config['analyzers'].first['label']).to eql("Brakeman") - expect(sast_config['analyzers'].first['enabled']).to be true - end - end -end diff --git a/spec/graphql/types/board_list_type_spec.rb b/spec/graphql/types/board_list_type_spec.rb index 66ea003d175..7976936fc1f 100644 --- a/spec/graphql/types/board_list_type_spec.rb +++ b/spec/graphql/types/board_list_type_spec.rb @@ -6,7 +6,7 @@ RSpec.describe GitlabSchema.types['BoardList'] do specify { expect(described_class.graphql_name).to eq('BoardList') } it 'has specific fields' do - expected_fields = %w[id list_type position label issues] + expected_fields = %w[id list_type position label issues_count issues] expect(described_class).to include_graphql_fields(*expected_fields) end diff --git a/spec/graphql/types/project_type_spec.rb b/spec/graphql/types/project_type_spec.rb index a0b6858fc99..8a5d0cdf12d 100644 --- a/spec/graphql/types/project_type_spec.rb +++ b/spec/graphql/types/project_type_spec.rb @@ -26,7 +26,7 @@ RSpec.describe GitlabSchema.types['Project'] do grafanaIntegration autocloseReferencedIssues suggestion_commit_message environments environment boards jira_import_status jira_imports services releases release alert_management_alerts alert_management_alert alert_management_alert_status_counts - container_expiration_policy sast_ci_configuration service_desk_enabled service_desk_address + container_expiration_policy service_desk_enabled service_desk_address issue_status_counts ] @@ -150,93 +150,5 @@ RSpec.describe GitlabSchema.types['Project'] do it { is_expected.to have_graphql_type(Types::ContainerExpirationPolicyType) } end - describe 'sast_ci_configuration' do - let_it_be(:project) { create(:project) } - let_it_be(:user) { create(:user) } - let_it_be(:query) do - %( - query { - project(fullPath: "#{project.full_path}") { - sastCiConfiguration { - global { - nodes { - type - options { - nodes { - label - value - } - } - field - label - defaultValue - value - } - } - pipeline { - nodes { - type - options { - nodes { - label - value - } - } - field - label - defaultValue - value - } - } - analyzers { - nodes { - name - label - enabled - } - } - } - } - } - ) - end - - subject { GitlabSchema.execute(query, context: { current_user: user }).as_json } - - before do - project.add_developer(user) - end - - it "returns the project's sast configuration for global variables" do - query_result = subject.dig('data', 'project', 'sastCiConfiguration', 'global', 'nodes') - first_config = query_result.first - fourth_config = query_result[3] - expect(first_config['type']).to eq('string') - expect(first_config['field']).to eq('SECURE_ANALYZERS_PREFIX') - expect(first_config['label']).to eq('Image prefix') - expect(first_config['defaultValue']).to eq('registry.gitlab.com/gitlab-org/security-products/analyzers') - expect(first_config['value']).to eq('') - expect(first_config['options']).to be_nil - expect(fourth_config['options']['nodes']).to match([{ "value" => "true", "label" => "true (disables SAST)" }, - { "value" => "false", "label" => "false (enables SAST)" }]) - end - - it "returns the project's sast configuration for pipeline variables" do - configuration = subject.dig('data', 'project', 'sastCiConfiguration', 'pipeline', 'nodes').first - expect(configuration['type']).to eq('dropdown') - expect(configuration['field']).to eq('stage') - expect(configuration['label']).to eq('Stage') - expect(configuration['defaultValue']).to eq('test') - expect(configuration['value']).to eq('') - end - - it "returns the project's sast configuration for analyzer variables" do - configuration = subject.dig('data', 'project', 'sastCiConfiguration', 'analyzers', 'nodes').first - expect(configuration['name']).to eq('brakeman') - expect(configuration['label']).to eq('Brakeman') - expect(configuration['enabled']).to eq(true) - end - end - it_behaves_like 'a GraphQL type with labels' end diff --git a/spec/lib/gitlab/app_logger_spec.rb b/spec/lib/gitlab/app_logger_spec.rb index 166b1fda268..23bac444dbe 100644 --- a/spec/lib/gitlab/app_logger_spec.rb +++ b/spec/lib/gitlab/app_logger_spec.rb @@ -19,4 +19,12 @@ RSpec.describe Gitlab::AppLogger do subject.info('Hello World!') end + + it 'logs info to only the AppJsonLogger when unstructured logs are disabled' do + stub_env('UNSTRUCTURED_RAILS_LOG', 'false') + expect_any_instance_of(Gitlab::AppTextLogger).not_to receive(:info).and_call_original + expect_any_instance_of(Gitlab::AppJsonLogger).to receive(:info).and_call_original + + subject.info('Hello World!') + end end diff --git a/spec/requests/api/graphql/mutations/boards/lists/update_spec.rb b/spec/requests/api/graphql/mutations/boards/lists/update_spec.rb new file mode 100644 index 00000000000..8a6d2cb3994 --- /dev/null +++ b/spec/requests/api/graphql/mutations/boards/lists/update_spec.rb @@ -0,0 +1,57 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe 'Update of an existing board list' do + include GraphqlHelpers + + let_it_be(:current_user) { create(:user) } + let_it_be(:group) { create(:group, :private) } + let_it_be(:board) { create(:board, group: group) } + let_it_be(:list) { create(:list, board: board, position: 0) } + let_it_be(:list2) { create(:list, board: board) } + let_it_be(:input) { { list_id: list.to_global_id.to_s, position: 1, collapsed: true } } + let(:mutation) { graphql_mutation(:update_board_list, input) } + let(:mutation_response) { graphql_mutation_response(:update_board_list) } + + context 'the user is not allowed to read board lists' do + it_behaves_like 'a mutation that returns top-level errors', + errors: ['The resource that you are attempting to access does not exist or you don\'t have permission to perform this action'] + end + + before do + list.update_preferences_for(current_user, collapsed: false) + end + + context 'when user has permissions to admin board lists' do + before do + group.add_reporter(current_user) + end + + it 'updates the list position and collapsed state' do + post_graphql_mutation(mutation, current_user: current_user) + + expect(response).to have_gitlab_http_status(:success) + expect(mutation_response['list']).to include( + 'position' => 1, + 'collapsed' => true + ) + end + end + + context 'when user has permissions to read board lists' do + before do + group.add_guest(current_user) + end + + it 'updates the list collapsed state but not the list position' do + post_graphql_mutation(mutation, current_user: current_user) + + expect(response).to have_gitlab_http_status(:success) + expect(mutation_response['list']).to include( + 'position' => 0, + 'collapsed' => true + ) + end + end +end diff --git a/spec/services/projects/update_pages_configuration_service_spec.rb b/spec/services/projects/update_pages_configuration_service_spec.rb index 479653304a0..9f7ebd40df6 100644 --- a/spec/services/projects/update_pages_configuration_service_spec.rb +++ b/spec/services/projects/update_pages_configuration_service_spec.rb @@ -3,50 +3,75 @@ require 'spec_helper' RSpec.describe Projects::UpdatePagesConfigurationService do - let(:project) { create(:project) } let(:service) { described_class.new(project) } describe "#execute" do - let(:file) { Tempfile.new('pages-test') } - subject { service.execute } - after do - file.close - file.unlink - end + context 'when pages are deployed' do + let_it_be(:project) do + create(:project).tap(&:mark_pages_as_deployed) + end - before do - allow(service).to receive(:pages_config_file).and_return(file.path) - end + let(:file) { Tempfile.new('pages-test') } - context 'when configuration changes' do - it 'updates the .update file' do - expect(service).to receive(:reload_daemon).and_call_original + before do + allow(service).to receive(:pages_config_file).and_return(file.path) + end - expect(subject).to include(status: :success) + after do + file.close + file.unlink end - end - context 'when configuration does not change' do - before do - # we set the configuration - service.execute + context 'when configuration changes' do + it 'updates the config and reloads the daemon' do + allow(service).to receive(:update_file).and_call_original + + expect(service).to receive(:update_file).with(file.path, an_instance_of(String)) + .and_call_original + expect(service).to receive(:reload_daemon).and_call_original + + expect(subject).to include(status: :success) + end + end + + context 'when configuration does not change' do + before do + # we set the configuration + service.execute + end + + it 'does not update the .update file' do + expect(service).not_to receive(:reload_daemon) + + expect(subject).to include(status: :success) + end end - it 'does not update the .update file' do - expect(service).not_to receive(:reload_daemon) + context 'when an error occurs' do + it 'returns an error object' do + e = StandardError.new("Failure") + allow(service).to receive(:reload_daemon).and_raise(e) - expect(subject).to include(status: :success) + expect(subject).to eq(status: :error, message: "Failure", exception: e) + end end end - context 'when an error occurs' do - it 'returns an error object' do - e = StandardError.new("Failure") - allow(service).to receive(:reload_daemon).and_raise(e) + context 'when pages are not deployed' do + let_it_be(:project) do + create(:project).tap(&:mark_pages_as_not_deployed) + end + + it 'returns successfully' do + expect(subject).to eq(status: :success) + end + + it 'does not update the config' do + expect(service).not_to receive(:update_file) - expect(subject).to eq(status: :error, message: "Failure", exception: e) + subject end end end diff --git a/spec/services/projects/update_service_spec.rb b/spec/services/projects/update_service_spec.rb index 48bbed40f29..4a613f42556 100644 --- a/spec/services/projects/update_service_spec.rb +++ b/spec/services/projects/update_service_spec.rb @@ -397,18 +397,30 @@ RSpec.describe Projects::UpdateService do end shared_examples 'updating pages configuration' do - it 'schedules the `PagesUpdateConfigurationWorker`' do + it 'schedules the `PagesUpdateConfigurationWorker` when pages are deployed' do + project.mark_pages_as_deployed + expect(PagesUpdateConfigurationWorker).to receive(:perform_async).with(project.id) subject end + it "does not schedule a job when pages aren't deployed" do + project.mark_pages_as_not_deployed + + expect(PagesUpdateConfigurationWorker).not_to receive(:perform_async).with(project.id) + + subject + end + context 'when `async_update_pages_config` is disabled' do before do stub_feature_flags(async_update_pages_config: false) end - it 'calls Projects::UpdatePagesConfigurationService' do + it 'calls Projects::UpdatePagesConfigurationService when pages are deployed' do + project.mark_pages_as_deployed + expect(Projects::UpdatePagesConfigurationService) .to receive(:new) .with(project) @@ -416,6 +428,15 @@ RSpec.describe Projects::UpdateService do subject end + + it "does not update pages config when pages aren't deployed" do + project.mark_pages_as_not_deployed + + expect(Projects::UpdatePagesConfigurationService) + .not_to receive(:new) + + subject + end end end diff --git a/spec/support/gitlab_stubs/gitlab_ci_for_sast.yml b/spec/support/gitlab_stubs/gitlab_ci_for_sast.yml new file mode 100644 index 00000000000..4134660e4b9 --- /dev/null +++ b/spec/support/gitlab_stubs/gitlab_ci_for_sast.yml @@ -0,0 +1,13 @@ +include: + - template: SAST.gitlab-ci.yml + +variables: + SECURE_ANALYZERS_PREFIX: "registry.gitlab.com/gitlab-org/security-products/analyzers2" + SAST_EXCLUDED_PATHS: "spec, executables" + +stages: + - our_custom_security_stage +sast: + stage: our_custom_security_stage + variables: + SEARCH_MAX_DEPTH: 8 diff --git a/spec/support/shared_contexts/read_ci_configuration_shared_context.rb b/spec/support/shared_contexts/read_ci_configuration_shared_context.rb new file mode 100644 index 00000000000..f8f33e2a745 --- /dev/null +++ b/spec/support/shared_contexts/read_ci_configuration_shared_context.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +RSpec.shared_context 'read ci configuration for sast enabled project' do + let_it_be(:gitlab_ci_yml_content) do + File.read(Rails.root.join('spec/support/gitlab_stubs/gitlab_ci_for_sast.yml')) + end + + let_it_be(:project) { create(:project, :repository) } +end |