diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2021-05-05 18:10:05 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2021-05-05 18:10:05 +0300 |
commit | cf05fd7f3956f0b1a17caf313e89bb7b3315d947 (patch) | |
tree | 8d847ad538180a03a6a25e7ee81605d2f86358d5 /app | |
parent | 023e050d82ed11d9060ce5bdaec99c3871b98164 (diff) |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app')
-rw-r--r-- | app/assets/javascripts/boards/components/board_card.vue | 3 | ||||
-rw-r--r-- | app/assets/javascripts/lib/utils/recurrence.js | 154 | ||||
-rw-r--r-- | app/assets/javascripts/pipeline_editor/components/drawer/pipeline_editor_drawer.vue | 76 | ||||
-rw-r--r-- | app/assets/javascripts/pipeline_editor/pipeline_editor_home.vue | 10 | ||||
-rw-r--r-- | app/controllers/projects/ci/pipeline_editor_controller.rb | 1 | ||||
-rw-r--r-- | app/controllers/projects/pipelines_controller.rb | 2 | ||||
-rw-r--r-- | app/graphql/types/metadata/kas_type.rb | 18 | ||||
-rw-r--r-- | app/graphql/types/metadata_type.rb | 2 | ||||
-rw-r--r-- | app/helpers/groups_helper.rb | 8 | ||||
-rw-r--r-- | app/models/ci/stage.rb | 7 | ||||
-rw-r--r-- | app/models/instance_metadata.rb | 3 | ||||
-rw-r--r-- | app/models/instance_metadata/kas.rb | 15 | ||||
-rw-r--r-- | app/presenters/ci/stage_presenter.rb | 32 | ||||
-rw-r--r-- | app/views/layouts/nav/sidebar/_group.html.haml | 17 | ||||
-rw-r--r-- | app/views/projects/stage/_stage.html.haml | 6 |
15 files changed, 333 insertions, 21 deletions
diff --git a/app/assets/javascripts/boards/components/board_card.vue b/app/assets/javascripts/boards/components/board_card.vue index 3e9c663a036..2821b799cef 100644 --- a/app/assets/javascripts/boards/components/board_card.vue +++ b/app/assets/javascripts/boards/components/board_card.vue @@ -1,5 +1,5 @@ <script> -import { mapActions, mapGetters, mapState } from 'vuex'; +import { mapActions, mapState } from 'vuex'; import BoardCardInner from './board_card_inner.vue'; export default { @@ -31,7 +31,6 @@ export default { }, computed: { ...mapState(['selectedBoardItems', 'activeId']), - ...mapGetters(['isSwimlanesOn']), isActive() { return this.item.id === this.activeId; }, diff --git a/app/assets/javascripts/lib/utils/recurrence.js b/app/assets/javascripts/lib/utils/recurrence.js new file mode 100644 index 00000000000..b9afb939090 --- /dev/null +++ b/app/assets/javascripts/lib/utils/recurrence.js @@ -0,0 +1,154 @@ +import { uuids } from '../../diffs/utils/uuids'; + +/** + * @module recurrence + */ + +const instances = {}; + +/** + * Create a new unique {@link module:recurrence~RecurInstance|RecurInstance} + * @returns {module:recurrence.RecurInstance} The newly created {@link module:recurrence~RecurInstance|RecurInstance} + */ +export function create() { + const id = uuids()[0]; + let handlers = {}; + let count = 0; + + /** + * @namespace RecurInstance + * @description A RecurInstance tracks the count of any occurrence as registered by calls to <code>occur</code>. + * <br /><br /> + * It maintains an internal counter and a registry of handlers that can be arbitrarily assigned by a user. + * <br /><br /> + * While a RecurInstance isn't specific to any particular use-case, it may be useful for: + * <br /> + * <ul> + * <li>Tracking repeated errors across multiple - but not linked - network requests</li> + * <li>Tracking repeated user interactions (e.g. multiple clicks)</li> + * </ul> + * @summary A closure to track repeated occurrences of any arbitrary event. + * */ + const instance = { + /** + * @type {module:uuids~UUIDv4} + * @description A randomly generated {@link module:uuids~UUIDv4|UUID} for this particular recurrence instance + * @memberof module:recurrence~RecurInstance + * @readonly + * @inner + */ + get id() { + return id; + }, + /** + * @type {Number} + * @description The number of times this particular instance of recurrence has been triggered + * @memberof module:recurrence~RecurInstance + * @readonly + * @inner + */ + get count() { + return count; + }, + /** + * @type {Object} + * @description The handlers assigned to this recurrence tracker + * @example + * myRecurrence.handle( 4, () => console.log( "four" ) ); + * console.log( myRecurrence.handlers ); // {"4": () => console.log( "four" )} + * @memberof module:recurrence~RecurInstance + * @readonly + * @inner + */ + get handlers() { + return handlers; + }, + /** + * @type {Boolean} + * @description Delete any internal reference to the instance. + * <br /> + * Keep in mind that this will only attempt to remove the <strong>internal</strong> reference. + * <br /> + * If your code maintains a reference to the instance, the regular garbage collector will not free the memory. + * @memberof module:recurrence~RecurInstance + * @inner + */ + free() { + return delete instances[id]; + }, + /** + * @description Register a handler to be called when this occurrence is seen <code>onCount</code> number of times. + * @param {Number} onCount - The number of times the occurrence has been seen to respond to + * @param {Function} behavior - A callback function to run when the occurrence has been seen <code>onCount</code> times + * @memberof module:recurrence~RecurInstance + * @inner + */ + handle(onCount, behavior) { + if (onCount && behavior) { + handlers[onCount] = behavior; + } + }, + /** + * @description Remove the behavior callback handler that would be run when the occurrence is seen <code>onCount</code> times + * @param {Number} onCount - The count identifier for which to eject the callback handler + * @memberof module:recurrence~RecurInstance + * @inner + */ + eject(onCount) { + if (onCount) { + delete handlers[onCount]; + } + }, + /** + * @description Register that this occurrence has been seen and trigger any appropriate handlers + * @memberof module:recurrence~RecurInstance + * @inner + */ + occur() { + count += 1; + + if (typeof handlers[count] === 'function') { + handlers[count](count); + } + }, + /** + * @description Reset this recurrence instance without destroying it entirely + * @param {Object} [options] + * @param {Boolean} [options.currentCount = true] - Whether to reset the count + * @param {Boolean} [options.handlersList = false] - Whether to reset the list of attached handlers back to an empty state + * @memberof module:recurrence~RecurInstance + * @inner + */ + reset({ currentCount = true, handlersList = false } = {}) { + if (currentCount) { + count = 0; + } + + if (handlersList) { + handlers = {}; + } + }, + }; + + instances[id] = instance; + + return instance; +} + +/** + * Retrieve a stored {@link module:recurrence~RecurInstance|RecurInstance} by {@link module:uuids~UUIDv4|UUID} + * @param {module:uuids~UUIDv4} id - The {@link module:uuids~UUIDv4|UUID} of a previously created {@link module:recurrence~RecurInstance|RecurInstance} + * @returns {(module:recurrence~RecurInstance|undefined)} The {@link module:recurrence~RecurInstance|RecurInstance}, or undefined if the UUID doesn't refer to a known Instance + */ +export function recall(id) { + return instances[id]; +} + +/** + * Release the memory space for a given {@link module:recurrence~RecurInstance|RecurInstance} by {@link module:uuids~UUIDv4|UUID} + * @param {module:uuids~UUIDv4} id - The {@link module:uuids~UUIDv4|UUID} of a previously created {@link module:recurrence~RecurInstance|RecurInstance} + * @returns {Boolean} Whether the reference to the stored {@link module:recurrence~RecurInstance|RecurInstance} was released + */ +export function free(id) { + return recall(id)?.free() || false; +} diff --git a/app/assets/javascripts/pipeline_editor/components/drawer/pipeline_editor_drawer.vue b/app/assets/javascripts/pipeline_editor/components/drawer/pipeline_editor_drawer.vue new file mode 100644 index 00000000000..ef5be8abf9a --- /dev/null +++ b/app/assets/javascripts/pipeline_editor/components/drawer/pipeline_editor_drawer.vue @@ -0,0 +1,76 @@ +<script> +import { GlButton, GlIcon } from '@gitlab/ui'; +import { __ } from '~/locale'; + +export default { + width: { + expanded: '482px', + collapsed: '58px', + }, + i18n: { + toggleTxt: __('Collapse'), + }, + components: { + GlButton, + GlIcon, + }, + data() { + return { + isExpanded: false, + topPosition: 0, + }; + }, + computed: { + buttonIconName() { + return this.isExpanded ? 'chevron-double-lg-right' : 'chevron-double-lg-left'; + }, + buttonClass() { + return this.isExpanded ? 'gl-justify-content-end!' : ''; + }, + rootStyle() { + const { expanded, collapsed } = this.$options.width; + const top = this.topPosition; + const style = { top: `${top}px` }; + + return this.isExpanded ? { ...style, width: expanded } : { ...style, width: collapsed }; + }, + }, + mounted() { + this.setTopPosition(); + }, + methods: { + setTopPosition() { + const navbarEl = document.querySelector('.js-navbar'); + + if (navbarEl) { + this.topPosition = navbarEl.getBoundingClientRect().bottom; + } + }, + toggleDrawer() { + this.isExpanded = !this.isExpanded; + }, + }, +}; +</script> +<template> + <aside + aria-live="polite" + class="gl-fixed gl-right-0 gl-h-full gl-bg-gray-10 gl-transition-medium gl-border-l-solid gl-border-1 gl-border-gray-100" + :style="rootStyle" + > + <gl-button + category="tertiary" + class="gl-w-full gl-h-9 gl-rounded-0! gl-border-none! gl-border-b-solid! gl-border-1! gl-border-gray-100 gl-text-decoration-none! gl-outline-0! gl-display-flex" + :class="buttonClass" + :title="__('Toggle sidebar')" + data-testid="toggleBtn" + @click="toggleDrawer" + > + <span v-if="isExpanded" class="gl-text-gray-500 gl-mr-3" data-testid="collapse-text">{{ + __('Collapse') + }}</span> + <gl-icon data-testid="toggle-icon" :name="buttonIconName" /> + </gl-button> + <div v-if="isExpanded" class="gl-p-5" data-testid="drawer-content"></div> + </aside> +</template> diff --git a/app/assets/javascripts/pipeline_editor/pipeline_editor_home.vue b/app/assets/javascripts/pipeline_editor/pipeline_editor_home.vue index adba55f9f4b..dfe9c82b912 100644 --- a/app/assets/javascripts/pipeline_editor/pipeline_editor_home.vue +++ b/app/assets/javascripts/pipeline_editor/pipeline_editor_home.vue @@ -1,5 +1,7 @@ <script> +import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; import CommitSection from './components/commit/commit_section.vue'; +import PipelineEditorDrawer from './components/drawer/pipeline_editor_drawer.vue'; import PipelineEditorFileNav from './components/file_nav/pipeline_editor_file_nav.vue'; import PipelineEditorHeader from './components/header/pipeline_editor_header.vue'; import PipelineEditorTabs from './components/pipeline_editor_tabs.vue'; @@ -8,10 +10,12 @@ import { TABS_WITH_COMMIT_FORM, CREATE_TAB } from './constants'; export default { components: { CommitSection, + PipelineEditorDrawer, PipelineEditorFileNav, PipelineEditorHeader, PipelineEditorTabs, }, + mixins: [glFeatureFlagMixin()], props: { ciConfigData: { type: Object, @@ -35,6 +39,9 @@ export default { showCommitForm() { return TABS_WITH_COMMIT_FORM.includes(this.currentTab); }, + showPipelineDrawer() { + return this.glFeatures.pipelineEditorDrawer; + }, }, methods: { setCurrentTab(tabName) { @@ -45,7 +52,7 @@ export default { </script> <template> - <div> + <div class="gl-pr-9 gl-transition-medium gl-w-full"> <pipeline-editor-file-nav v-on="$listeners" /> <pipeline-editor-header :ci-config-data="ciConfigData" @@ -58,5 +65,6 @@ export default { @set-current-tab="setCurrentTab" /> <commit-section v-if="showCommitForm" :ci-file-content="ciFileContent" v-on="$listeners" /> + <pipeline-editor-drawer v-if="showPipelineDrawer" /> </div> </template> diff --git a/app/controllers/projects/ci/pipeline_editor_controller.rb b/app/controllers/projects/ci/pipeline_editor_controller.rb index 4136a10e124..13c22356b60 100644 --- a/app/controllers/projects/ci/pipeline_editor_controller.rb +++ b/app/controllers/projects/ci/pipeline_editor_controller.rb @@ -6,6 +6,7 @@ class Projects::Ci::PipelineEditorController < Projects::ApplicationController push_frontend_feature_flag(:ci_config_merged_tab, @project, default_enabled: :yaml) push_frontend_feature_flag(:pipeline_editor_empty_state_action, @project, default_enabled: :yaml) push_frontend_feature_flag(:pipeline_editor_branch_switcher, @project, default_enabled: :yaml) + push_frontend_feature_flag(:pipeline_editor_drawer, @project, default_enabled: :yaml) end feature_category :pipeline_authoring diff --git a/app/controllers/projects/pipelines_controller.rb b/app/controllers/projects/pipelines_controller.rb index 62b464fe955..82ff7d77a6a 100644 --- a/app/controllers/projects/pipelines_controller.rb +++ b/app/controllers/projects/pipelines_controller.rb @@ -227,7 +227,7 @@ class Projects::PipelinesController < Projects::ApplicationController end def render_show - @stages = @pipeline.stages.with_latest_and_retried_statuses + @stages = @pipeline.stages respond_to do |format| format.html do diff --git a/app/graphql/types/metadata/kas_type.rb b/app/graphql/types/metadata/kas_type.rb new file mode 100644 index 00000000000..8af4c23270b --- /dev/null +++ b/app/graphql/types/metadata/kas_type.rb @@ -0,0 +1,18 @@ +# frozen_string_literal: true + +module Types + module Metadata + class KasType < ::Types::BaseObject + graphql_name 'Kas' + + authorize :read_instance_metadata + + field :enabled, GraphQL::BOOLEAN_TYPE, null: false, + description: 'Indicates whether the Kubernetes Agent Server is enabled.' + field :version, GraphQL::STRING_TYPE, null: true, + description: 'KAS version.' + field :external_url, GraphQL::STRING_TYPE, null: true, + description: 'The URL used by the Agents to communicate with KAS.' + end + end +end diff --git a/app/graphql/types/metadata_type.rb b/app/graphql/types/metadata_type.rb index 0c360d4f292..851c2a3f1e3 100644 --- a/app/graphql/types/metadata_type.rb +++ b/app/graphql/types/metadata_type.rb @@ -10,5 +10,7 @@ module Types description: 'Version.' field :revision, GraphQL::STRING_TYPE, null: false, description: 'Revision.' + field :kas, ::Types::Metadata::KasType, null: false, + description: 'Metadata about KAS.' end end diff --git a/app/helpers/groups_helper.rb b/app/helpers/groups_helper.rb index 79d89c55f28..a78cd752223 100644 --- a/app/helpers/groups_helper.rb +++ b/app/helpers/groups_helper.rb @@ -38,6 +38,14 @@ module GroupsHelper ] end + def group_information_title(group) + if Feature.enabled?(:sidebar_refactor, current_user) + group.subgroup? ? _('Subgroup information') : _('Group information') + else + group.subgroup? ? _('Subgroup overview') : _('Group overview') + end + end + def group_container_registry_nav? Gitlab.config.registry.enabled && can?(current_user, :read_container_image, @group) diff --git a/app/models/ci/stage.rb b/app/models/ci/stage.rb index 7c5324a2181..ef920b2d589 100644 --- a/app/models/ci/stage.rb +++ b/app/models/ci/stage.rb @@ -6,6 +6,7 @@ module Ci include Importable include Ci::HasStatus include Gitlab::OptimisticLocking + include Presentable enum status: Ci::HasStatus::STATUSES_ENUM @@ -22,12 +23,6 @@ module Ci scope :ordered, -> { order(position: :asc) } scope :in_pipelines, ->(pipelines) { where(pipeline: pipelines) } scope :by_name, ->(names) { where(name: names) } - scope :with_latest_and_retried_statuses, -> do - includes( - latest_statuses: [:pipeline, project: :namespace], - retried_statuses: [:pipeline, project: :namespace] - ) - end with_options unless: :importing? do validates :project, presence: true diff --git a/app/models/instance_metadata.rb b/app/models/instance_metadata.rb index 96622d0b1b3..6cac78178e0 100644 --- a/app/models/instance_metadata.rb +++ b/app/models/instance_metadata.rb @@ -1,10 +1,11 @@ # frozen_string_literal: true class InstanceMetadata - attr_reader :version, :revision + attr_reader :version, :revision, :kas def initialize(version: Gitlab::VERSION, revision: Gitlab.revision) @version = version @revision = revision + @kas = ::InstanceMetadata::Kas.new end end diff --git a/app/models/instance_metadata/kas.rb b/app/models/instance_metadata/kas.rb new file mode 100644 index 00000000000..7d2d71120b5 --- /dev/null +++ b/app/models/instance_metadata/kas.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +class InstanceMetadata::Kas + attr_reader :enabled, :version, :external_url + + def initialize + @enabled = Gitlab::Kas.enabled? + @version = Gitlab::Kas.version if @enabled + @external_url = Gitlab::Kas.external_url if @enabled + end + + def self.declarative_policy_class + "InstanceMetadataPolicy" + end +end diff --git a/app/presenters/ci/stage_presenter.rb b/app/presenters/ci/stage_presenter.rb new file mode 100644 index 00000000000..9ec3f8d153a --- /dev/null +++ b/app/presenters/ci/stage_presenter.rb @@ -0,0 +1,32 @@ +# frozen_string_literal: true + +module Ci + class StagePresenter < Gitlab::View::Presenter::Delegated + presents :stage + + def latest_ordered_statuses + preload_statuses(stage.statuses.latest_ordered) + end + + def retried_ordered_statuses + preload_statuses(stage.statuses.retried_ordered) + end + + private + + def preload_statuses(statuses) + loaded_statuses = statuses.load + statuses.tap do |statuses| + # rubocop: disable CodeReuse/ActiveRecord + ActiveRecord::Associations::Preloader.new.preload(preloadable_statuses(loaded_statuses), %w[pipeline tags job_artifacts_archive metadata]) + # rubocop: enable CodeReuse/ActiveRecord + end + end + + def preloadable_statuses(statuses) + statuses.reject do |status| + status.instance_of?(::GenericCommitStatus) || status.instance_of?(::Ci::Bridge) + end + end + end +end diff --git a/app/views/layouts/nav/sidebar/_group.html.haml b/app/views/layouts/nav/sidebar/_group.html.haml index a89ba072c0e..286d12b9ac8 100644 --- a/app/views/layouts/nav/sidebar/_group.html.haml +++ b/app/views/layouts/nav/sidebar/_group.html.haml @@ -1,7 +1,6 @@ - issues_count = cached_issuables_count(@group, type: :issues) - merge_requests_count = group_open_merge_requests_count(@group) - aside_title = @group.subgroup? ? _('Subgroup navigation') : _('Group navigation') -- overview_title = @group.subgroup? ? _('Subgroup overview') : _('Group overview') %aside.nav-sidebar{ class: ("sidebar-collapsed-desktop" if collapsed_sidebar?), **sidebar_tracking_attributes_by_object(@group), 'aria-label': aside_title } .nav-sidebar-inner-scroll @@ -19,21 +18,23 @@ = nav_link(path: paths, unless: -> { current_path?('groups/contribution_analytics#show') }, html_options: { class: 'home' }) do = link_to group_path(@group) do .nav-icon-container - = sprite_icon('home') + - sprite = Feature.enabled?(:sidebar_refactor, current_user) ? 'group' : 'home' + = sprite_icon(sprite) %span.nav-item-name - = overview_title + = group_information_title(@group) %ul.sidebar-sub-level-items = nav_link(path: ['groups#show', 'groups#details', 'groups#activity', 'groups#subgroups'], html_options: { class: "fly-out-top-item" } ) do = link_to group_path(@group) do %strong.fly-out-top-item-name - = overview_title + = group_information_title(@group) %li.divider.fly-out-top-item - = nav_link(path: ['groups#show', 'groups#details', 'groups#subgroups'], html_options: { class: 'home' }) do - = link_to details_group_path(@group), title: _('Group details') do - %span - = _('Details') + - if Feature.disabled?(:sidebar_refactor, current_user) + = nav_link(path: ['groups#show', 'groups#details', 'groups#subgroups'], html_options: { class: 'home' }) do + = link_to details_group_path(@group), title: _('Group details') do + %span + = _('Details') - if group_sidebar_link?(:activity) = nav_link(path: 'groups#activity') do diff --git a/app/views/projects/stage/_stage.html.haml b/app/views/projects/stage/_stage.html.haml index 92bfd5a48a8..387c8fb3234 100644 --- a/app/views/projects/stage/_stage.html.haml +++ b/app/views/projects/stage/_stage.html.haml @@ -1,3 +1,5 @@ +- stage = stage.present(current_user: current_user) + %tr %th{ colspan: 10 } %strong @@ -6,8 +8,8 @@ = ci_icon_for_status(stage.status) = stage.name.titleize -= render stage.latest_statuses, stage: false, ref: false, pipeline_link: false, allow_retry: true -= render stage.retried_statuses, stage: false, ref: false, pipeline_link: false, retried: true += render stage.latest_ordered_statuses, stage: false, ref: false, pipeline_link: false, allow_retry: true += render stage.retried_ordered_statuses, stage: false, ref: false, pipeline_link: false, retried: true %tr %td{ colspan: 10 } |