Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
path: root/app
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2021-05-05 18:10:05 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2021-05-05 18:10:05 +0300
commitcf05fd7f3956f0b1a17caf313e89bb7b3315d947 (patch)
tree8d847ad538180a03a6a25e7ee81605d2f86358d5 /app
parent023e050d82ed11d9060ce5bdaec99c3871b98164 (diff)
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app')
-rw-r--r--app/assets/javascripts/boards/components/board_card.vue3
-rw-r--r--app/assets/javascripts/lib/utils/recurrence.js154
-rw-r--r--app/assets/javascripts/pipeline_editor/components/drawer/pipeline_editor_drawer.vue76
-rw-r--r--app/assets/javascripts/pipeline_editor/pipeline_editor_home.vue10
-rw-r--r--app/controllers/projects/ci/pipeline_editor_controller.rb1
-rw-r--r--app/controllers/projects/pipelines_controller.rb2
-rw-r--r--app/graphql/types/metadata/kas_type.rb18
-rw-r--r--app/graphql/types/metadata_type.rb2
-rw-r--r--app/helpers/groups_helper.rb8
-rw-r--r--app/models/ci/stage.rb7
-rw-r--r--app/models/instance_metadata.rb3
-rw-r--r--app/models/instance_metadata/kas.rb15
-rw-r--r--app/presenters/ci/stage_presenter.rb32
-rw-r--r--app/views/layouts/nav/sidebar/_group.html.haml17
-rw-r--r--app/views/projects/stage/_stage.html.haml6
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)
&nbsp;
= 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 }
&nbsp;