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 /app | |
parent | d23b2a0871f3ca507aafa949e0314625f1f0c6a7 (diff) | |
download | gitlab-ce-61f0c58946ebac453b55a657cd4be1ac50a01e11.tar.gz |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app')
20 files changed, 249 insertions, 95 deletions
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 |