diff options
Diffstat (limited to 'app')
13 files changed, 179 insertions, 55 deletions
diff --git a/app/assets/javascripts/ci/pipeline_editor/components/editor/ci_editor_header.vue b/app/assets/javascripts/ci/pipeline_editor/components/editor/ci_editor_header.vue index 66957365261..eabf4749e9c 100644 --- a/app/assets/javascripts/ci/pipeline_editor/components/editor/ci_editor_header.vue +++ b/app/assets/javascripts/ci/pipeline_editor/components/editor/ci_editor_header.vue @@ -17,6 +17,7 @@ export default { GlButton, }, mixins: [glFeatureFlagMixin(), Tracking.mixin()], + inject: ['aiChatAvailable'], props: { showDrawer: { type: Boolean, @@ -31,6 +32,11 @@ export default { required: true, }, }, + computed: { + isAiConfigChatAvailable() { + return this.glFeatures.aiCiConfigGenerator && this.aiChatAvailable; + }, + }, methods: { toggleDrawer() { if (this.showDrawer) { @@ -96,7 +102,7 @@ export default { {{ $options.i18n.jobAssistant }} </gl-button> <gl-button - v-if="glFeatures.aiCiConfigGenerator" + v-if="isAiConfigChatAvailable" icon="bulb" size="small" data-testid="ai-assistant-drawer-toggle" diff --git a/app/assets/javascripts/ci/pipeline_editor/index.js b/app/assets/javascripts/ci/pipeline_editor/index.js index 09acd805410..b8d6c27435d 100644 --- a/app/assets/javascripts/ci/pipeline_editor/index.js +++ b/app/assets/javascripts/ci/pipeline_editor/index.js @@ -46,6 +46,7 @@ export const initPipelineEditor = (selector = '#js-pipeline-editor') => { usesExternalConfig, validateTabIllustrationPath, ymlHelpPagePath, + aiChatAvailable, } = el.dataset; const configurationPaths = Object.fromEntries( @@ -115,6 +116,7 @@ export const initPipelineEditor = (selector = '#js-pipeline-editor') => { el, apolloProvider, provide: { + aiChatAvailable: parseBoolean(aiChatAvailable), ciConfigPath, ciExamplesHelpPagePath, ciHelpPagePath, diff --git a/app/assets/javascripts/design_management/components/list/item.vue b/app/assets/javascripts/design_management/components/list/item.vue index f52486f0629..9c1bcf5bf90 100644 --- a/app/assets/javascripts/design_management/components/list/item.vue +++ b/app/assets/javascripts/design_management/components/list/item.vue @@ -133,7 +133,11 @@ export default { <div class="card-body gl-p-0 gl-display-flex gl-align-items-center gl-justify-content-center gl-overflow-hidden gl-relative" > - <div v-if="icon.name" data-testid="design-event" class="gl-top-5 gl-right-5 gl-absolute"> + <div + v-if="icon.name" + data-testid="design-event" + class="gl-absolute gl-top-3 gl-right-3 gl-mr-1" + > <span :title="icon.tooltip" :aria-label="icon.tooltip"> <gl-icon :name="icon.name" @@ -165,11 +169,11 @@ export default { /> </gl-intersection-observer> </div> - <div class="card-footer gl-display-flex gl-w-full"> + <div class="card-footer gl-display-flex gl-w-full gl-bg-white gl-py-3 gl-px-4"> <div class="gl-display-flex gl-flex-direction-column str-truncated-100"> <span v-gl-tooltip - class="gl-font-weight-bold str-truncated-100" + class="gl-font-weight-semibold str-truncated-100" data-qa-selector="design_file_name" :data-testid="`design-img-filename-${id}`" :title="filename" diff --git a/app/assets/javascripts/design_management/pages/index.vue b/app/assets/javascripts/design_management/pages/index.vue index e270613e4eb..dcc65c957fe 100644 --- a/app/assets/javascripts/design_management/pages/index.vue +++ b/app/assets/javascripts/design_management/pages/index.vue @@ -141,6 +141,12 @@ export default { } return 'col-12'; }, + designContentWrapperClass() { + if (this.hasDesigns) { + return 'gl-bg-gray-10 gl-border gl-border-t-0 gl-rounded-bottom-left-base gl-rounded-bottom-right-base gl-px-5'; + } + return null; + }, }, mounted() { if (this.$route.path === '/designs') { @@ -364,7 +370,7 @@ export default { </gl-alert> <header v-if="showToolbar" - class="gl-display-flex gl-my-0 gl-text-gray-900" + class="gl-border gl-px-5 gl-py-4 gl-display-flex gl-justify-content-space-between gl-bg-white gl-rounded-base gl-rounded-bottom-left-none! gl-rounded-bottom-right-none!" data-testid="design-toolbar-wrapper" > <div @@ -418,7 +424,7 @@ export default { </div> </div> </header> - <div> + <div :class="designContentWrapperClass"> <gl-loading-icon v-if="isLoading" size="lg" /> <gl-alert v-else-if="error" variant="danger" :dismissible="false"> {{ __('An error occurred while loading designs. Please try again.') }} @@ -483,7 +489,7 @@ export default { v-if="canSelectDesign(design.filename)" :checked="isDesignSelected(design.filename)" type="checkbox" - class="design-checkbox" + class="design-checkbox gl-absolute gl-top-4 gl-left-6 gl-ml-2" data-qa-selector="design_checkbox" :data-qa-design="design.filename" @change="changeSelectedDesigns(design.filename)" diff --git a/app/assets/javascripts/super_sidebar/components/menu_section.vue b/app/assets/javascripts/super_sidebar/components/menu_section.vue index 5de6e04d827..93c249dffeb 100644 --- a/app/assets/javascripts/super_sidebar/components/menu_section.vue +++ b/app/assets/javascripts/super_sidebar/components/menu_section.vue @@ -69,6 +69,7 @@ export default { <template> <component :is="tag"> + <hr v-if="separated" aria-hidden="true" class="gl-mx-4 gl-my-2" /> <button class="gl-rounded-base gl-relative gl-display-flex gl-align-items-center gl-mb-1 gl-py-3 gl-px-0 gl-line-height-normal gl-text-black-normal! gl-hover-bg-t-gray-a-08 gl-focus-bg-t-gray-a-08 gl-text-decoration-none! gl-appearance-none gl-border-0 gl-bg-transparent gl-text-left gl-w-full gl-focus--focus" :class="computedLinkClasses" @@ -117,6 +118,5 @@ export default { /> </slot> </gl-collapse> - <hr v-if="separated" aria-hidden="true" class="gl-mx-4 gl-my-2" /> </component> </template> diff --git a/app/assets/javascripts/super_sidebar/components/pinned_section.vue b/app/assets/javascripts/super_sidebar/components/pinned_section.vue index 4fc86e41ef2..ccd739c8bb1 100644 --- a/app/assets/javascripts/super_sidebar/components/pinned_section.vue +++ b/app/assets/javascripts/super_sidebar/components/pinned_section.vue @@ -23,6 +23,11 @@ export default { required: false, default: () => [], }, + separated: { + type: Boolean, + required: false, + default: false, + }, }, data() { return { @@ -69,7 +74,7 @@ export default { <menu-section :item="sectionItem" :expanded="expanded" - :separated="true" + :separated="separated" @collapse-toggle="expanded = !expanded" > <draggable diff --git a/app/assets/javascripts/super_sidebar/components/sidebar_menu.vue b/app/assets/javascripts/super_sidebar/components/sidebar_menu.vue index 12abd727ef0..08af9232107 100644 --- a/app/assets/javascripts/super_sidebar/components/sidebar_menu.vue +++ b/app/assets/javascripts/super_sidebar/components/sidebar_menu.vue @@ -89,6 +89,9 @@ export default { supportsPins() { return PANELS_WITH_PINS.includes(this.panelType); }, + hasStaticItems() { + return this.staticItems.length > 0; + }, }, methods: { createPin(itemId) { @@ -126,34 +129,50 @@ export default { Sentry.captureException(e); }); }, + isSection(navItem) { + return navItem.items?.length; + }, }, }; </script> <template> <nav class="gl-p-2 gl-relative"> - <template v-if="staticItems.length > 0"> - <ul class="gl-p-0 gl-m-0"> - <nav-item v-for="item in staticItems" :key="item.id" :item="item" is-static /> - </ul> - <hr aria-hidden="true" class="gl-my-2 gl-mx-4" /> - </template> + <ul v-if="hasStaticItems" class="gl-p-0 gl-m-0"> + <nav-item v-for="item in staticItems" :key="item.id" :item="item" is-static /> + </ul> <pinned-section v-if="supportsPins" + separated :items="pinnedItems" @pin-remove="destroyPin" @pin-reorder="movePin" /> + <hr + v-if="supportsPins" + aria-hidden="true" + class="gl-my-2 gl-mx-4" + data-testid="main-menu-separator" + /> <ul class="gl-p-0 gl-list-style-none"> - <component - :is="item.items && item.items.length ? 'MenuSection' : 'NavItem'" - v-for="item in nonStaticItems" - :key="item.id" - :item="item" - tag="li" - @pin-add="createPin" - @pin-remove="destroyPin" - /> + <template v-for="item in nonStaticItems"> + <menu-section + v-if="isSection(item)" + :key="item.id" + :item="item" + :separated="item.separated" + @pin-add="createPin" + @pin-remove="destroyPin" + /> + <nav-item + v-else + :key="item.id" + :item="item" + tag="li" + @pin-add="createPin" + @pin-remove="destroyPin" + /> + </template> </ul> </nav> </template> diff --git a/app/assets/javascripts/work_items/components/widget_wrapper.vue b/app/assets/javascripts/work_items/components/widget_wrapper.vue index 3f5ff526e91..6ae30e9b084 100644 --- a/app/assets/javascripts/work_items/components/widget_wrapper.vue +++ b/app/assets/javascripts/work_items/components/widget_wrapper.vue @@ -45,7 +45,7 @@ export default { <template> <div id="tasks" - class="gl-rounded-base gl-border-1 gl-border-solid gl-border-gray-100 gl-bg-gray-10 gl-mt-4" + class="gl-rounded-base gl-border-1 gl-border-solid gl-border-gray-100 gl-bg-gray-10 gl-mt-5" > <div class="gl-pl-5 gl-pr-4 gl-py-4 gl-display-flex gl-justify-content-space-between gl-bg-white gl-rounded-base" diff --git a/app/assets/stylesheets/page_bundles/design_management.scss b/app/assets/stylesheets/page_bundles/design_management.scss index f56eb4ae6fb..b42e6fd85fa 100644 --- a/app/assets/stylesheets/page_bundles/design_management.scss +++ b/app/assets/stylesheets/page_bundles/design_management.scss @@ -8,12 +8,6 @@ $t-gray-a-16-design-pin: rgba($black, 0.16); background: transparent; } -.design-checkbox { - position: absolute; - top: $gl-padding; - left: 30px; -} - .layout-page.design-detail-layout { max-height: 100vh; } diff --git a/app/helpers/projects/ml/experiments_helper.rb b/app/helpers/projects/ml/experiments_helper.rb index 6e5e13ef0b6..7aef208447a 100644 --- a/app/helpers/projects/ml/experiments_helper.rb +++ b/app/helpers/projects/ml/experiments_helper.rb @@ -5,27 +5,6 @@ module Projects require 'json' include ActionView::Helpers::NumberHelper - def show_candidate_view_model(candidate) - data = { - candidate: { - params: candidate.params, - metrics: candidate.latest_metrics, - info: { - iid: candidate.iid, - eid: candidate.eid, - path_to_artifact: link_to_artifact(candidate), - experiment_name: candidate.experiment.name, - path_to_experiment: link_to_experiment(candidate.project, candidate.experiment), - path: link_to_details(candidate), - status: candidate.status - }, - metadata: candidate.metadata - } - } - - Gitlab::Json.generate(data) - end - def experiment_as_data(experiment) data = { name: experiment.name, diff --git a/app/models/organization.rb b/app/models/organization.rb index 73a7e84305f..cfbbbf1183e 100644 --- a/app/models/organization.rb +++ b/app/models/organization.rb @@ -1,6 +1,26 @@ # frozen_string_literal: true -# rubocop: disable Gitlab/NamespacedClass class Organization < ApplicationRecord + DEFAULT_ORGANIZATION_ID = 1 + + scope :without_default, -> { where.not(id: DEFAULT_ORGANIZATION_ID) } + + before_destroy :check_if_default_organization + + validates :name, + presence: true, + length: { maximum: 255 }, + uniqueness: { case_sensitive: false } + + def default? + id == DEFAULT_ORGANIZATION_ID + end + + private + + def check_if_default_organization + return unless default? + + raise ActiveRecord::RecordNotDestroyed, _('Cannot delete the default organization') + end end -# rubocop: enable Gitlab/NamespacedClass diff --git a/app/presenters/ml/candidate_details_presenter.rb b/app/presenters/ml/candidate_details_presenter.rb new file mode 100644 index 00000000000..58ec2aee471 --- /dev/null +++ b/app/presenters/ml/candidate_details_presenter.rb @@ -0,0 +1,88 @@ +# frozen_string_literal: true + +module Ml + class CandidateDetailsPresenter + include Rails.application.routes.url_helpers + + def initialize(candidate) + @candidate = candidate + end + + def present + data = { + candidate: { + info: { + iid: candidate.iid, + eid: candidate.eid, + path_to_artifact: link_to_artifact, + experiment_name: candidate.experiment.name, + path_to_experiment: link_to_experiment, + path: link_to_details, + status: candidate.status, + ci_job: job_info + }, + params: candidate.params, + metrics: candidate.latest_metrics, + metadata: candidate.metadata + } + } + + Gitlab::Json.generate(data) + end + + private + + attr_reader :candidate + + def job_info + return unless candidate.from_ci? + + build = candidate.ci_build + + { + path: project_job_path(build.project, build), + name: build.name, + **user_info(build.user) || {}, + **mr_info(build.pipeline.merge_request) || {} + } + end + + def user_info(user) + return unless user + + { + user: { + path: user_path(user), + username: user.username + } + } + end + + def mr_info(mr) + return unless mr + + { + merge_request: { + path: project_merge_request_path(mr.project, mr), + title: mr.title + } + } + end + + def link_to_artifact + artifact = candidate.artifact + + return unless artifact.present? + + project_package_path(candidate.project, artifact) + end + + def link_to_details + project_ml_candidate_path(candidate.project, candidate.iid) + end + + def link_to_experiment + project_ml_experiment_path(candidate.project, candidate.experiment.iid) + end + end +end diff --git a/app/views/projects/ml/candidates/show.html.haml b/app/views/projects/ml/candidates/show.html.haml index aea74ecfb48..5d5059551d3 100644 --- a/app/views/projects/ml/candidates/show.html.haml +++ b/app/views/projects/ml/candidates/show.html.haml @@ -3,5 +3,6 @@ - add_to_breadcrumbs experiment.name, project_ml_experiment_path(@project, experiment.iid) - breadcrumb_title "Candidate #{@candidate.iid}" - add_page_specific_style 'page_bundles/ml_experiment_tracking' +- presenter = ::Ml::CandidateDetailsPresenter.new(@candidate) -#js-show-ml-candidate{ data: { view_model: show_candidate_view_model(@candidate) } } +#js-show-ml-candidate{ data: { view_model: presenter.present } } |