diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2020-05-22 00:08:07 +0000 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2020-05-22 00:08:07 +0000 |
commit | 21539fe9ab9a7a9604bb667b78b08854b4976f7b (patch) | |
tree | 82fb2be49aaaace5057d1e8e208ad12a422a7bbb /app | |
parent | e7bc93852d0ce48c490a780b6a1adc6cc36dd342 (diff) | |
download | gitlab-ce-21539fe9ab9a7a9604bb667b78b08854b4976f7b.tar.gz |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app')
13 files changed, 180 insertions, 84 deletions
diff --git a/app/assets/javascripts/frequent_items/index.js b/app/assets/javascripts/frequent_items/index.js index 6263acbab8e..c074f173776 100644 --- a/app/assets/javascripts/frequent_items/index.js +++ b/app/assets/javascripts/frequent_items/index.js @@ -1,8 +1,7 @@ import $ from 'jquery'; import Vue from 'vue'; import Translate from '~/vue_shared/translate'; -import eventHub from '~/frequent_items/event_hub'; -import frequentItems from './components/app.vue'; +import eventHub from './event_hub'; Vue.use(Translate); @@ -17,7 +16,7 @@ const frequentItemDropdowns = [ }, ]; -const initFrequentItemDropdowns = () => { +export default function initFrequentItemDropdowns() { frequentItemDropdowns.forEach(dropdown => { const { namespace, key } = dropdown; const el = document.getElementById(`js-${namespace}-dropdown`); @@ -29,45 +28,40 @@ const initFrequentItemDropdowns = () => { return; } - $(navEl).on('shown.bs.dropdown', () => { - eventHub.$emit(`${namespace}-dropdownOpen`); - }); + $(navEl).on('shown.bs.dropdown', () => + import('./components/app.vue').then(({ default: FrequentItems }) => { + // eslint-disable-next-line no-new + new Vue({ + el, + data() { + const { dataset } = this.$options.el; + const item = { + id: Number(dataset[`${key}Id`]), + name: dataset[`${key}Name`], + namespace: dataset[`${key}Namespace`], + webUrl: dataset[`${key}WebUrl`], + avatarUrl: dataset[`${key}AvatarUrl`] || null, + lastAccessedOn: Date.now(), + }; - // eslint-disable-next-line no-new - new Vue({ - el, - components: { - frequentItems, - }, - data() { - const { dataset } = this.$options.el; - const item = { - id: Number(dataset[`${key}Id`]), - name: dataset[`${key}Name`], - namespace: dataset[`${key}Namespace`], - webUrl: dataset[`${key}WebUrl`], - avatarUrl: dataset[`${key}AvatarUrl`] || null, - lastAccessedOn: Date.now(), - }; - - return { - currentUserName: dataset.userName, - currentItem: item, - }; - }, - render(createElement) { - return createElement('frequent-items', { - props: { - namespace, - currentUserName: this.currentUserName, - currentItem: this.currentItem, + return { + currentUserName: dataset.userName, + currentItem: item, + }; + }, + render(createElement) { + return createElement(FrequentItems, { + props: { + namespace, + currentUserName: this.currentUserName, + currentItem: this.currentItem, + }, + }); }, }); - }, - }); - }); -}; -document.addEventListener('DOMContentLoaded', () => { - requestIdleCallback(initFrequentItemDropdowns); -}); + eventHub.$emit(`${namespace}-dropdownOpen`); + }), + ); + }); +} diff --git a/app/assets/javascripts/main.js b/app/assets/javascripts/main.js index 713f57a2b27..dbe445b374d 100644 --- a/app/assets/javascripts/main.js +++ b/app/assets/javascripts/main.js @@ -28,7 +28,7 @@ import initLayoutNav from './layout_nav'; import './feature_highlight/feature_highlight_options'; import LazyLoader from './lazy_loader'; import initLogoAnimation from './logo'; -import './frequent_items'; +import initFrequentItemDropdowns from './frequent_items'; import initBreadcrumbs from './breadcrumb'; import initUsagePingConsent from './usage_ping_consent'; import initPerformanceBar from './performance_bar'; @@ -107,6 +107,7 @@ function deferredInitialisation() { initUsagePingConsent(); initUserPopovers(); initBroadcastNotifications(); + initFrequentItemDropdowns(); const recoverySettingsCallout = document.querySelector('.js-recovery-settings-callout'); PersistentUserCallout.factory(recoverySettingsCallout); diff --git a/app/assets/javascripts/performance_bar/components/detailed_metric.vue b/app/assets/javascripts/performance_bar/components/detailed_metric.vue index e1a0e2df0e0..ef24dbfb6ce 100644 --- a/app/assets/javascripts/performance_bar/components/detailed_metric.vue +++ b/app/assets/javascripts/performance_bar/components/detailed_metric.vue @@ -39,6 +39,11 @@ export default { metricDetails() { return this.currentRequest.details[this.metric]; }, + metricDetailsLabel() { + return this.metricDetails.duration + ? `${this.metricDetails.duration} / ${this.metricDetails.calls}` + : this.metricDetails.calls; + }, detailsList() { return this.metricDetails.details; }, @@ -68,7 +73,7 @@ export default { type="button" data-toggle="modal" > - {{ metricDetails.duration }} / {{ metricDetails.calls }} + {{ metricDetailsLabel }} </button> <gl-modal :id="`modal-peek-${metric}-details`" @@ -80,7 +85,9 @@ export default { <template v-if="detailsList.length"> <tr v-for="(item, index) in detailsList" :key="index"> <td> - <span>{{ sprintf(__('%{duration}ms'), { duration: item.duration }) }}</span> + <span v-if="item.duration">{{ + sprintf(__('%{duration}ms'), { duration: item.duration }) + }}</span> </td> <td> <div class="js-toggle-container"> diff --git a/app/assets/javascripts/performance_bar/components/performance_bar_app.vue b/app/assets/javascripts/performance_bar/components/performance_bar_app.vue index 1df5562e1b6..41147ccaea8 100644 --- a/app/assets/javascripts/performance_bar/components/performance_bar_app.vue +++ b/app/assets/javascripts/performance_bar/components/performance_bar_app.vue @@ -38,6 +38,11 @@ export default { keys: ['sql'], }, { + metric: 'bullet', + header: s__('PerformanceBar|Bullet notifications'), + keys: ['notification'], + }, + { metric: 'gitaly', header: s__('PerformanceBar|Gitaly calls'), keys: ['feature', 'request'], diff --git a/app/assets/javascripts/releases/components/release_block.vue b/app/assets/javascripts/releases/components/release_block.vue index 58045b57d80..adb0e69b786 100644 --- a/app/assets/javascripts/releases/components/release_block.vue +++ b/app/assets/javascripts/releases/components/release_block.vue @@ -109,7 +109,7 @@ export default { <evidence-block v-if="hasEvidence && shouldShowEvidence" :release="release" /> <div ref="gfm-content" class="card-text prepend-top-default"> - <div v-html="release.descriptionHtml"></div> + <div class="md" v-html="release.descriptionHtml"></div> </div> </div> diff --git a/app/graphql/mutations/concerns/mutations/resolves_issuable.rb b/app/graphql/mutations/concerns/mutations/resolves_issuable.rb index d63cc27a450..f3ed3565b03 100644 --- a/app/graphql/mutations/concerns/mutations/resolves_issuable.rb +++ b/app/graphql/mutations/concerns/mutations/resolves_issuable.rb @@ -7,8 +7,15 @@ module Mutations def resolve_issuable(type:, parent_path:, iid:) parent = resolve_issuable_parent(type, parent_path) + key = type == :merge_request ? :iids : :iid + args = { key => iid.to_s } - issuable_resolver(type, parent, context).resolve(iid: iid.to_s) + resolver = issuable_resolver(type, parent, context) + ready, early_return = resolver.ready?(**args) + + return early_return unless ready + + resolver.resolve(**args) end private diff --git a/app/graphql/resolvers/concerns/resolves_merge_requests.rb b/app/graphql/resolvers/concerns/resolves_merge_requests.rb new file mode 100644 index 00000000000..779ff0b50d4 --- /dev/null +++ b/app/graphql/resolvers/concerns/resolves_merge_requests.rb @@ -0,0 +1,50 @@ +# frozen_string_literal: true + +# Mixin for resolving merge requests. All arguments must be in forms +# that `MergeRequestsFinder` can handle, so you may need to use aliasing. +module ResolvesMergeRequests + extend ActiveSupport::Concern + + included do + type Types::MergeRequestType, null: true + end + + def resolve(**args) + args[:iids] = Array.wrap(args[:iids]) if args[:iids] + args.compact! + + if args.keys == [:iids] + batch_load_merge_requests(args[:iids]) + else + args[:project_id] = project.id + + MergeRequestsFinder.new(current_user, args).execute + end.then(&(single? ? :first : :itself)) + end + + def ready?(**args) + return early_return if no_results_possible?(args) + + super + end + + def early_return + [false, single? ? nil : MergeRequest.none] + end + + private + + def batch_load_merge_requests(iids) + iids.map { |iid| batch_load(iid) }.select(&:itself) # .compact doesn't work on BatchLoader + end + + # rubocop: disable CodeReuse/ActiveRecord + def batch_load(iid) + BatchLoader::GraphQL.for(iid.to_s).batch(key: project) do |iids, loader, args| + args[:key].merge_requests.where(iid: iids).each do |mr| + loader.call(mr.iid.to_s, mr) + end + end + end + # rubocop: enable CodeReuse/ActiveRecord +end diff --git a/app/graphql/resolvers/merge_request_resolver.rb b/app/graphql/resolvers/merge_request_resolver.rb new file mode 100644 index 00000000000..a47a128ea32 --- /dev/null +++ b/app/graphql/resolvers/merge_request_resolver.rb @@ -0,0 +1,18 @@ +# frozen_string_literal: true + +module Resolvers + class MergeRequestResolver < BaseResolver.single + include ResolvesMergeRequests + + alias_method :project, :synchronized_object + + argument :iid, GraphQL::STRING_TYPE, + required: true, + as: :iids, + description: 'IID of the merge request, for example `1`' + + def no_results_possible?(args) + project.nil? + end + end +end diff --git a/app/graphql/resolvers/merge_requests_resolver.rb b/app/graphql/resolvers/merge_requests_resolver.rb index 25121dce005..44fc4e17cd4 100644 --- a/app/graphql/resolvers/merge_requests_resolver.rb +++ b/app/graphql/resolvers/merge_requests_resolver.rb @@ -2,47 +2,39 @@ module Resolvers class MergeRequestsResolver < BaseResolver - argument :iid, GraphQL::STRING_TYPE, - required: false, - description: 'IID of the merge request, for example `1`' + include ResolvesMergeRequests + + alias_method :project, :synchronized_object argument :iids, [GraphQL::STRING_TYPE], required: false, description: 'Array of IIDs of merge requests, for example `[1, 2]`' - type Types::MergeRequestType, null: true - - alias_method :project, :object + argument :source_branches, [GraphQL::STRING_TYPE], + required: false, + as: :source_branch, + description: 'Array of source branch names. All resolved merge requests will have one of these branches as their source.' - def resolve(**args) - project = object.respond_to?(:sync) ? object.sync : object - return MergeRequest.none if project.nil? + argument :target_branches, [GraphQL::STRING_TYPE], + required: false, + as: :target_branch, + description: 'Array of target branch names. All resolved merge requests will have one of these branches as their target.' - args[:iids] ||= [args[:iid]].compact + argument :state, ::Types::MergeRequestStateEnum, + required: false, + description: 'A merge request state. If provided, all resolved merge requests will have this state.' - if args[:iids].any? - batch_load_merge_requests(args[:iids]) - else - args[:project_id] = project.id + argument :labels, [GraphQL::STRING_TYPE], + required: false, + as: :label_name, + description: 'Array of label names. All resolved merge requests will have all of these labels.' - MergeRequestsFinder.new(context[:current_user], args).execute - end + def self.single + ::Resolvers::MergeRequestResolver end - def batch_load_merge_requests(iids) - iids.map { |iid| batch_load(iid) }.select(&:itself) # .compact doesn't work on BatchLoader - end - - # rubocop: disable CodeReuse/ActiveRecord - def batch_load(iid) - BatchLoader::GraphQL.for(iid.to_s).batch(key: project) do |iids, loader, args| - arg_key = args[:key].respond_to?(:sync) ? args[:key].sync : args[:key] - - arg_key.merge_requests.where(iid: iids).each do |mr| - loader.call(mr.iid.to_s, mr) - end - end + def no_results_possible?(args) + project.nil? || args.values.any? { |v| v.is_a?(Array) && v.empty? } end - # rubocop: enable CodeReuse/ActiveRecord end end diff --git a/app/graphql/types/merge_request_type.rb b/app/graphql/types/merge_request_type.rb index cd4c6b4d46a..6ac385a8e31 100644 --- a/app/graphql/types/merge_request_type.rb +++ b/app/graphql/types/merge_request_type.rb @@ -81,8 +81,14 @@ module Types description: 'Default merge commit message of the merge request' field :merge_ongoing, GraphQL::BOOLEAN_TYPE, method: :merge_ongoing?, null: false, description: 'Indicates if a merge is currently occurring' - field :source_branch_exists, GraphQL::BOOLEAN_TYPE, method: :source_branch_exists?, null: false, + field :source_branch_exists, GraphQL::BOOLEAN_TYPE, + null: false, calls_gitaly: true, + method: :source_branch_exists?, description: 'Indicates if the source branch of the merge request exists' + field :target_branch_exists, GraphQL::BOOLEAN_TYPE, + null: false, calls_gitaly: true, + method: :target_branch_exists?, + description: 'Indicates if the target branch of the merge request exists' field :mergeable_discussions_state, GraphQL::BOOLEAN_TYPE, null: true, description: 'Indicates if all discussions in the merge request have been resolved, allowing the merge request to be merged' field :web_url, GraphQL::STRING_TYPE, null: true, diff --git a/app/graphql/types/permission_types/merge_request.rb b/app/graphql/types/permission_types/merge_request.rb index d877fc177d2..28b7ebd2af6 100644 --- a/app/graphql/types/permission_types/merge_request.rb +++ b/app/graphql/types/permission_types/merge_request.rb @@ -3,6 +3,11 @@ module Types module PermissionTypes class MergeRequest < BasePermissionType + PERMISSION_FIELDS = %i[push_to_source_branch + remove_source_branch + cherry_pick_on_current_merge_request + revert_on_current_merge_request].freeze + present_using MergeRequestPresenter description 'Check permissions for the current user on a merge request' graphql_name 'MergeRequestPermissions' @@ -10,10 +15,9 @@ module Types abilities :read_merge_request, :admin_merge_request, :update_merge_request, :create_note - permission_field :push_to_source_branch, method: :can_push_to_source_branch?, calls_gitaly: true - permission_field :remove_source_branch, method: :can_remove_source_branch?, calls_gitaly: true - permission_field :cherry_pick_on_current_merge_request, method: :can_cherry_pick_on_current_merge_request? - permission_field :revert_on_current_merge_request, method: :can_revert_on_current_merge_request? + PERMISSION_FIELDS.each do |field_name| + permission_field field_name, method: :"can_#{field_name}?", calls_gitaly: true + end end end end diff --git a/app/services/releases/create_service.rb b/app/services/releases/create_service.rb index 81ca9d6d123..2a4cbe2dc4b 100644 --- a/app/services/releases/create_service.rb +++ b/app/services/releases/create_service.rb @@ -49,6 +49,8 @@ module Releases notify_create_release(release) + create_evidence!(release) + success(tag: tag, release: release) rescue => e error(e.message, 400) @@ -70,5 +72,15 @@ module Releases milestones: milestones ) end + + def create_evidence!(release) + return if release.historical_release? + + if release.upcoming_release? + CreateEvidenceWorker.perform_at(release.released_at, release.id) + else + CreateEvidenceWorker.perform_async(release.id) + end + end end end diff --git a/app/views/shared/_promo.html.haml b/app/views/shared/_promo.html.haml index 0f31b60d8d3..855f6b9c1f4 100644 --- a/app/views/shared/_promo.html.haml +++ b/app/views/shared/_promo.html.haml @@ -1,5 +1,5 @@ .gitlab-promo - = link_to 'Homepage', promo_url - = link_to 'Blog', promo_url + '/blog/' + = link_to _('Homepage'), promo_url + = link_to _('Blog'), promo_url + '/blog/' = link_to '@gitlab', 'https://twitter.com/gitlab' - = link_to 'Requests', 'https://gitlab.com/gitlab-org/gitlab-foss/blob/master/CONTRIBUTING.md#feature-proposals' + = link_to _('Requests'), 'https://gitlab.com/gitlab-org/gitlab-foss/blob/master/CONTRIBUTING.md#feature-proposals' |