diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2019-11-08 12:06:32 +0000 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2019-11-08 12:06:32 +0000 |
commit | 61f0c58946ebac453b55a657cd4be1ac50a01e11 (patch) | |
tree | 7b164c1cc9dc8ab1d100ca4fe90decf6d72e984b | |
parent | d23b2a0871f3ca507aafa949e0314625f1f0c6a7 (diff) | |
download | gitlab-ce-61f0c58946ebac453b55a657cd4be1ac50a01e11.tar.gz |
Add latest changes from gitlab-org/gitlab@master
81 files changed, 1067 insertions, 246 deletions
diff --git a/.gitlab/ci/review.gitlab-ci.yml b/.gitlab/ci/review.gitlab-ci.yml index 8e91d053ca0..4ed9ac03d0c 100644 --- a/.gitlab/ci/review.gitlab-ci.yml +++ b/.gitlab/ci/review.gitlab-ci.yml @@ -117,7 +117,6 @@ schedule:review-build-cng: - source scripts/utils.sh - install_api_client_dependencies_with_apk - source scripts/review_apps/review-apps.sh - - export REVIEW_APP_CONFIG_CHANGED=$(base_config_changed) script: - check_kube_domain - ensure_namespace diff --git a/app/assets/javascripts/blob/blob_utils.js b/app/assets/javascripts/blob/blob_utils.js index 27fcc7f7b79..cc9c621c679 100644 --- a/app/assets/javascripts/blob/blob_utils.js +++ b/app/assets/javascripts/blob/blob_utils.js @@ -1,5 +1,5 @@ -// capture anything starting with http:// or https:// +// capture anything starting with http:// or https:// which is not already part of a html link // up until a disallowed character or whitespace -export const blobLinkRegex = /https?:\/\/[^"<>\\^`{|}\s]+/g; +export const blobLinkRegex = /(?<!<a href=")https?:\/\/[^"<>\\^`{|}\s]+/g; export default { blobLinkRegex }; diff --git a/app/assets/javascripts/flash.js b/app/assets/javascripts/flash.js index fc9c5827ed4..2c3320b5e79 100644 --- a/app/assets/javascripts/flash.js +++ b/app/assets/javascripts/flash.js @@ -37,7 +37,7 @@ const createAction = config => ` `; const createFlashEl = (message, type) => ` - <div class="flash-content flash-${type} rounded"> + <div class="flash-${type}"> <div class="flash-text"> ${_.escape(message)} <div class="close-icon-wrapper js-close-icon"> diff --git a/app/assets/javascripts/lib/utils/datetime_utility.js b/app/assets/javascripts/lib/utils/datetime_utility.js index 118d5facafc..28143859e4c 100644 --- a/app/assets/javascripts/lib/utils/datetime_utility.js +++ b/app/assets/javascripts/lib/utils/datetime_utility.js @@ -541,7 +541,7 @@ export const stringifyTime = (timeObject, fullNameFormat = false) => { * The result cannot become negative. * * @param endDate date string that the time difference is calculated for - * @return {number} number of milliseconds remaining until the given date + * @return {Number} number of milliseconds remaining until the given date */ export const calculateRemainingMilliseconds = endDate => { const remainingMilliseconds = new Date(endDate).getTime() - Date.now(); @@ -552,7 +552,7 @@ export const calculateRemainingMilliseconds = endDate => { * Subtracts a given number of days from a given date and returns the new date. * * @param {Date} date the date that we will substract days from - * @param {number} daysInPast number of days that are subtracted from a given date + * @param {Number} daysInPast number of days that are subtracted from a given date * @returns {Date} Date in past as Date object */ export const getDateInPast = (date, daysInPast) => @@ -594,3 +594,11 @@ export const getDatesInRange = (d1, d2, formatter = x => x) => { return range.map(formatter); }; + +/** + * Converts the supplied number of seconds to milliseconds. + * + * @param {Number} seconds + * @return {Number} number of milliseconds + */ +export const secondsToMilliseconds = seconds => seconds * 1000; diff --git a/app/assets/javascripts/monitoring/utils.js b/app/assets/javascripts/monitoring/utils.js index 6747306a6d9..2ae1647011d 100644 --- a/app/assets/javascripts/monitoring/utils.js +++ b/app/assets/javascripts/monitoring/utils.js @@ -1,7 +1,6 @@ import dateformat from 'dateformat'; import { secondsIn, dateTimePickerRegex, dateFormats } from './constants'; - -const secondsToMilliseconds = seconds => seconds * 1000; +import { secondsToMilliseconds } from '~/lib/utils/datetime_utility'; export const getTimeDiff = timeWindow => { const end = Math.floor(Date.now() / 1000); // convert milliseconds to seconds diff --git a/app/assets/javascripts/pipelines/components/test_reports/test_summary.vue b/app/assets/javascripts/pipelines/components/test_reports/test_summary.vue index 5fc0e220a72..dce8b020d6f 100644 --- a/app/assets/javascripts/pipelines/components/test_reports/test_summary.vue +++ b/app/assets/javascripts/pipelines/components/test_reports/test_summary.vue @@ -1,7 +1,7 @@ <script> import { GlButton, GlLink, GlProgressBar } from '@gitlab/ui'; import { __ } from '~/locale'; -import { formatTime } from '~/lib/utils/datetime_utility'; +import { formatTime, secondsToMilliseconds } from '~/lib/utils/datetime_utility'; import Icon from '~/vue_shared/components/icon.vue'; export default { @@ -31,7 +31,7 @@ export default { return Math.round((this.report.success_count / this.report.total_count) * 100) || 0; }, formattedDuration() { - return formatTime(this.report.total_time * 1000); + return formatTime(secondsToMilliseconds(this.report.total_time)); }, progressBarVariant() { if (this.successPercentage < 33) { diff --git a/app/assets/javascripts/pipelines/stores/test_reports/utils.js b/app/assets/javascripts/pipelines/stores/test_reports/utils.js index c426a5f0bb5..95466587d6b 100644 --- a/app/assets/javascripts/pipelines/stores/test_reports/utils.js +++ b/app/assets/javascripts/pipelines/stores/test_reports/utils.js @@ -1,5 +1,5 @@ import { TestStatus } from '~/pipelines/constants'; -import { formatTime } from '~/lib/utils/datetime_utility'; +import { formatTime, secondsToMilliseconds } from '~/lib/utils/datetime_utility'; function iconForTestStatus(status) { switch (status) { @@ -12,7 +12,7 @@ function iconForTestStatus(status) { } } -export const formattedTime = timeInSeconds => formatTime(timeInSeconds * 1000); +export const formattedTime = timeInSeconds => formatTime(secondsToMilliseconds(timeInSeconds)); export const addIconStatus = testCase => ({ ...testCase, diff --git a/app/assets/javascripts/releases/list/components/release_block.vue b/app/assets/javascripts/releases/list/components/release_block.vue index 1b78901d771..2b6aa6aeff9 100644 --- a/app/assets/javascripts/releases/list/components/release_block.vue +++ b/app/assets/javascripts/releases/list/components/release_block.vue @@ -10,6 +10,7 @@ import { slugify } from '~/lib/utils/text_utility'; import { getLocationHash } from '~/lib/utils/url_utility'; import { scrollToElement } from '~/lib/utils/common_utils'; import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; +import ReleaseBlockFooter from './release_block_footer.vue'; export default { name: 'ReleaseBlock', @@ -19,6 +20,7 @@ export default { GlButton, Icon, UserAvatarLink, + ReleaseBlockFooter, }, directives: { GlTooltip: GlTooltipDirective, @@ -79,6 +81,9 @@ export default { this.glFeatures.releaseEditPage && this.release._links && this.release._links.edit_url, ); }, + shouldShowFooter() { + return this.glFeatures.releaseIssueSummary; + }, }, mounted() { const hash = getLocationHash(); @@ -164,7 +169,7 @@ export default { by <user-avatar-link class="prepend-left-4" - :link-href="author.path" + :link-href="author.web_url" :img-src="author.avatar_url" :img-alt="userImageAltDescription" :tooltip-text="author.username" @@ -216,5 +221,16 @@ export default { <div v-html="release.description_html"></div> </div> </div> + + <release-block-footer + v-if="shouldShowFooter" + class="card-footer" + :commit="release.commit" + :commit-path="release.commit_path" + :tag-name="release.tag_name" + :tag-path="release.tag_path" + :author="release.author" + :released-at="release.released_at" + /> </div> </template> diff --git a/app/assets/javascripts/releases/list/components/release_block_footer.vue b/app/assets/javascripts/releases/list/components/release_block_footer.vue new file mode 100644 index 00000000000..5659f0e530b --- /dev/null +++ b/app/assets/javascripts/releases/list/components/release_block_footer.vue @@ -0,0 +1,112 @@ +<script> +import Icon from '~/vue_shared/components/icon.vue'; +import { GlTooltipDirective, GlLink } from '@gitlab/ui'; +import UserAvatarLink from '~/vue_shared/components/user_avatar/user_avatar_link.vue'; +import timeagoMixin from '~/vue_shared/mixins/timeago'; +import { __, sprintf } from '~/locale'; + +export default { + name: 'ReleaseBlockFooter', + components: { + Icon, + GlLink, + UserAvatarLink, + }, + directives: { + GlTooltip: GlTooltipDirective, + }, + mixins: [timeagoMixin], + props: { + commit: { + type: Object, + required: false, + default: null, + }, + commitPath: { + type: String, + required: false, + default: '', + }, + tagName: { + type: String, + required: false, + default: '', + }, + tagPath: { + type: String, + required: false, + default: '', + }, + author: { + type: Object, + required: false, + default: null, + }, + releasedAt: { + type: String, + required: false, + default: '', + }, + }, + computed: { + releasedAtTimeAgo() { + return this.timeFormated(this.releasedAt); + }, + userImageAltDescription() { + return this.author && this.author.username + ? sprintf(__("%{username}'s avatar"), { username: this.author.username }) + : null; + }, + }, +}; +</script> +<template> + <div> + <div v-if="commit" class="float-left mr-3 d-flex align-items-center js-commit-info"> + <icon ref="commitIcon" name="commit" class="mr-1" /> + <div v-gl-tooltip.bottom :title="commit.title"> + <gl-link v-if="commitPath" :href="commitPath"> + {{ commit.short_id }} + </gl-link> + <span v-else>{{ commit.short_id }}</span> + </div> + </div> + + <div v-if="tagName" class="float-left mr-3 d-flex align-items-center js-tag-info"> + <icon name="tag" class="mr-1" /> + <div v-gl-tooltip.bottom :title="__('Tag')"> + <gl-link v-if="tagPath" :href="tagPath"> + {{ tagName }} + </gl-link> + <span v-else>{{ tagName }}</span> + </div> + </div> + + <div + v-if="releasedAt || author" + class="float-left d-flex align-items-center js-author-date-info" + > + <span class="text-secondary">{{ __('Created') }} </span> + <template v-if="releasedAt"> + <span + v-gl-tooltip.bottom + :title="tooltipTitle(releasedAt)" + class="text-secondary flex-shrink-0" + > + {{ releasedAtTimeAgo }} + </span> + </template> + + <div v-if="author" class="d-flex"> + <span class="text-secondary">{{ __('by') }} </span> + <user-avatar-link + :link-href="author.web_url" + :img-src="author.avatar_url" + :img-alt="userImageAltDescription" + :tooltip-text="author.username" + tooltip-placement="bottom" + /> + </div> + </div> + </div> +</template> diff --git a/app/assets/stylesheets/bootstrap_migration.scss b/app/assets/stylesheets/bootstrap_migration.scss index c9b00e5ff27..885e9ac6667 100644 --- a/app/assets/stylesheets/bootstrap_migration.scss +++ b/app/assets/stylesheets/bootstrap_migration.scss @@ -282,8 +282,7 @@ pre code { white-space: pre-wrap; } -.alert, -.flash-notice { +.alert { border-radius: 0; } @@ -310,12 +309,10 @@ pre code { .alert-success, .alert-info, .alert-warning, -.alert-danger, -.flash-notice { +.alert-danger { color: $white-light; h4, - a:not(.btn), .alert-link { color: $white-light; } diff --git a/app/assets/stylesheets/framework/flash.scss b/app/assets/stylesheets/framework/flash.scss index 8fc2fd5f53b..657e7023111 100644 --- a/app/assets/stylesheets/framework/flash.scss +++ b/app/assets/stylesheets/framework/flash.scss @@ -1,7 +1,7 @@ $notification-box-shadow-color: rgba(0, 0, 0, 0.25); .flash-container { - margin: 0; + margin-top: 10px; margin-bottom: $gl-padding; font-size: 14px; position: relative; @@ -12,17 +12,22 @@ $notification-box-shadow-color: rgba(0, 0, 0, 0.25); position: -webkit-sticky; top: $flash-container-top; z-index: 251; + } - .flash-content { - box-shadow: 0 2px 4px 0 $notification-box-shadow-color; - } + &.flash-container-page { + margin-bottom: 0; + } + + &:empty { + margin: 0; } .close-icon-wrapper { - padding: ($gl-btn-padding + $gl-padding-4) $gl-padding $gl-btn-padding; + padding: ($gl-padding + $gl-padding-4) $gl-padding $gl-padding; position: absolute; right: 0; top: 0; + bottom: 0; cursor: pointer; .close-icon { @@ -31,13 +36,11 @@ $notification-box-shadow-color: rgba(0, 0, 0, 0.25); } } - .flash-notice, .flash-alert, + .flash-notice, .flash-success, .flash-warning { - border-radius: $border-radius-default; - color: $white-light; - padding-right: $gl-padding * 2; + padding: $gl-padding $gl-padding-32 $gl-padding ($gl-padding + $gl-padding-4); .container-fluid, .container-fluid.container-limited { @@ -45,75 +48,31 @@ $notification-box-shadow-color: rgba(0, 0, 0, 0.25); } } - .flash-notice { - @extend .alert; - background-color: $blue-500; - margin: 0; - - &.flash-notice-persistent { - background-color: $blue-100; - color: $gl-text-color; + .flash-alert { + background-color: $red-100; + color: $red-700; + } - a { - color: $blue-600; + .flash-notice { + background-color: $blue-100; + color: $blue-700; + } - &:hover { - color: $blue-800; - text-decoration: none; - } - } - } + .flash-success { + background-color: $theme-green-100; + color: $green-700; } .flash-warning { - @extend .alert; background-color: $orange-100; - color: $orange-900; + color: $orange-800; cursor: default; - margin: 0; } .flash-text, .flash-action { display: inline-block; } - - .flash-alert { - @extend .alert; - background-color: $red-500; - margin: 0; - - .flash-action { - margin-left: 5px; - text-decoration: none; - font-weight: $gl-font-weight-normal; - border-bottom: 1px solid; - - &:hover { - border-color: transparent; - } - } - } - - .flash-success { - @extend .alert; - background-color: $green-500; - margin: 0; - } - - &.flash-container-page { - margin-bottom: 0; - - .flash-notice, - .flash-alert, - .flash-success { - border-radius: 0; - } - } - - &:empty { - margin: 0; - } } @include media-breakpoint-down(sm) { diff --git a/app/assets/stylesheets/framework/layout.scss b/app/assets/stylesheets/framework/layout.scss index 7205324e86f..8038a367fb9 100644 --- a/app/assets/stylesheets/framework/layout.scss +++ b/app/assets/stylesheets/framework/layout.scss @@ -33,7 +33,8 @@ body { &.limit-container-width { .flash-container.sticky { max-width: $limited-layout-width; - margin: 0 auto; + margin-right: auto; + margin-left: auto; } } } diff --git a/app/controllers/clusters/clusters_controller.rb b/app/controllers/clusters/clusters_controller.rb index 7c5c4bb8e80..5dd4040628f 100644 --- a/app/controllers/clusters/clusters_controller.rb +++ b/app/controllers/clusters/clusters_controller.rb @@ -92,13 +92,12 @@ class Clusters::ClustersController < Clusters::BaseController end def destroy - if cluster.destroy - flash[:notice] = _('Kubernetes cluster integration was successfully removed.') - redirect_to clusterable.index_path, status: :found - else - flash[:notice] = _('Kubernetes cluster integration was not removed.') - render :show - end + response = Clusters::DestroyService + .new(current_user, destroy_params) + .execute(cluster) + + flash[:notice] = response[:message] + redirect_to clusterable.index_path, status: :found end def create_gcp @@ -143,6 +142,14 @@ class Clusters::ClustersController < Clusters::BaseController private + def destroy_params + # To be uncomented on https://gitlab.com/gitlab-org/gitlab/merge_requests/16954 + # This MR got split into other since it was too big. + # + # params.permit(:cleanup) + {} + end + def update_params if cluster.provided_by_user? params.require(:cluster).permit( diff --git a/app/controllers/projects/releases_controller.rb b/app/controllers/projects/releases_controller.rb index 40e467e9e8a..7ee5230de06 100644 --- a/app/controllers/projects/releases_controller.rb +++ b/app/controllers/projects/releases_controller.rb @@ -7,6 +7,7 @@ class Projects::ReleasesController < Projects::ApplicationController before_action :authorize_read_release! before_action do push_frontend_feature_flag(:release_edit_page, project) + push_frontend_feature_flag(:release_issue_summary, project) end before_action :authorize_update_release!, only: %i[edit update] diff --git a/app/helpers/users_helper.rb b/app/helpers/users_helper.rb index 4ff25d021fb..ef0cb8b4bcb 100644 --- a/app/helpers/users_helper.rb +++ b/app/helpers/users_helper.rb @@ -95,6 +95,14 @@ module UsersHelper tabs end + def trials_link_url + 'https://about.gitlab.com/free-trial/' + end + + def trials_allowed?(user) + false + end + def get_current_user_menu_items items = [] @@ -105,6 +113,7 @@ module UsersHelper items << :help items << :profile if can?(current_user, :read_user, current_user) items << :settings if can?(current_user, :update_user, current_user) + items << :start_trial if trials_allowed?(current_user) items end diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb index 913253e4e92..12926bc2379 100644 --- a/app/models/ci/pipeline.rb +++ b/app/models/ci/pipeline.rb @@ -607,8 +607,14 @@ module Ci rescue Gitlab::Ci::YamlProcessor::ValidationError => e self.yaml_errors = e.message nil - rescue - self.yaml_errors = 'Undefined error' + rescue => ex + self.yaml_errors = "Undefined error (#{Labkit::Correlation::CorrelationId.current_id})" + + Gitlab::Sentry.track_acceptable_exception(ex, extra: { + project_id: project.id, + sha: sha, + ci_yaml_file: ci_yaml_file_path + }) nil end end diff --git a/app/services/clusters/destroy_service.rb b/app/services/clusters/destroy_service.rb new file mode 100644 index 00000000000..a8de04683fa --- /dev/null +++ b/app/services/clusters/destroy_service.rb @@ -0,0 +1,34 @@ +# frozen_string_literal: true + +module Clusters + class DestroyService + attr_reader :current_user, :params + + def initialize(user = nil, params = {}) + @current_user, @params = user, params.dup + @response = {} + end + + def execute(cluster) + cleanup? ? start_cleanup!(cluster) : destroy_cluster!(cluster) + + @response + end + + private + + def cleanup? + Gitlab::Utils.to_boolean(params[:cleanup]) + end + + def start_cleanup!(cluster) + cluster.start_cleanup! + @response[:message] = _('Kubernetes cluster integration and resources are being removed.') + end + + def destroy_cluster!(cluster) + cluster.destroy! + @response[:message] = _('Kubernetes cluster integration was successfully removed.') + end + end +end diff --git a/app/views/clusters/clusters/_gcp_signup_offer_banner.html.haml b/app/views/clusters/clusters/_gcp_signup_offer_banner.html.haml index a9299af8d78..617e5d1d5d3 100644 --- a/app/views/clusters/clusters/_gcp_signup_offer_banner.html.haml +++ b/app/views/clusters/clusters/_gcp_signup_offer_banner.html.haml @@ -7,6 +7,6 @@ .gcp-signup-offer--copy %h4= s_('ClusterIntegration|Did you know?') %p= s_('ClusterIntegration|Every new Google Cloud Platform (GCP) account receives $300 in credit upon %{sign_up_link}. In partnership with Google, GitLab is able to offer an additional $200 for both new and existing GCP accounts to get started with GitLab\'s Google Kubernetes Engine Integration.').html_safe % { sign_up_link: link } - %a.btn.btn-default{ href: 'https://goo.gl/AaJzRW', target: '_blank', rel: 'noopener noreferrer' } + %a.btn.btn-default{ href: 'https://cloud.google.com/partners/partnercredit/?pcn_code=0014M00001h35gDQAQ#contact-form', target: '_blank', rel: 'noopener noreferrer' } = s_("ClusterIntegration|Apply for credit") diff --git a/app/views/layouts/_flash.html.haml b/app/views/layouts/_flash.html.haml index 92572f0308c..a0815f3a565 100644 --- a/app/views/layouts/_flash.html.haml +++ b/app/views/layouts/_flash.html.haml @@ -3,7 +3,7 @@ - flash.each do |key, value| -# Don't show a flash message if the message is nil - if value - %div{ class: "flash-content flash-#{key} rounded" } + %div{ class: "flash-#{key}" } %span= value %div{ class: "close-icon-wrapper js-close-icon" } = sprite_icon('close', size: 16, css_class: 'close-icon') diff --git a/app/views/layouts/header/_current_user_dropdown.html.haml b/app/views/layouts/header/_current_user_dropdown.html.haml index 484a5053a4b..8fb335c3801 100644 --- a/app/views/layouts/header/_current_user_dropdown.html.haml +++ b/app/views/layouts/header/_current_user_dropdown.html.haml @@ -18,6 +18,11 @@ - if current_user_menu?(:profile) %li = link_to s_("CurrentUser|Profile"), current_user, class: 'profile-link', data: { user: current_user.username } + - if current_user_menu?(:start_trial) + %li + %a.profile-link{ href: trials_link_url } + = s_("CurrentUser|Start a trial") + = emoji_icon('rocket') - if current_user_menu?(:settings) %li = link_to s_("CurrentUser|Settings"), profile_path, data: { qa_selector: 'settings_link' } diff --git a/app/views/projects/labels/index.html.haml b/app/views/projects/labels/index.html.haml index 0328751c68c..0373e37818d 100644 --- a/app/views/projects/labels/index.html.haml +++ b/app/views/projects/labels/index.html.haml @@ -26,7 +26,7 @@ = render partial: 'shared/label', collection: @prioritized_labels, as: :label, locals: { force_priority: true, subject: @project } - elsif search.present? .nothing-here-block - = _('No prioritised labels with such name or description') + = _('No prioritized labels with such name or description') - if @labels.present? .other-labels diff --git a/changelogs/unreleased/30229-gitlab-backgroundmigration-pruneorphanedgeoevents-did-you-mean-prun.yml b/changelogs/unreleased/30229-gitlab-backgroundmigration-pruneorphanedgeoevents-did-you-mean-prun.yml new file mode 100644 index 00000000000..daaf051ea0c --- /dev/null +++ b/changelogs/unreleased/30229-gitlab-backgroundmigration-pruneorphanedgeoevents-did-you-mean-prun.yml @@ -0,0 +1,5 @@ +--- +title: "[Geo] Fix: undefined Gitlab::BackgroundMigration::PruneOrphanedGeoEvents" +merge_request: 19638 +author: +type: fixed diff --git a/changelogs/unreleased/32962-update-gcp-credit-url.yml b/changelogs/unreleased/32962-update-gcp-credit-url.yml new file mode 100644 index 00000000000..87e3e7ff364 --- /dev/null +++ b/changelogs/unreleased/32962-update-gcp-credit-url.yml @@ -0,0 +1,5 @@ +--- +title: Update GCP credit URLs +merge_request: 19683 +author: +type: fixed diff --git a/changelogs/unreleased/33121-refactor-user-counts.yml b/changelogs/unreleased/33121-refactor-user-counts.yml new file mode 100644 index 00000000000..6e3ee5f18f3 --- /dev/null +++ b/changelogs/unreleased/33121-refactor-user-counts.yml @@ -0,0 +1,5 @@ +--- +title: Refactor maximum user counts in license +merge_request: 19071 +author: briankabiro +type: changed diff --git a/changelogs/unreleased/33182-fix-productivity-analytics-multiple-labels-bug.yml b/changelogs/unreleased/33182-fix-productivity-analytics-multiple-labels-bug.yml new file mode 100644 index 00000000000..4c306c539d4 --- /dev/null +++ b/changelogs/unreleased/33182-fix-productivity-analytics-multiple-labels-bug.yml @@ -0,0 +1,5 @@ +--- +title: Fix productivity analytics listing with multiple labels +merge_request: 33182 +author: +type: fixed diff --git a/changelogs/unreleased/35637-add-start-a-trial-option-in-top-right-drop-down.yml b/changelogs/unreleased/35637-add-start-a-trial-option-in-top-right-drop-down.yml new file mode 100644 index 00000000000..f07de3cc104 --- /dev/null +++ b/changelogs/unreleased/35637-add-start-a-trial-option-in-top-right-drop-down.yml @@ -0,0 +1,5 @@ +--- +title: Add start a trial option in the top-right user dropdown +merge_request: 19632 +author: +type: added diff --git a/changelogs/unreleased/jh_flash_messages_styling_22992.yml b/changelogs/unreleased/jh_flash_messages_styling_22992.yml new file mode 100644 index 00000000000..efbc2e3cd1a --- /dev/null +++ b/changelogs/unreleased/jh_flash_messages_styling_22992.yml @@ -0,0 +1,5 @@ +--- +title: Update flash messages color sitewide +merge_request: 18369 +author: +type: changed diff --git a/changelogs/unreleased/nfriend-move-release-data-into-footer.yml b/changelogs/unreleased/nfriend-move-release-data-into-footer.yml new file mode 100644 index 00000000000..64772924d26 --- /dev/null +++ b/changelogs/unreleased/nfriend-move-release-data-into-footer.yml @@ -0,0 +1,5 @@ +--- +title: Move release meta-data into footer on Releases page +merge_request: 19451 +author: +type: changed diff --git a/doc/api/license.md b/doc/api/license.md index 12f1d03d576..c56a5fee95a 100644 --- a/doc/api/license.md +++ b/doc/api/license.md @@ -17,6 +17,7 @@ GET /license "starts_at": "2018-01-27", "expires_at": "2022-01-27", "historical_max": 300, + "maximum_user_count": 300, "expired": false, "overage": 200, "user_limit": 100, @@ -46,6 +47,7 @@ GET /licenses "starts_at": "2018-01-27", "expires_at": "2022-01-27", "historical_max": 300, + "maximum_user_count": 300, "expired": false, "overage": 200, "user_limit": 100, @@ -64,6 +66,7 @@ GET /licenses "starts_at": "2018-01-27", "expires_at": "2022-01-27", "historical_max": 300, + "maximum_user_count": 300, "expired": false, "overage": 200, "user_limit": 100, @@ -112,6 +115,7 @@ Example response: "starts_at": "2018-01-27", "expires_at": "2022-01-27", "historical_max": 300, + "maximum_user_count": 300, "expired": false, "overage": 200, "user_limit": 100, @@ -155,6 +159,7 @@ Example response: "starts_at": "2018-01-27", "expires_at": "2022-01-27", "historical_max": 300, + "maximum_user_count": 300, "expired": false, "overage": 200, "user_limit": 100, diff --git a/lib/gitlab/ci/config/entry/job.rb b/lib/gitlab/ci/config/entry/job.rb index 1298e2d3462..2d5981a4255 100644 --- a/lib/gitlab/ci/config/entry/job.rb +++ b/lib/gitlab/ci/config/entry/job.rb @@ -50,7 +50,6 @@ module Gitlab validates :timeout, duration: { limit: ChronicDuration.output(Project::MAX_BUILD_TIMEOUT) } validates :dependencies, array_of_strings: true - validates :needs, array_of_strings: true validates :extends, array_of_strings_or_string: true validates :rules, array_of_hashes: true end @@ -114,6 +113,11 @@ module Gitlab description: 'List of evaluable Rules to determine job inclusion.', inherit: false + entry :needs, Entry::Needs, + description: 'Needs configuration for this job.', + metadata: { allowed_needs: %i[job] }, + inherit: false + entry :variables, Entry::Variables, description: 'Environment variables available for this job.', inherit: false diff --git a/lib/gitlab/ci/config/entry/need.rb b/lib/gitlab/ci/config/entry/need.rb new file mode 100644 index 00000000000..b6db546d8ff --- /dev/null +++ b/lib/gitlab/ci/config/entry/need.rb @@ -0,0 +1,44 @@ +# frozen_string_literal: true + +module Gitlab + module Ci + class Config + module Entry + class Need < ::Gitlab::Config::Entry::Simplifiable + strategy :Job, if: -> (config) { config.is_a?(String) } + + class Job < ::Gitlab::Config::Entry::Node + include ::Gitlab::Config::Entry::Validatable + + validations do + validates :config, presence: true + validates :config, type: String + end + + def type + :job + end + + def value + { name: @config } + end + end + + class UnknownStrategy < ::Gitlab::Config::Entry::Node + def type + end + + def value + end + + def errors + ["#{location} has an unsupported type"] + end + end + end + end + end + end +end + +::Gitlab::Ci::Config::Entry::Need.prepend_if_ee('::EE::Gitlab::Ci::Config::Entry::Need') diff --git a/lib/gitlab/ci/config/entry/needs.rb b/lib/gitlab/ci/config/entry/needs.rb new file mode 100644 index 00000000000..28452aaaa16 --- /dev/null +++ b/lib/gitlab/ci/config/entry/needs.rb @@ -0,0 +1,55 @@ +# frozen_string_literal: true + +module Gitlab + module Ci + class Config + module Entry + ## + # Entry that represents a set of needs dependencies. + # + class Needs < ::Gitlab::Config::Entry::Node + include ::Gitlab::Config::Entry::Validatable + + validations do + validates :config, presence: true + + validate do + unless config.is_a?(Hash) || config.is_a?(Array) + errors.add(:config, 'can only be a Hash or an Array') + end + end + + validate on: :composed do + extra_keys = value.keys - opt(:allowed_needs) + if extra_keys.any? + errors.add(:config, "uses invalid types: #{extra_keys.join(', ')}") + end + end + end + + def compose!(deps = nil) + super(deps) do + [@config].flatten.each_with_index do |need, index| + @entries[index] = ::Gitlab::Config::Entry::Factory.new(Entry::Need) + .value(need) + .with(key: "need", parent: self, description: "need definition.") # rubocop:disable CodeReuse/ActiveRecord + .create! + end + + @entries.each_value do |entry| + entry.compose!(deps) + end + end + end + + def value + values = @entries.values.select(&:type) + values.group_by(&:type).transform_values do |values| + values.map(&:value) + end + end + end + end + end + end +end diff --git a/lib/gitlab/ci/config/normalizer.rb b/lib/gitlab/ci/config/normalizer.rb index 09f9bf5f69f..e714ef225f5 100644 --- a/lib/gitlab/ci/config/normalizer.rb +++ b/lib/gitlab/ci/config/normalizer.rb @@ -18,8 +18,8 @@ module Gitlab config[:dependencies] = expand_names(config[:dependencies]) end - if config[:needs] - config[:needs] = expand_names(config[:needs]) + if job_needs = config.dig(:needs, :job) + config[:needs][:job] = expand_needs(job_needs) end config @@ -36,6 +36,22 @@ module Gitlab end end + def expand_needs(job_needs) + return unless job_needs + + job_needs.flat_map do |job_need| + job_need_name = job_need[:name].to_sym + + if all_job_names = parallelized_jobs[job_need_name] + all_job_names.map do |job_name| + { name: job_name } + end + else + job_need + end + end + end + def parallelized_jobs strong_memoize(:parallelized_jobs) do @jobs_config.each_with_object({}) do |(job_name, config), hash| diff --git a/lib/gitlab/ci/templates/Android-Fastlane.gitlab-ci.yml b/lib/gitlab/ci/templates/Android-Fastlane.gitlab-ci.yml index be584814271..4ec3bb15230 100644 --- a/lib/gitlab/ci/templates/Android-Fastlane.gitlab-ci.yml +++ b/lib/gitlab/ci/templates/Android-Fastlane.gitlab-ci.yml @@ -113,9 +113,10 @@ promoteBeta: promoteProduction: extends: .promote_job stage: production - # We only allow production promotion on `master` because + # We only allow production promotion on the default branch because # it has its own production scoped secret variables only: - - master + variables: + - $CI_DEFAULT_BRANCH == $CI_COMMIT_REF_NAME script: - bundle exec fastlane promote_beta_to_production diff --git a/lib/gitlab/ci/templates/Docker.gitlab-ci.yml b/lib/gitlab/ci/templates/Docker.gitlab-ci.yml index 15cdbf63cb1..5160c05a251 100644 --- a/lib/gitlab/ci/templates/Docker.gitlab-ci.yml +++ b/lib/gitlab/ci/templates/Docker.gitlab-ci.yml @@ -10,7 +10,8 @@ docker-build-master: - docker build --pull -t "$CI_REGISTRY_IMAGE" . - docker push "$CI_REGISTRY_IMAGE" only: - - master + variables: + - $CI_DEFAULT_BRANCH == $CI_COMMIT_REF_NAME docker-build: # Official docker image. @@ -24,4 +25,5 @@ docker-build: - docker build --pull -t "$CI_REGISTRY_IMAGE:$CI_COMMIT_REF_SLUG" . - docker push "$CI_REGISTRY_IMAGE:$CI_COMMIT_REF_SLUG" except: - - master + variables: + - $CI_DEFAULT_BRANCH == $CI_COMMIT_REF_NAME diff --git a/lib/gitlab/ci/templates/Jobs/Deploy.gitlab-ci.yml b/lib/gitlab/ci/templates/Jobs/Deploy.gitlab-ci.yml index a95714d5684..416aa19e666 100644 --- a/lib/gitlab/ci/templates/Jobs/Deploy.gitlab-ci.yml +++ b/lib/gitlab/ci/templates/Jobs/Deploy.gitlab-ci.yml @@ -24,9 +24,8 @@ review: - tags kubernetes: active except: - refs: - - master variables: + - $CI_DEFAULT_BRANCH == $CI_COMMIT_REF_NAME - $REVIEW_DISABLED stop_review: @@ -48,9 +47,8 @@ stop_review: - tags kubernetes: active except: - refs: - - master variables: + - $CI_DEFAULT_BRANCH == $CI_COMMIT_REF_NAME - $REVIEW_DISABLED # Staging deploys are disabled by default since @@ -73,10 +71,9 @@ staging: name: staging url: http://$CI_PROJECT_PATH_SLUG-staging.$KUBE_INGRESS_BASE_DOMAIN only: - refs: - - master kubernetes: active variables: + - $CI_DEFAULT_BRANCH == $CI_COMMIT_REF_NAME - $STAGING_ENABLED # Canaries are disabled by default, but if you want them, @@ -98,10 +95,9 @@ canary: url: http://$CI_PROJECT_PATH_SLUG.$KUBE_INGRESS_BASE_DOMAIN when: manual only: - refs: - - master kubernetes: active variables: + - $CI_DEFAULT_BRANCH == $CI_COMMIT_REF_NAME - $CANARY_ENABLED .production: &production_template @@ -126,9 +122,9 @@ canary: production: <<: *production_template only: - refs: - - master kubernetes: active + variables: + - $CI_DEFAULT_BRANCH == $CI_COMMIT_REF_NAME except: variables: - $STAGING_ENABLED @@ -141,10 +137,9 @@ production_manual: when: manual allow_failure: false only: - refs: - - master kubernetes: active variables: + - $CI_DEFAULT_BRANCH == $CI_COMMIT_REF_NAME - $STAGING_ENABLED - $CANARY_ENABLED except: @@ -152,7 +147,7 @@ production_manual: - $INCREMENTAL_ROLLOUT_ENABLED - $INCREMENTAL_ROLLOUT_MODE -# This job implements incremental rollout on for every push to `master`. +# This job implements incremental rollout for every push to the default branch. .rollout: &rollout_template extends: .auto-deploy @@ -178,10 +173,9 @@ production_manual: when: manual # This selectors are backward compatible mode with $INCREMENTAL_ROLLOUT_ENABLED (before 11.4) only: - refs: - - master kubernetes: active variables: + - $CI_DEFAULT_BRANCH == $CI_COMMIT_REF_NAME - $INCREMENTAL_ROLLOUT_MODE == "manual" - $INCREMENTAL_ROLLOUT_ENABLED except: @@ -193,10 +187,9 @@ production_manual: when: delayed start_in: 5 minutes only: - refs: - - master kubernetes: active variables: + - $CI_DEFAULT_BRANCH == $CI_COMMIT_REF_NAME - $INCREMENTAL_ROLLOUT_MODE == "timed" timed rollout 10%: diff --git a/lib/gitlab/ci/templates/Julia.gitlab-ci.yml b/lib/gitlab/ci/templates/Julia.gitlab-ci.yml index 32d4e07d398..56785f2017d 100644 --- a/lib/gitlab/ci/templates/Julia.gitlab-ci.yml +++ b/lib/gitlab/ci/templates/Julia.gitlab-ci.yml @@ -64,7 +64,8 @@ pages: paths: - public only: - - master + variables: + - $CI_DEFAULT_BRANCH == $CI_COMMIT_REF_NAME # WARNING: This template is using the `julia` images from [Docker # Hub][3]. One can use custom Julia images and/or the official ones found diff --git a/lib/gitlab/ci/templates/Maven.gitlab-ci.yml b/lib/gitlab/ci/templates/Maven.gitlab-ci.yml index 84bb0ff3b33..f6b69051bfa 100644 --- a/lib/gitlab/ci/templates/Maven.gitlab-ci.yml +++ b/lib/gitlab/ci/templates/Maven.gitlab-ci.yml @@ -6,7 +6,7 @@ # This template will build and test your projects # * Caches downloaded dependencies and plugins between invocation. # * Verify but don't deploy merge requests. -# * Deploy built artifacts from master branch only. +# * Deploy built artifacts from the default branch only. variables: # This will suppress any download for dependencies and plugins or upload messages which would clutter the console log. @@ -33,7 +33,8 @@ cache: script: - 'mvn $MAVEN_CLI_OPTS verify' except: - - master + variables: + - $CI_DEFAULT_BRANCH == $CI_COMMIT_REF_NAME # Verify merge requests using JDK8 verify:jdk8: @@ -42,7 +43,7 @@ verify:jdk8: # To deploy packages from CI, create a ci_settings.xml file # For deploying packages to GitLab's Maven Repository: See https://docs.gitlab.com/ee/user/project/packages/maven_repository.html#creating-maven-packages-with-gitlab-cicd for more details. # Please note: The GitLab Maven Repository is currently only available in GitLab Premium / Ultimate. -# For `master` branch run `mvn deploy` automatically. +# For the default branch run `mvn deploy` automatically. deploy:jdk8: stage: deploy script: @@ -51,4 +52,5 @@ deploy:jdk8: fi - 'mvn $MAVEN_CLI_OPTS deploy -s ci_settings.xml' only: - - master + variables: + - $CI_DEFAULT_BRANCH == $CI_COMMIT_REF_NAME diff --git a/lib/gitlab/ci/templates/Mono.gitlab-ci.yml b/lib/gitlab/ci/templates/Mono.gitlab-ci.yml index 10fb6be6c39..9192f233eac 100644 --- a/lib/gitlab/ci/templates/Mono.gitlab-ci.yml +++ b/lib/gitlab/ci/templates/Mono.gitlab-ci.yml @@ -25,7 +25,8 @@ before_script: release: stage: deploy only: - - master + variables: + - $CI_DEFAULT_BRANCH == $CI_COMMIT_REF_NAME artifacts: paths: - build/release/MyProject.exe diff --git a/lib/gitlab/ci/templates/OpenShift.gitlab-ci.yml b/lib/gitlab/ci/templates/OpenShift.gitlab-ci.yml index 65abee1f5eb..91de258eb7c 100644 --- a/lib/gitlab/ci/templates/OpenShift.gitlab-ci.yml +++ b/lib/gitlab/ci/templates/OpenShift.gitlab-ci.yml @@ -49,7 +49,8 @@ review: only: - branches except: - - master + variables: + - $CI_DEFAULT_BRANCH == $CI_COMMIT_REF_NAME stop-review: <<: *deploy @@ -66,7 +67,8 @@ stop-review: only: - branches except: - - master + variables: + - $CI_DEFAULT_BRANCH == $CI_COMMIT_REF_NAME staging: <<: *deploy @@ -78,7 +80,8 @@ staging: name: staging url: http://$CI_PROJECT_NAME-staging.$OPENSHIFT_DOMAIN only: - - master + variables: + - $CI_DEFAULT_BRANCH == $CI_COMMIT_REF_NAME production: <<: *deploy @@ -91,4 +94,5 @@ production: name: production url: http://$CI_PROJECT_NAME.$OPENSHIFT_DOMAIN only: - - master + variables: + - $CI_DEFAULT_BRANCH == $CI_COMMIT_REF_NAME diff --git a/lib/gitlab/ci/templates/Packer.gitlab-ci.yml b/lib/gitlab/ci/templates/Packer.gitlab-ci.yml index 0a3cf3dcf77..28255eb893c 100644 --- a/lib/gitlab/ci/templates/Packer.gitlab-ci.yml +++ b/lib/gitlab/ci/templates/Packer.gitlab-ci.yml @@ -25,4 +25,5 @@ build: - find . -maxdepth 1 -name '*.json' -print0 | xargs -t0n1 packer build when: manual only: - - master + variables: + - $CI_DEFAULT_BRANCH == $CI_COMMIT_REF_NAME diff --git a/lib/gitlab/ci/templates/Pages/Brunch.gitlab-ci.yml b/lib/gitlab/ci/templates/Pages/Brunch.gitlab-ci.yml index d2dd3fbfb75..18778326029 100644 --- a/lib/gitlab/ci/templates/Pages/Brunch.gitlab-ci.yml +++ b/lib/gitlab/ci/templates/Pages/Brunch.gitlab-ci.yml @@ -12,4 +12,5 @@ pages: paths: - public only: - - master + variables: + - $CI_DEFAULT_BRANCH == $CI_COMMIT_REF_NAME diff --git a/lib/gitlab/ci/templates/Pages/Doxygen.gitlab-ci.yml b/lib/gitlab/ci/templates/Pages/Doxygen.gitlab-ci.yml index ba422c08614..920b2c7dbd0 100644 --- a/lib/gitlab/ci/templates/Pages/Doxygen.gitlab-ci.yml +++ b/lib/gitlab/ci/templates/Pages/Doxygen.gitlab-ci.yml @@ -10,4 +10,5 @@ pages: paths: - public only: - - master + variables: + - $CI_DEFAULT_BRANCH == $CI_COMMIT_REF_NAME diff --git a/lib/gitlab/ci/templates/Pages/Gatsby.gitlab-ci.yml b/lib/gitlab/ci/templates/Pages/Gatsby.gitlab-ci.yml index a683561a455..87f70abe0be 100644 --- a/lib/gitlab/ci/templates/Pages/Gatsby.gitlab-ci.yml +++ b/lib/gitlab/ci/templates/Pages/Gatsby.gitlab-ci.yml @@ -14,4 +14,5 @@ pages: paths: - public only: - - master + variables: + - $CI_DEFAULT_BRANCH == $CI_COMMIT_REF_NAME diff --git a/lib/gitlab/ci/templates/Pages/HTML.gitlab-ci.yml b/lib/gitlab/ci/templates/Pages/HTML.gitlab-ci.yml index 92f25280c6e..8aee121a2e9 100644 --- a/lib/gitlab/ci/templates/Pages/HTML.gitlab-ci.yml +++ b/lib/gitlab/ci/templates/Pages/HTML.gitlab-ci.yml @@ -9,4 +9,5 @@ pages: paths: - public only: - - master + variables: + - $CI_DEFAULT_BRANCH == $CI_COMMIT_REF_NAME diff --git a/lib/gitlab/ci/templates/Pages/Harp.gitlab-ci.yml b/lib/gitlab/ci/templates/Pages/Harp.gitlab-ci.yml index 0e206423fa5..a784e89a6ca 100644 --- a/lib/gitlab/ci/templates/Pages/Harp.gitlab-ci.yml +++ b/lib/gitlab/ci/templates/Pages/Harp.gitlab-ci.yml @@ -12,4 +12,5 @@ pages: paths: - public only: - - master + variables: + - $CI_DEFAULT_BRANCH == $CI_COMMIT_REF_NAME diff --git a/lib/gitlab/ci/templates/Pages/Hexo.gitlab-ci.yml b/lib/gitlab/ci/templates/Pages/Hexo.gitlab-ci.yml index d91a8d7421f..0750bb2cd97 100644 --- a/lib/gitlab/ci/templates/Pages/Hexo.gitlab-ci.yml +++ b/lib/gitlab/ci/templates/Pages/Hexo.gitlab-ci.yml @@ -14,4 +14,5 @@ pages: - node_modules key: project only: - - master + variables: + - $CI_DEFAULT_BRANCH == $CI_COMMIT_REF_NAME diff --git a/lib/gitlab/ci/templates/Pages/Hugo.gitlab-ci.yml b/lib/gitlab/ci/templates/Pages/Hugo.gitlab-ci.yml index 9a3ecd1c34f..45b06c040bd 100644 --- a/lib/gitlab/ci/templates/Pages/Hugo.gitlab-ci.yml +++ b/lib/gitlab/ci/templates/Pages/Hugo.gitlab-ci.yml @@ -8,10 +8,12 @@ pages: paths: - public only: - - master + variables: + - $CI_DEFAULT_BRANCH == $CI_COMMIT_REF_NAME test: script: - hugo except: - - master + variables: + - $CI_DEFAULT_BRANCH == $CI_COMMIT_REF_NAME diff --git a/lib/gitlab/ci/templates/Pages/Hyde.gitlab-ci.yml b/lib/gitlab/ci/templates/Pages/Hyde.gitlab-ci.yml index 7a441a2f70f..6fadda88a30 100644 --- a/lib/gitlab/ci/templates/Pages/Hyde.gitlab-ci.yml +++ b/lib/gitlab/ci/templates/Pages/Hyde.gitlab-ci.yml @@ -11,7 +11,8 @@ test: - pip install hyde - hyde gen except: - - master + variables: + - $CI_DEFAULT_BRANCH == $CI_COMMIT_REF_NAME pages: stage: deploy @@ -22,4 +23,5 @@ pages: paths: - public only: - - master + variables: + - $CI_DEFAULT_BRANCH == $CI_COMMIT_REF_NAME diff --git a/lib/gitlab/ci/templates/Pages/Jekyll.gitlab-ci.yml b/lib/gitlab/ci/templates/Pages/Jekyll.gitlab-ci.yml index e7dacd3a1fc..ef0adfdfcf2 100644 --- a/lib/gitlab/ci/templates/Pages/Jekyll.gitlab-ci.yml +++ b/lib/gitlab/ci/templates/Pages/Jekyll.gitlab-ci.yml @@ -17,7 +17,8 @@ test: paths: - test except: - - master + variables: + - $CI_DEFAULT_BRANCH == $CI_COMMIT_REF_NAME pages: stage: deploy @@ -27,4 +28,5 @@ pages: paths: - public only: - - master + variables: + - $CI_DEFAULT_BRANCH == $CI_COMMIT_REF_NAME diff --git a/lib/gitlab/ci/templates/Pages/Jigsaw.gitlab-ci.yml b/lib/gitlab/ci/templates/Pages/Jigsaw.gitlab-ci.yml index 2d26b86a328..b53fcb3308a 100644 --- a/lib/gitlab/ci/templates/Pages/Jigsaw.gitlab-ci.yml +++ b/lib/gitlab/ci/templates/Pages/Jigsaw.gitlab-ci.yml @@ -34,4 +34,5 @@ pages: paths: - public only: - - master + variables: + - $CI_DEFAULT_BRANCH == $CI_COMMIT_REF_NAME diff --git a/lib/gitlab/ci/templates/Pages/Lektor.gitlab-ci.yml b/lib/gitlab/ci/templates/Pages/Lektor.gitlab-ci.yml index 93ab8e0be0d..7fec535aedd 100644 --- a/lib/gitlab/ci/templates/Pages/Lektor.gitlab-ci.yml +++ b/lib/gitlab/ci/templates/Pages/Lektor.gitlab-ci.yml @@ -9,4 +9,5 @@ pages: paths: - public only: - - master + variables: + - $CI_DEFAULT_BRANCH == $CI_COMMIT_REF_NAME diff --git a/lib/gitlab/ci/templates/Pages/Metalsmith.gitlab-ci.yml b/lib/gitlab/ci/templates/Pages/Metalsmith.gitlab-ci.yml index 6524405133a..7e661fc9858 100644 --- a/lib/gitlab/ci/templates/Pages/Metalsmith.gitlab-ci.yml +++ b/lib/gitlab/ci/templates/Pages/Metalsmith.gitlab-ci.yml @@ -13,4 +13,5 @@ pages: paths: - public only: - - master + variables: + - $CI_DEFAULT_BRANCH == $CI_COMMIT_REF_NAME diff --git a/lib/gitlab/ci/templates/Pages/Middleman.gitlab-ci.yml b/lib/gitlab/ci/templates/Pages/Middleman.gitlab-ci.yml index 57ac323dfdf..cdd50485a81 100644 --- a/lib/gitlab/ci/templates/Pages/Middleman.gitlab-ci.yml +++ b/lib/gitlab/ci/templates/Pages/Middleman.gitlab-ci.yml @@ -12,7 +12,8 @@ test: - bundle install --path vendor - bundle exec middleman build except: - - master + variables: + - $CI_DEFAULT_BRANCH == $CI_COMMIT_REF_NAME pages: script: @@ -24,4 +25,5 @@ pages: paths: - public only: - - master + variables: + - $CI_DEFAULT_BRANCH == $CI_COMMIT_REF_NAME diff --git a/lib/gitlab/ci/templates/Pages/Nanoc.gitlab-ci.yml b/lib/gitlab/ci/templates/Pages/Nanoc.gitlab-ci.yml index 7f037b5f5cf..be1a2d0ff0a 100644 --- a/lib/gitlab/ci/templates/Pages/Nanoc.gitlab-ci.yml +++ b/lib/gitlab/ci/templates/Pages/Nanoc.gitlab-ci.yml @@ -9,4 +9,5 @@ pages: paths: - public only: - - master + variables: + - $CI_DEFAULT_BRANCH == $CI_COMMIT_REF_NAME diff --git a/lib/gitlab/ci/templates/Pages/Octopress.gitlab-ci.yml b/lib/gitlab/ci/templates/Pages/Octopress.gitlab-ci.yml index 6d912a89bc1..616f9a6c99b 100644 --- a/lib/gitlab/ci/templates/Pages/Octopress.gitlab-ci.yml +++ b/lib/gitlab/ci/templates/Pages/Octopress.gitlab-ci.yml @@ -12,4 +12,5 @@ pages: paths: - public only: - - master + variables: + - $CI_DEFAULT_BRANCH == $CI_COMMIT_REF_NAME diff --git a/lib/gitlab/ci/templates/Pages/SwaggerUI.gitlab-ci.yml b/lib/gitlab/ci/templates/Pages/SwaggerUI.gitlab-ci.yml index 8fd08ea7995..f3af9db3b42 100644 --- a/lib/gitlab/ci/templates/Pages/SwaggerUI.gitlab-ci.yml +++ b/lib/gitlab/ci/templates/Pages/SwaggerUI.gitlab-ci.yml @@ -26,4 +26,5 @@ pages: paths: - public only: - - master + variables: + - $CI_DEFAULT_BRANCH == $CI_COMMIT_REF_NAME diff --git a/lib/gitlab/ci/templates/Python.gitlab-ci.yml b/lib/gitlab/ci/templates/Python.gitlab-ci.yml index 00b8b94b574..9f115c05802 100644 --- a/lib/gitlab/ci/templates/Python.gitlab-ci.yml +++ b/lib/gitlab/ci/templates/Python.gitlab-ci.yml @@ -48,4 +48,5 @@ pages: paths: - public only: - - master + variables: + - $CI_DEFAULT_BRANCH == $CI_COMMIT_REF_NAME diff --git a/lib/gitlab/ci/templates/Swift.gitlab-ci.yml b/lib/gitlab/ci/templates/Swift.gitlab-ci.yml index ffed7a0fec2..f7b4552f8da 100644 --- a/lib/gitlab/ci/templates/Swift.gitlab-ci.yml +++ b/lib/gitlab/ci/templates/Swift.gitlab-ci.yml @@ -22,7 +22,8 @@ archive_project: - xcodebuild clean archive -archivePath build/ProjectName -scheme SchemeName - xcodebuild -exportArchive -exportFormat ipa -archivePath "build/ProjectName.xcarchive" -exportPath "build/ProjectName.ipa" -exportProvisioningProfile "ProvisioningProfileName" only: - - master + variables: + - $CI_DEFAULT_BRANCH == $CI_COMMIT_REF_NAME artifacts: paths: - build/ProjectName.ipa diff --git a/lib/gitlab/ci/templates/Terraform.gitlab-ci.yml b/lib/gitlab/ci/templates/Terraform.gitlab-ci.yml index f374bc7e26a..85c5104eaad 100644 --- a/lib/gitlab/ci/templates/Terraform.gitlab-ci.yml +++ b/lib/gitlab/ci/templates/Terraform.gitlab-ci.yml @@ -53,4 +53,5 @@ apply: - plan when: manual only: - - master + variables: + - $CI_DEFAULT_BRANCH == $CI_COMMIT_REF_NAME diff --git a/lib/gitlab/ci/yaml_processor.rb b/lib/gitlab/ci/yaml_processor.rb index f6a3abefcfb..c2a55fa8b1b 100644 --- a/lib/gitlab/ci/yaml_processor.rb +++ b/lib/gitlab/ci/yaml_processor.rb @@ -40,7 +40,7 @@ module Gitlab environment: job[:environment_name], coverage_regex: job[:coverage], yaml_variables: yaml_variables(name), - needs_attributes: job[:needs]&.map { |need| { name: need } }, + needs_attributes: job.dig(:needs, :job), interruptible: job[:interruptible], rules: job[:rules], options: { @@ -59,7 +59,7 @@ module Gitlab instance: job[:instance], start_in: job[:start_in], trigger: job[:trigger], - bridge_needs: job[:needs] + bridge_needs: job.dig(:needs, :bridge)&.first }.compact }.compact end @@ -159,17 +159,19 @@ module Gitlab end def validate_job_needs!(name, job) - return unless job[:needs] + return unless job.dig(:needs, :job) stage_index = @stages.index(job[:stage]) - job[:needs].each do |need| - raise ValidationError, "#{name} job: undefined need: #{need}" unless @jobs[need.to_sym] + job.dig(:needs, :job).each do |need| + need_job_name = need[:name] - needs_stage_index = @stages.index(@jobs[need.to_sym][:stage]) + raise ValidationError, "#{name} job: undefined need: #{need_job_name}" unless @jobs[need_job_name.to_sym] + + needs_stage_index = @stages.index(@jobs[need_job_name.to_sym][:stage]) unless needs_stage_index.present? && needs_stage_index < stage_index - raise ValidationError, "#{name} job: need #{need} is not defined in prior stages" + raise ValidationError, "#{name} job: need #{need_job_name} is not defined in prior stages" end end end diff --git a/lib/gitlab/config/entry/configurable.rb b/lib/gitlab/config/entry/configurable.rb index b7ec4b7c4f8..bda84dc2cff 100644 --- a/lib/gitlab/config/entry/configurable.rb +++ b/lib/gitlab/config/entry/configurable.rb @@ -29,22 +29,24 @@ module Gitlab def compose!(deps = nil) return unless valid? - self.class.nodes.each do |key, factory| - # If we override the config type validation - # we can end with different config types like String - next unless config.is_a?(Hash) + super do + self.class.nodes.each do |key, factory| + # If we override the config type validation + # we can end with different config types like String + next unless config.is_a?(Hash) - factory - .value(config[key]) - .with(key: key, parent: self) + factory + .value(config[key]) + .with(key: key, parent: self) - entries[key] = factory.create! - end + entries[key] = factory.create! + end - yield if block_given? + yield if block_given? - entries.each_value do |entry| - entry.compose!(deps) + entries.each_value do |entry| + entry.compose!(deps) + end end end # rubocop: enable CodeReuse/ActiveRecord @@ -67,12 +69,13 @@ module Gitlab private # rubocop: disable CodeReuse/ActiveRecord - def entry(key, entry, description: nil, default: nil, inherit: nil, reserved: nil) + def entry(key, entry, description: nil, default: nil, inherit: nil, reserved: nil, metadata: {}) factory = ::Gitlab::Config::Entry::Factory.new(entry) .with(description: description) .with(default: default) .with(inherit: inherit) .with(reserved: reserved) + .metadata(metadata) (@nodes ||= {}).merge!(key.to_sym => factory) end diff --git a/lib/gitlab/config/entry/node.rb b/lib/gitlab/config/entry/node.rb index e014f15fbd8..84d3409ed91 100644 --- a/lib/gitlab/config/entry/node.rb +++ b/lib/gitlab/config/entry/node.rb @@ -112,6 +112,10 @@ module Gitlab @aspects ||= [] end + def self.with_aspect(blk) + self.aspects.append(blk) + end + private attr_reader :entries diff --git a/lib/gitlab/config/entry/simplifiable.rb b/lib/gitlab/config/entry/simplifiable.rb index d58aba07d15..315f1947e2c 100644 --- a/lib/gitlab/config/entry/simplifiable.rb +++ b/lib/gitlab/config/entry/simplifiable.rb @@ -4,11 +4,11 @@ module Gitlab module Config module Entry class Simplifiable < SimpleDelegator - EntryStrategy = Struct.new(:name, :condition) + EntryStrategy = Struct.new(:name, :klass, :condition) attr_reader :subject - def initialize(config, **metadata) + def initialize(config, **metadata, &blk) unless self.class.const_defined?(:UnknownStrategy) raise ArgumentError, 'UndefinedStrategy not available!' end @@ -19,14 +19,13 @@ module Gitlab entry = self.class.entry_class(strategy) - @subject = entry.new(config, metadata) + @subject = entry.new(config, metadata, &blk) - yield(@subject) if block_given? super(@subject) end def self.strategy(name, **opts) - EntryStrategy.new(name, opts.fetch(:if)).tap do |strategy| + EntryStrategy.new(name, opts.dig(:class), opts.fetch(:if)).tap do |strategy| strategies.append(strategy) end end @@ -37,7 +36,7 @@ module Gitlab def self.entry_class(strategy) if strategy.present? - self.const_get(strategy.name, false) + strategy.klass || self.const_get(strategy.name, false) else self::UnknownStrategy end diff --git a/lib/gitlab/config/entry/validatable.rb b/lib/gitlab/config/entry/validatable.rb index 1c88c68c11c..45b852dc2e0 100644 --- a/lib/gitlab/config/entry/validatable.rb +++ b/lib/gitlab/config/entry/validatable.rb @@ -7,14 +7,27 @@ module Gitlab extend ActiveSupport::Concern def self.included(node) - node.aspects.append -> do - @validator = self.class.validator.new(self) - @validator.validate(:new) + node.with_aspect -> do + validate(:new) end end + def validator + @validator ||= self.class.validator.new(self) + end + + def validate(context = nil) + validator.validate(context) + end + + def compose!(deps = nil, &blk) + super(deps, &blk) + + validate(:composed) + end + def errors - @validator.messages + descendants.flat_map(&:errors) # rubocop:disable Gitlab/ModuleWithInstanceVariables + validator.messages + descendants.flat_map(&:errors) end class_methods do diff --git a/locale/gitlab.pot b/locale/gitlab.pot index db409df295b..bcf7405a711 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -4958,6 +4958,9 @@ msgstr "" msgid "CurrentUser|Settings" msgstr "" +msgid "CurrentUser|Start a trial" +msgstr "" + msgid "Custom CI configuration path" msgstr "" @@ -9676,7 +9679,7 @@ msgstr "" msgid "Kubernetes cluster creation time exceeds timeout; %{timeout}" msgstr "" -msgid "Kubernetes cluster integration was not removed." +msgid "Kubernetes cluster integration and resources are being removed." msgstr "" msgid "Kubernetes cluster integration was successfully removed." @@ -11285,7 +11288,7 @@ msgstr "" msgid "No preview for this file type" msgstr "" -msgid "No prioritised labels with such name or description" +msgid "No prioritized labels with such name or description" msgstr "" msgid "No public groups" diff --git a/scripts/review_apps/review-apps.sh b/scripts/review_apps/review-apps.sh index b405e992b63..0842f7871ee 100755 --- a/scripts/review_apps/review-apps.sh +++ b/scripts/review_apps/review-apps.sh @@ -207,19 +207,16 @@ function download_chart() { } function base_config_changed() { - git fetch origin master --depth=50 + if [ -z "${CI_MERGE_REQUEST_IID}" ]; then return; fi - [ -n "$(git diff origin/master... --name-only -- scripts/review_apps/base-config.yaml)" ] + curl "${CI_API_V4_URL}/projects/${CI_MERGE_REQUEST_PROJECT_ID}/merge_requests/${CI_MERGE_REQUEST_IID}/changes" | jq '.changes | any(.old_path == "scripts/review_apps/base-config.yaml")' } function deploy() { local name="$CI_ENVIRONMENT_SLUG" local edition="${GITLAB_EDITION-ce}" local base_config_file_ref="master" - echo "REVIEW_APP_CONFIG_CHANGED: ${REVIEW_APP_CONFIG_CHANGED}" - if [ -n "${REVIEW_APP_CONFIG_CHANGED}" ]; then - base_config_file_ref="$CI_COMMIT_SHA" - fi + if [[ "$(base_config_changed)" == "true" ]]; then base_config_file_ref="$CI_COMMIT_SHA"; fi local base_config_file="https://gitlab.com/gitlab-org/gitlab/raw/${base_config_file_ref}/scripts/review_apps/base-config.yaml" echoinfo "Deploying ${name}..." true diff --git a/spec/features/projects/labels/search_labels_spec.rb b/spec/features/projects/labels/search_labels_spec.rb index 2d5a138c3cc..e2eec7400ff 100644 --- a/spec/features/projects/labels/search_labels_spec.rb +++ b/spec/features/projects/labels/search_labels_spec.rb @@ -68,7 +68,7 @@ describe 'Search for labels', :js do find('#label-search').native.send_keys(:enter) page.within('.prioritized-labels') do - expect(page).to have_content('No prioritised labels with such name or description') + expect(page).to have_content('No prioritized labels with such name or description') end page.within('.other-labels') do diff --git a/spec/frontend/lib/utils/datetime_utility_spec.js b/spec/frontend/lib/utils/datetime_utility_spec.js index e4c97543b03..ee27789b6b9 100644 --- a/spec/frontend/lib/utils/datetime_utility_spec.js +++ b/spec/frontend/lib/utils/datetime_utility_spec.js @@ -474,3 +474,11 @@ describe('getDatesInRange', () => { }); }); }); + +describe('secondsToMilliseconds', () => { + it('converts seconds to milliseconds correctly', () => { + expect(datetimeUtility.secondsToMilliseconds(0)).toBe(0); + expect(datetimeUtility.secondsToMilliseconds(60)).toBe(60000); + expect(datetimeUtility.secondsToMilliseconds(123)).toBe(123000); + }); +}); diff --git a/spec/frontend/releases/list/components/release_block_footer_spec.js b/spec/frontend/releases/list/components/release_block_footer_spec.js new file mode 100644 index 00000000000..172147f1cc8 --- /dev/null +++ b/spec/frontend/releases/list/components/release_block_footer_spec.js @@ -0,0 +1,163 @@ +import { mount } from '@vue/test-utils'; +import ReleaseBlockFooter from '~/releases/list/components/release_block_footer.vue'; +import Icon from '~/vue_shared/components/icon.vue'; +import { GlLink } from '@gitlab/ui'; +import { trimText } from 'helpers/text_helper'; +import { release } from '../../mock_data'; +import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils'; + +jest.mock('~/vue_shared/mixins/timeago', () => ({ + methods: { + timeFormated() { + return '7 fortnightes ago'; + }, + tooltipTitle() { + return 'February 30, 2401'; + }, + }, +})); + +describe('Release block footer', () => { + let wrapper; + let releaseClone; + + const factory = (props = {}) => { + wrapper = mount(ReleaseBlockFooter, { + propsData: { + ...convertObjectPropsToCamelCase(releaseClone), + ...props, + }, + sync: false, + }); + + return wrapper.vm.$nextTick(); + }; + + beforeEach(() => { + releaseClone = JSON.parse(JSON.stringify(release)); + }); + + afterEach(() => { + wrapper.destroy(); + }); + + const commitInfoSection = () => wrapper.find('.js-commit-info'); + const commitInfoSectionLink = () => commitInfoSection().find(GlLink); + const tagInfoSection = () => wrapper.find('.js-tag-info'); + const tagInfoSectionLink = () => tagInfoSection().find(GlLink); + const authorDateInfoSection = () => wrapper.find('.js-author-date-info'); + + describe('with all props provided', () => { + beforeEach(() => factory()); + + it('renders the commit icon', () => { + const commitIcon = commitInfoSection().find(Icon); + + expect(commitIcon.exists()).toBe(true); + expect(commitIcon.props('name')).toBe('commit'); + }); + + it('renders the commit SHA with a link', () => { + const commitLink = commitInfoSectionLink(); + + expect(commitLink.exists()).toBe(true); + expect(commitLink.text()).toBe(releaseClone.commit.short_id); + expect(commitLink.attributes('href')).toBe(releaseClone.commit_path); + }); + + it('renders the tag icon', () => { + const commitIcon = tagInfoSection().find(Icon); + + expect(commitIcon.exists()).toBe(true); + expect(commitIcon.props('name')).toBe('tag'); + }); + + it('renders the tag name with a link', () => { + const commitLink = tagInfoSection().find(GlLink); + + expect(commitLink.exists()).toBe(true); + expect(commitLink.text()).toBe(releaseClone.tag_name); + expect(commitLink.attributes('href')).toBe(releaseClone.tag_path); + }); + + it('renders the author and creation time info', () => { + expect(trimText(authorDateInfoSection().text())).toBe( + `Created 7 fortnightes ago by ${releaseClone.author.username}`, + ); + }); + + it("renders the author's avatar image", () => { + const avatarImg = authorDateInfoSection().find('img'); + + expect(avatarImg.exists()).toBe(true); + expect(avatarImg.attributes('src')).toBe(releaseClone.author.avatar_url); + }); + + it("renders a link to the author's profile", () => { + const authorLink = authorDateInfoSection().find(GlLink); + + expect(authorLink.exists()).toBe(true); + expect(authorLink.attributes('href')).toBe(releaseClone.author.web_url); + }); + }); + + describe('without any commit info', () => { + beforeEach(() => factory({ commit: undefined })); + + it('does not render any commit info', () => { + expect(commitInfoSection().exists()).toBe(false); + }); + }); + + describe('without a commit URL', () => { + beforeEach(() => factory({ commitPath: undefined })); + + it('renders the commit SHA as plain text (instead of a link)', () => { + expect(commitInfoSectionLink().exists()).toBe(false); + expect(commitInfoSection().text()).toBe(releaseClone.commit.short_id); + }); + }); + + describe('without a tag name', () => { + beforeEach(() => factory({ tagName: undefined })); + + it('does not render any tag info', () => { + expect(tagInfoSection().exists()).toBe(false); + }); + }); + + describe('without a tag URL', () => { + beforeEach(() => factory({ tagPath: undefined })); + + it('renders the tag name as plain text (instead of a link)', () => { + expect(tagInfoSectionLink().exists()).toBe(false); + expect(tagInfoSection().text()).toBe(releaseClone.tag_name); + }); + }); + + describe('without any author info', () => { + beforeEach(() => factory({ author: undefined })); + + it('renders the release date without the author name', () => { + expect(trimText(authorDateInfoSection().text())).toBe('Created 7 fortnightes ago'); + }); + }); + + describe('without a released at date', () => { + beforeEach(() => factory({ releasedAt: undefined })); + + it('renders the author name without the release date', () => { + expect(trimText(authorDateInfoSection().text())).toBe( + `Created by ${releaseClone.author.username}`, + ); + }); + }); + + describe('without a release date or author info', () => { + beforeEach(() => factory({ author: undefined, releasedAt: undefined })); + + it('does not render any author or release date info', () => { + expect(authorDateInfoSection().exists()).toBe(false); + }); + }); +}); diff --git a/spec/frontend/releases/list/components/release_block_spec.js b/spec/frontend/releases/list/components/release_block_spec.js index 6601c4265f6..b63ef068d8e 100644 --- a/spec/frontend/releases/list/components/release_block_spec.js +++ b/spec/frontend/releases/list/components/release_block_spec.js @@ -1,5 +1,6 @@ import { mount } from '@vue/test-utils'; import ReleaseBlock from '~/releases/list/components/release_block.vue'; +import ReleaseBlockFooter from '~/releases/list/components/release_block_footer.vue'; import timeagoMixin from '~/vue_shared/mixins/timeago'; import { first } from 'underscore'; import { release } from '../../mock_data'; @@ -21,14 +22,16 @@ describe('Release block', () => { let wrapper; let releaseClone; - const factory = (releaseProp, releaseEditPageFeatureFlag = true) => { + const factory = (releaseProp, featureFlags = {}) => { wrapper = mount(ReleaseBlock, { propsData: { release: releaseProp, }, provide: { glFeatures: { - releaseEditPage: releaseEditPageFeatureFlag, + releaseEditPage: true, + releaseIssueSummary: true, + ...featureFlags, }, }, sync: false, @@ -142,6 +145,10 @@ describe('Release block', () => { expect(milestoneLink.attributes('data-original-title')).toBe(milestone.description); }); + + it('renders the footer', () => { + expect(wrapper.find(ReleaseBlockFooter).exists()).toBe(true); + }); }); it('renders commit sha', () => { @@ -173,7 +180,7 @@ describe('Release block', () => { }); it('does not render an edit button if the releaseEditPage feature flag is disabled', () => - factory(releaseClone, false).then(() => { + factory(releaseClone, { releaseEditPage: false }).then(() => { expect(editButton().exists()).toBe(false); })); diff --git a/spec/frontend/releases/mock_data.js b/spec/frontend/releases/mock_data.js index 32095fd4b78..61d95b86b1c 100644 --- a/spec/frontend/releases/mock_data.js +++ b/spec/frontend/releases/mock_data.js @@ -30,6 +30,7 @@ export const milestones = [ export const release = { name: 'New release', tag_name: 'v0.3', + tag_path: '/root/release-test/-/tags/v0.3', description: 'A super nice release!', description_html: '<p data-sourcepos="1:1-1:21" dir="auto">A super nice release!</p>', created_at: '2019-08-26T17:54:04.952Z', @@ -56,6 +57,7 @@ export const release = { committer_email: 'admin@example.com', committed_date: '2019-08-26T17:47:07.000Z', }, + commit_path: '/root/release-test/commit/c22b0728d1b465f82898c884d32b01aa642f96c1', upcoming_release: false, milestones, assets: { diff --git a/spec/helpers/users_helper_spec.rb b/spec/helpers/users_helper_spec.rb index 59abe8c09e1..172ead158fb 100644 --- a/spec/helpers/users_helper_spec.rb +++ b/spec/helpers/users_helper_spec.rb @@ -76,6 +76,10 @@ describe UsersHelper do allow(helper).to receive(:can?).and_return(false) end + after do + expect(items).not_to include(:start_trial) + end + it 'includes all default items' do expect(items).to include(:help, :sign_out) end diff --git a/spec/javascripts/blob/viewer/index_spec.js b/spec/javascripts/blob/viewer/index_spec.js index bbc59632f3c..766c3378584 100644 --- a/spec/javascripts/blob/viewer/index_spec.js +++ b/spec/javascripts/blob/viewer/index_spec.js @@ -176,15 +176,13 @@ describe('Blob viewer', () => { }); }); - describe('a URL inside the blob content', () => { - beforeEach(() => { + describe('linkifyURLs', () => { + it('renders a plain url as a link in simple view', done => { mock.onGet('http://test.host/snippets/1.json?viewer=simple').reply(200, { html: '<div class="js-blob-content"><pre class="code"><code><span class="line" lang="yaml"><span class="c1">To install gitlab-shell you also need a Go compiler version 1.8 or newer. https://golang.org/dl/</span></span></code></pre></div>', }); - }); - it('is rendered as a link in simple view', done => { asyncClick() .then(() => { expect(document.querySelector('.blob-viewer[data-type="simple"]').innerHTML).toContain( @@ -197,5 +195,24 @@ describe('Blob viewer', () => { done(); }); }); + + it('leaves an unescaped url untouched', done => { + mock.onGet('http://test.host/snippets/1.json?viewer=simple').reply(200, { + html: + '<div class="js-blob-content"><pre class="code"><code><span class="line" lang="yaml"><a href="https://golang.org/dl/">golang</a></span></span></code></pre></div>', + }); + + asyncClick() + .then(() => { + expect(document.querySelector('.blob-viewer[data-type="simple"]').innerHTML).toContain( + '<a href="https://golang.org/dl/">golang</a>', + ); + done(); + }) + .catch(() => { + fail(); + done(); + }); + }); }); }); diff --git a/spec/lib/gitlab/ci/config/entry/job_spec.rb b/spec/lib/gitlab/ci/config/entry/job_spec.rb index d3eb5a9663f..9fe18caf689 100644 --- a/spec/lib/gitlab/ci/config/entry/job_spec.rb +++ b/spec/lib/gitlab/ci/config/entry/job_spec.rb @@ -23,7 +23,7 @@ describe Gitlab::Ci::Config::Entry::Job do let(:result) do %i[before_script script stage type after_script cache - image services only except rules variables artifacts + image services only except rules needs variables artifacts environment coverage retry] end @@ -384,21 +384,6 @@ describe Gitlab::Ci::Config::Entry::Job do end context 'when has needs' do - context 'that are not a array of strings' do - let(:config) do - { - stage: 'test', - script: 'echo', - needs: 'build-job' - } - end - - it 'returns error about invalid type' do - expect(entry).not_to be_valid - expect(entry.errors).to include 'job needs should be an array of strings' - end - end - context 'when have dependencies that are not subset of needs' do let(:config) do { diff --git a/spec/lib/gitlab/ci/config/entry/need_spec.rb b/spec/lib/gitlab/ci/config/entry/need_spec.rb new file mode 100644 index 00000000000..d119e604900 --- /dev/null +++ b/spec/lib/gitlab/ci/config/entry/need_spec.rb @@ -0,0 +1,36 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe ::Gitlab::Ci::Config::Entry::Need do + subject(:need) { described_class.new(config) } + + context 'when job is specified' do + let(:config) { 'job_name' } + + describe '#valid?' do + it { is_expected.to be_valid } + end + + describe '#value' do + it 'returns job needs configuration' do + expect(need.value).to eq(name: 'job_name') + end + end + end + + context 'when need is empty' do + let(:config) { '' } + + describe '#valid?' do + it { is_expected.not_to be_valid } + end + + describe '#errors' do + it 'is returns an error about an empty config' do + expect(need.errors) + .to contain_exactly("job config can't be blank") + end + end + end +end diff --git a/spec/lib/gitlab/ci/config/entry/needs_spec.rb b/spec/lib/gitlab/ci/config/entry/needs_spec.rb new file mode 100644 index 00000000000..f4a76b52d30 --- /dev/null +++ b/spec/lib/gitlab/ci/config/entry/needs_spec.rb @@ -0,0 +1,84 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe ::Gitlab::Ci::Config::Entry::Needs do + subject(:needs) { described_class.new(config) } + + before do + needs.metadata[:allowed_needs] = %i[job] + end + + describe 'validations' do + before do + needs.compose! + end + + context 'when entry config value is correct' do + let(:config) { ['job_name'] } + + describe '#valid?' do + it { is_expected.to be_valid } + end + end + + context 'when config value has wrong type' do + let(:config) { 123 } + + describe '#valid?' do + it { is_expected.not_to be_valid } + end + + describe '#errors' do + it 'returns error about incorrect type' do + expect(needs.errors) + .to include('needs config can only be a hash or an array') + end + end + end + + context 'when wrong needs type is used' do + let(:config) { [123] } + + describe '#valid?' do + it { is_expected.not_to be_valid } + end + + describe '#errors' do + it 'returns error about incorrect type' do + expect(needs.errors).to contain_exactly( + 'need has an unsupported type') + end + end + end + end + + describe '.compose!' do + context 'when valid job entries composed' do + let(:config) { %w[first_job_name second_job_name] } + + before do + needs.compose! + end + + describe '#value' do + it 'returns key value' do + expect(needs.value).to eq( + job: [ + { name: 'first_job_name' }, + { name: 'second_job_name' } + ] + ) + end + end + + describe '#descendants' do + it 'creates valid descendant nodes' do + expect(needs.descendants.count).to eq 2 + expect(needs.descendants) + .to all(be_an_instance_of(::Gitlab::Ci::Config::Entry::Need)) + end + end + end + end +end diff --git a/spec/lib/gitlab/ci/config/normalizer_spec.rb b/spec/lib/gitlab/ci/config/normalizer_spec.rb index 6b766cc37bf..bf880478387 100644 --- a/spec/lib/gitlab/ci/config/normalizer_spec.rb +++ b/spec/lib/gitlab/ci/config/normalizer_spec.rb @@ -7,6 +7,16 @@ describe Gitlab::Ci::Config::Normalizer do let(:job_config) { { script: 'rspec', parallel: 5, name: 'rspec' } } let(:config) { { job_name => job_config } } + let(:expanded_job_names) do + [ + "rspec 1/5", + "rspec 2/5", + "rspec 3/5", + "rspec 4/5", + "rspec 5/5" + ] + end + describe '.normalize_jobs' do subject { described_class.new(config).normalize_jobs } @@ -15,9 +25,7 @@ describe Gitlab::Ci::Config::Normalizer do end it 'has parallelized jobs' do - job_names = [:"rspec 1/5", :"rspec 2/5", :"rspec 3/5", :"rspec 4/5", :"rspec 5/5"] - - is_expected.to include(*job_names) + is_expected.to include(*expanded_job_names.map(&:to_sym)) end it 'sets job instance in options' do @@ -43,49 +51,109 @@ describe Gitlab::Ci::Config::Normalizer do let(:job_name) { :"rspec 35/2" } it 'properly parallelizes job names' do - job_names = [:"rspec 35/2 1/5", :"rspec 35/2 2/5", :"rspec 35/2 3/5", :"rspec 35/2 4/5", :"rspec 35/2 5/5"] + job_names = [ + :"rspec 35/2 1/5", + :"rspec 35/2 2/5", + :"rspec 35/2 3/5", + :"rspec 35/2 4/5", + :"rspec 35/2 5/5" + ] is_expected.to include(*job_names) end end - %i[dependencies needs].each do |context| - context "when job has #{context} on parallelized jobs" do + context 'for dependencies' do + context "when job has dependencies on parallelized jobs" do let(:config) do { job_name => job_config, - other_job: { script: 'echo 1', context => [job_name.to_s] } + other_job: { script: 'echo 1', dependencies: [job_name.to_s] } } end - it "parallelizes #{context}" do - job_names = ["rspec 1/5", "rspec 2/5", "rspec 3/5", "rspec 4/5", "rspec 5/5"] - - expect(subject[:other_job][context]).to include(*job_names) + it "parallelizes dependencies" do + expect(subject[:other_job][:dependencies]).to eq(expanded_job_names) end it "does not include original job name in #{context}" do - expect(subject[:other_job][context]).not_to include(job_name) + expect(subject[:other_job][:dependencies]).not_to include(job_name) end end - context "when there are #{context} which are both parallelized and not" do + context "when there are dependencies which are both parallelized and not" do let(:config) do { job_name => job_config, other_job: { script: 'echo 1' }, - final_job: { script: 'echo 1', context => [job_name.to_s, "other_job"] } + final_job: { script: 'echo 1', dependencies: [job_name.to_s, "other_job"] } } end - it "parallelizes #{context}" do + it "parallelizes dependencies" do job_names = ["rspec 1/5", "rspec 2/5", "rspec 3/5", "rspec 4/5", "rspec 5/5"] - expect(subject[:final_job][context]).to include(*job_names) + expect(subject[:final_job][:dependencies]).to include(*job_names) + end + + it "includes the regular job in dependencies" do + expect(subject[:final_job][:dependencies]).to include('other_job') + end + end + end + + context 'for needs' do + let(:expanded_job_attributes) do + expanded_job_names.map do |job_name| + { name: job_name } + end + end + + context "when job has needs on parallelized jobs" do + let(:config) do + { + job_name => job_config, + other_job: { + script: 'echo 1', + needs: { + job: [ + { name: job_name.to_s } + ] + } + } + } + end + + it "parallelizes needs" do + expect(subject.dig(:other_job, :needs, :job)).to eq(expanded_job_attributes) + end + end + + context "when there are dependencies which are both parallelized and not" do + let(:config) do + { + job_name => job_config, + other_job: { + script: 'echo 1' + }, + final_job: { + script: 'echo 1', + needs: { + job: [ + { name: job_name.to_s }, + { name: "other_job" } + ] + } + } + } + end + + it "parallelizes dependencies" do + expect(subject.dig(:final_job, :needs, :job)).to include(*expanded_job_attributes) end - it "includes the regular job in #{context}" do - expect(subject[:final_job][context]).to include('other_job') + it "includes the regular job in dependencies" do + expect(subject.dig(:final_job, :needs, :job)).to include(name: 'other_job') end end end diff --git a/spec/lib/gitlab/ci/yaml_processor_spec.rb b/spec/lib/gitlab/ci/yaml_processor_spec.rb index c747ea670bb..dc7bbc519ee 100644 --- a/spec/lib/gitlab/ci/yaml_processor_spec.rb +++ b/spec/lib/gitlab/ci/yaml_processor_spec.rb @@ -1293,7 +1293,7 @@ module Gitlab end end - describe "Needs" do + describe "Job Needs" do let(:needs) { } let(:dependencies) { } @@ -1301,6 +1301,7 @@ module Gitlab { build1: { stage: 'build', script: 'test' }, build2: { stage: 'build', script: 'test' }, + parallel: { stage: 'build', script: 'test', parallel: 2 }, test1: { stage: 'test', script: 'test', needs: needs, dependencies: dependencies }, test2: { stage: 'test', script: 'test' }, deploy: { stage: 'test', script: 'test' } @@ -1317,7 +1318,7 @@ module Gitlab let(:needs) { %w(build1 build2) } it "does create jobs with valid specification" do - expect(subject.builds.size).to eq(5) + expect(subject.builds.size).to eq(7) expect(subject.builds[0]).to eq( stage: "build", stage_idx: 1, @@ -1329,16 +1330,11 @@ module Gitlab allow_failure: false, yaml_variables: [] ) - expect(subject.builds[2]).to eq( + expect(subject.builds[4]).to eq( stage: "test", stage_idx: 2, name: "test1", - options: { - script: ["test"], - # This does not make sense, there is a follow-up: - # https://gitlab.com/gitlab-org/gitlab-foss/issues/65569 - bridge_needs: %w[build1 build2] - }, + options: { script: ["test"] }, needs_attributes: [ { name: "build1" }, { name: "build2" } @@ -1350,10 +1346,25 @@ module Gitlab end end - context 'needs two builds defined as symbols' do - let(:needs) { [:build1, :build2] } + context 'needs parallel job' do + let(:needs) { %w(parallel) } - it { expect { subject }.not_to raise_error } + it "does create jobs with valid specification" do + expect(subject.builds.size).to eq(7) + expect(subject.builds[4]).to eq( + stage: "test", + stage_idx: 2, + name: "test1", + options: { script: ["test"] }, + needs_attributes: [ + { name: "parallel 1/2" }, + { name: "parallel 2/2" } + ], + when: "on_success", + allow_failure: false, + yaml_variables: [] + ) + end end context 'undefined need' do diff --git a/spec/models/ci/pipeline_spec.rb b/spec/models/ci/pipeline_spec.rb index 5e5a94f8cda..9295bb993ce 100644 --- a/spec/models/ci/pipeline_spec.rb +++ b/spec/models/ci/pipeline_spec.rb @@ -2893,6 +2893,25 @@ describe Ci::Pipeline, :mailer do it 'contains yaml errors' do expect(pipeline).to have_yaml_errors + expect(pipeline.yaml_errors).to include('contains unknown keys') + end + end + + context 'when pipeline has undefined error' do + let(:pipeline) do + create(:ci_pipeline, config: {}) + end + + it 'contains yaml errors' do + expect(::Gitlab::Ci::YamlProcessor).to receive(:new) + .and_raise(RuntimeError, 'undefined failure') + + expect(Gitlab::Sentry).to receive(:track_acceptable_exception) + .with(be_a(RuntimeError), anything) + .and_call_original + + expect(pipeline).to have_yaml_errors + expect(pipeline.yaml_errors).to include('Undefined error') end end diff --git a/spec/services/clusters/destroy_service_spec.rb b/spec/services/clusters/destroy_service_spec.rb new file mode 100644 index 00000000000..c0fcc971500 --- /dev/null +++ b/spec/services/clusters/destroy_service_spec.rb @@ -0,0 +1,56 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Clusters::DestroyService do + describe '#execute' do + subject { described_class.new(cluster.user, params).execute(cluster) } + + let!(:cluster) { create(:cluster, :project, :provided_by_user) } + + context 'when correct params' do + shared_examples 'only removes cluster' do + it 'does not start cleanup' do + expect(cluster).not_to receive(:start_cleanup) + subject + end + + it 'destroys the cluster' do + subject + expect { cluster.reload }.to raise_error ActiveRecord::RecordNotFound + end + end + + context 'when params are empty' do + let(:params) { {} } + + it_behaves_like 'only removes cluster' + end + + context 'when cleanup param is false' do + let(:params) { { cleanup: 'false' } } + + it_behaves_like 'only removes cluster' + end + + context 'when cleanup param is true' do + let(:params) { { cleanup: 'true' } } + + before do + allow(Clusters::Cleanup::AppWorker).to receive(:perform_async) + end + + it 'does not destroy cluster' do + subject + expect(Clusters::Cluster.where(id: cluster.id).exists?).not_to be_falsey + end + + it 'transition cluster#cleanup_status from cleanup_not_started to uninstalling_applications' do + expect { subject }.to change { cluster.cleanup_status_name } + .from(:cleanup_not_started) + .to(:cleanup_uninstalling_applications) + end + end + end + end +end |