diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2022-03-11 21:08:37 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2022-03-11 21:08:37 +0300 |
commit | c511df8a7e79a3df0b03eb774be53651a1aa465d (patch) | |
tree | 486d0b5dc967b610cce89286a7d7849deef8593e /app | |
parent | e46506bcc32de1af076ec8a5d51d405f827dd986 (diff) |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app')
12 files changed, 326 insertions, 42 deletions
diff --git a/app/assets/javascripts/environments/components/new_environment_folder.vue b/app/assets/javascripts/environments/components/new_environment_folder.vue index 510c194f15f..30a178db5cc 100644 --- a/app/assets/javascripts/environments/components/new_environment_folder.vue +++ b/app/assets/javascripts/environments/components/new_environment_folder.vue @@ -19,6 +19,10 @@ export default { type: Object, required: true, }, + scope: { + type: String, + required: true, + }, }, data() { return { visible: false, interval: undefined }; @@ -27,7 +31,7 @@ export default { folder: { query: folderQuery, variables() { - return { environment: this.nestedEnvironment.latest }; + return { environment: this.nestedEnvironment.latest, scope: this.scope }; }, pollInterval() { return this.interval; @@ -52,7 +56,7 @@ export default { return this.visible ? this.$options.i18n.collapse : this.$options.i18n.expand; }, count() { - return this.folder?.availableCount ?? 0; + return this.folder?.[`${this.scope}Count`] ?? 0; }, folderClass() { return { 'gl-font-weight-bold': this.visible }; diff --git a/app/assets/javascripts/environments/components/new_environment_item.vue b/app/assets/javascripts/environments/components/new_environment_item.vue index 80d9b300d3f..f35fabccae7 100644 --- a/app/assets/javascripts/environments/components/new_environment_item.vue +++ b/app/assets/javascripts/environments/components/new_environment_item.vue @@ -302,7 +302,11 @@ export default { class="gl-pl-4" /> </div> - <div v-if="upcomingDeployment" :class="$options.deploymentClasses"> + <div + v-if="upcomingDeployment" + :class="$options.deploymentClasses" + data-testid="upcoming-deployment-content" + > <deployment :deployment="upcomingDeployment" :class="{ 'gl-ml-7': inFolder }" diff --git a/app/assets/javascripts/environments/components/new_environments_app.vue b/app/assets/javascripts/environments/components/new_environments_app.vue index 67fd6ffd975..8e6457ed918 100644 --- a/app/assets/javascripts/environments/components/new_environments_app.vue +++ b/app/assets/javascripts/environments/components/new_environments_app.vue @@ -16,12 +16,14 @@ import EnvironmentItem from './new_environment_item.vue'; import ConfirmRollbackModal from './confirm_rollback_modal.vue'; import DeleteEnvironmentModal from './delete_environment_modal.vue'; import CanaryUpdateModal from './canary_update_modal.vue'; +import EmptyState from './empty_state.vue'; export default { components: { DeleteEnvironmentModal, CanaryUpdateModal, ConfirmRollbackModal, + EmptyState, EnvironmentFolder, EnableReviewAppModal, EnvironmentItem, @@ -66,7 +68,7 @@ export default { query: environmentToChangeCanaryQuery, }, }, - inject: ['newEnvironmentPath', 'canCreateEnvironment'], + inject: ['newEnvironmentPath', 'canCreateEnvironment', 'helpPagePath'], i18n: { newEnvironmentButtonLabel: s__('Environments|New environment'), reviewAppButtonLabel: s__('Environments|Enable review app'), @@ -103,6 +105,9 @@ export default { environments() { return this.environmentApp?.environments?.filter((e) => e.size === 1) ?? []; }, + hasEnvironments() { + return this.environments.length > 0 || this.folders.length > 0; + }, availableCount() { return this.environmentApp?.availableCount; }, @@ -221,19 +226,23 @@ export default { </template> </gl-tab> </gl-tabs> - <environment-folder - v-for="folder in folders" - :key="folder.name" - class="gl-mb-3" - :nested-environment="folder" - /> - <environment-item - v-for="environment in environments" - :key="environment.name" - class="gl-mb-3 gl-border-gray-100 gl-border-1 gl-border-b-solid" - :environment="environment.latest" - @change="resetPolling" - /> + <template v-if="hasEnvironments"> + <environment-folder + v-for="folder in folders" + :key="folder.name" + class="gl-mb-3" + :scope="scope" + :nested-environment="folder" + /> + <environment-item + v-for="environment in environments" + :key="environment.name" + class="gl-mb-3 gl-border-gray-100 gl-border-1 gl-border-b-solid" + :environment="environment.latest" + @change="resetPolling" + /> + </template> + <empty-state v-else :help-path="helpPagePath" /> <gl-pagination align="center" :total-items="totalItems" diff --git a/app/assets/javascripts/environments/graphql/queries/folder.query.graphql b/app/assets/javascripts/environments/graphql/queries/folder.query.graphql index 3292c916b2e..e8c145ee916 100644 --- a/app/assets/javascripts/environments/graphql/queries/folder.query.graphql +++ b/app/assets/javascripts/environments/graphql/queries/folder.query.graphql @@ -1,5 +1,5 @@ -query getEnvironmentFolder($environment: NestedLocalEnvironment) { - folder(environment: $environment) @client { +query getEnvironmentFolder($environment: NestedLocalEnvironment, $scope: String) { + folder(environment: $environment, scope: $scope) @client { availableCount environments stoppedCount diff --git a/app/assets/javascripts/environments/graphql/resolvers.js b/app/assets/javascripts/environments/graphql/resolvers.js index 2544fd5273c..a7866c1e778 100644 --- a/app/assets/javascripts/environments/graphql/resolvers.js +++ b/app/assets/javascripts/environments/graphql/resolvers.js @@ -59,8 +59,8 @@ export const resolvers = (endpoint) => ({ }; }); }, - folder(_, { environment: { folderPath } }) { - return axios.get(folderPath, { params: { per_page: 3 } }).then((res) => ({ + folder(_, { environment: { folderPath }, scope }) { + return axios.get(folderPath, { params: { scope, per_page: 3 } }).then((res) => ({ availableCount: res.data.available_count, environments: res.data.environments.map(mapEnvironment), stoppedCount: res.data.stopped_count, diff --git a/app/assets/javascripts/pipeline_wizard/components/commit.vue b/app/assets/javascripts/pipeline_wizard/components/commit.vue index 518b41c66b1..e68458a494f 100644 --- a/app/assets/javascripts/pipeline_wizard/components/commit.vue +++ b/app/assets/javascripts/pipeline_wizard/components/commit.vue @@ -195,7 +195,7 @@ export default { data-testid="branch_selector_group" label-for="branch" > - <ref-selector id="branch" v-model="branch" data-testid="branch" :project-id="projectPath" /> + <ref-selector id="branch" v-model="branch" :project-id="projectPath" data-testid="branch" /> </gl-form-group> <gl-alert v-if="!!commitError" @@ -206,7 +206,7 @@ export default { > {{ commitError }} </gl-alert> - <step-nav show-back-button v-bind="$props" @back="$emit('go-back')"> + <step-nav show-back-button v-bind="$props" @back="$emit('back')"> <template #after> <gl-button :disabled="isCommitButtonEnabled" diff --git a/app/assets/javascripts/pipeline_wizard/components/wrapper.vue b/app/assets/javascripts/pipeline_wizard/components/wrapper.vue new file mode 100644 index 00000000000..b7207576ddc --- /dev/null +++ b/app/assets/javascripts/pipeline_wizard/components/wrapper.vue @@ -0,0 +1,185 @@ +<script> +import { GlProgressBar } from '@gitlab/ui'; +import { Document } from 'yaml'; +import { merge } from '~/lib/utils/yaml'; +import { __ } from '~/locale'; +import { isValidStepSeq } from '~/pipeline_wizard/validators'; +import YamlEditor from './editor.vue'; +import WizardStep from './step.vue'; +import CommitStep from './commit.vue'; + +export const i18n = { + stepNofN: __('Step %{currentStep} of %{stepCount}'), + draft: __('Draft: %{filename}'), + overlayMessage: __(`Start inputting changes and we will generate a + YAML-file for you to add to your repository`), +}; + +export default { + name: 'PipelineWizardWrapper', + i18n, + components: { + GlProgressBar, + YamlEditor, + WizardStep, + CommitStep, + }, + props: { + steps: { + type: Object, + required: true, + validator: isValidStepSeq, + }, + projectPath: { + type: String, + required: true, + }, + defaultBranch: { + type: String, + required: true, + }, + filename: { + type: String, + required: true, + }, + }, + data() { + return { + highlightPath: null, + currentStepIndex: 0, + // TODO: In order to support updating existing pipelines, the below + // should contain a parsed version of an existing .gitlab-ci.yml. + // See https://gitlab.com/gitlab-org/gitlab/-/issues/355306 + compiled: new Document({}), + showPlaceholder: true, + pipelineBlob: null, + placeholder: this.getPlaceholder(), + }; + }, + computed: { + currentStepConfig() { + return this.steps.get(this.currentStepIndex); + }, + currentStepInputs() { + return this.currentStepConfig.get('inputs').toJSON(); + }, + currentStepTemplate() { + return this.currentStepConfig.get('template', true); + }, + currentStep() { + return this.currentStepIndex + 1; + }, + stepCount() { + return this.steps.items.length + 1; + }, + progress() { + return Math.ceil((this.currentStep / (this.stepCount + 1)) * 100); + }, + isLastStep() { + return this.currentStep === this.stepCount; + }, + }, + watch: { + isLastStep(value) { + if (value) this.resetHighlight(); + }, + }, + methods: { + resetHighlight() { + this.highlightPath = null; + }, + onUpdate() { + this.showPlaceholder = false; + }, + onEditorUpdate(blob) { + // TODO: In a later iteration, we could add a loopback allowing for + // changes from the editor to flow back into the model + // see https://gitlab.com/gitlab-org/gitlab/-/issues/355312 + this.pipelineBlob = blob; + }, + getPlaceholder() { + const doc = new Document({}); + this.steps.items.forEach((tpl) => { + merge(doc, tpl.get('template').clone()); + }); + return doc; + }, + }, +}; +</script> + +<template> + <div class="row gl-mt-8"> + <main class="col-md-6 gl-pr-8"> + <header class="gl-mb-5"> + <h3 class="text-secondary gl-mt-0" data-testid="step-count"> + {{ sprintf($options.i18n.stepNofN, { currentStep, stepCount }) }} + </h3> + <gl-progress-bar :value="progress" variant="success" /> + </header> + <section class="gl-mb-4"> + <commit-step + v-if="isLastStep" + ref="step" + :default-branch="defaultBranch" + :file-content="pipelineBlob" + :filename="filename" + :project-path="projectPath" + @back="currentStepIndex--" + /> + <wizard-step + v-else + :key="currentStepIndex" + ref="step" + :compiled.sync="compiled" + :has-next-step="currentStepIndex < steps.items.length" + :has-previous-step="currentStepIndex > 0" + :highlight.sync="highlightPath" + :inputs="currentStepInputs" + :template="currentStepTemplate" + @back="currentStepIndex--" + @next="currentStepIndex++" + @update:compiled="onUpdate" + /> + </section> + </main> + <aside class="col-md-6 gl-pt-3"> + <div + class="gl-border-1 gl-border-gray-100 gl-border-solid border-radius-default gl-bg-gray-10" + > + <h6 class="gl-p-2 gl-px-4 text-secondary" data-testid="editor-header"> + {{ sprintf($options.i18n.draft, { filename }) }} + </h6> + <div class="gl-relative gl-overflow-hidden"> + <yaml-editor + :aria-hidden="showPlaceholder" + :doc="showPlaceholder ? placeholder : compiled" + :filename="filename" + :highlight="highlightPath" + class="gl-w-full" + @update:yaml="onEditorUpdate" + /> + <div + v-if="showPlaceholder" + class="gl-absolute gl-top-0 gl-right-0 gl-bottom-0 gl-left-0 gl-filter-blur-1" + data-testid="placeholder-overlay" + > + <div + class="gl-absolute gl-top-0 gl-right-0 gl-bottom-0 gl-left-0 bg-white gl-opacity-5 gl-z-index-2" + ></div> + <div + class="gl-relative gl-h-full gl-display-flex gl-align-items-center gl-justify-content-center gl-z-index-3" + > + <div class="gl-max-w-34"> + <h4 data-testid="filename">{{ filename }}</h4> + <p data-testid="description"> + {{ $options.i18n.overlayMessage }} + </p> + </div> + </div> + </div> + </div> + </div> + </aside> + </div> +</template> diff --git a/app/assets/javascripts/pipeline_wizard/pipeline_wizard.vue b/app/assets/javascripts/pipeline_wizard/pipeline_wizard.vue new file mode 100644 index 00000000000..7200b4e3782 --- /dev/null +++ b/app/assets/javascripts/pipeline_wizard/pipeline_wizard.vue @@ -0,0 +1,65 @@ +<script> +import { parseDocument } from 'yaml'; +import WizardWrapper from './components/wrapper.vue'; + +export default { + name: 'PipelineWizard', + components: { + WizardWrapper, + }, + props: { + template: { + type: String, + required: true, + }, + projectPath: { + type: String, + required: true, + }, + defaultBranch: { + type: String, + required: true, + }, + defaultFilename: { + type: String, + required: false, + default: '.gitlab-ci.yml', + }, + }, + computed: { + parsedTemplate() { + return this.template ? parseDocument(this.template) : null; + }, + title() { + return this.parsedTemplate?.get('title'); + }, + description() { + return this.parsedTemplate?.get('description'); + }, + filename() { + return this.parsedTemplate?.get('filename') || this.defaultFilename; + }, + steps() { + return this.parsedTemplate?.get('steps'); + }, + }, +}; +</script> + +<template> + <div> + <div class="gl-my-8"> + <h2 class="gl-mb-4" data-testid="title">{{ title }}</h2> + <p class="text-tertiary gl-font-lg gl-max-w-80" data-testid="description"> + {{ description }} + </p> + </div> + <wizard-wrapper + v-if="steps" + :default-branch="defaultBranch" + :filename="filename" + :project-path="projectPath" + :steps="steps" + /> + </div> +</template> diff --git a/app/assets/javascripts/pipeline_wizard/validators.js b/app/assets/javascripts/pipeline_wizard/validators.js new file mode 100644 index 00000000000..57cd56b23a5 --- /dev/null +++ b/app/assets/javascripts/pipeline_wizard/validators.js @@ -0,0 +1,4 @@ +import { isSeq } from 'yaml'; + +export const isValidStepSeq = (v) => + isSeq(v) && v.items.every((s) => s.get('inputs') && s.get('template')); diff --git a/app/assets/stylesheets/utilities.scss b/app/assets/stylesheets/utilities.scss index 8a4f9c32f9f..d7a5e21e303 100644 --- a/app/assets/stylesheets/utilities.scss +++ b/app/assets/stylesheets/utilities.scss @@ -342,4 +342,27 @@ to @gitlab/ui by https://gitlab.com/gitlab-org/gitlab-ui/-/issues/1709 margin-bottom: $gl-spacing-scale-12 !important; // only need !important for now so that it overrides styles from @gitlab/ui which currently take precedence } } + /* End gitlab-ui#1709 */ + +/* + * The below two styles will be moved to @gitlab/ui by + * https://gitlab.com/gitlab-org/gitlab-ui/-/issues/1750 + */ +.gl-max-w-34 { + max-width: 34 * $grid-size; +} + +.gl-max-w-80 { + max-width: 80 * $grid-size; +} + +/* + * The below style will be moved to @gitlab/ui by + * https://gitlab.com/gitlab-org/gitlab-ui/-/issues/1751 + */ +.gl-filter-blur-1 { + backdrop-filter: blur(2px); + /* stylelint-disable property-no-vendor-prefix */ + -webkit-backdrop-filter: blur(2px); // still required by Safari +} diff --git a/app/presenters/search_service_presenter.rb b/app/presenters/search_service_presenter.rb index 72f967b8beb..4755b88cbea 100644 --- a/app/presenters/search_service_presenter.rb +++ b/app/presenters/search_service_presenter.rb @@ -25,7 +25,7 @@ class SearchServicePresenter < Gitlab::View::Presenter::Delegated case scope when 'users' - objects.eager_load(:status) # rubocop:disable CodeReuse/ActiveRecord + objects.eager_load(:status) if objects.respond_to?(:eager_load) # rubocop:disable CodeReuse/ActiveRecord when 'commits' prepare_commits_for_rendering(objects) else diff --git a/app/views/projects/environments/index.html.haml b/app/views/projects/environments/index.html.haml index 77b2fc25c9a..e4b8750b96c 100644 --- a/app/views/projects/environments/index.html.haml +++ b/app/views/projects/environments/index.html.haml @@ -1,21 +1,11 @@ - page_title _("Environments") - add_page_specific_style 'page_bundles/environments' -- if Feature.enabled?(:new_environments_table) - #environments-table{ data: { endpoint: project_environments_path(@project, format: :json), - "can-read-environment" => can?(current_user, :read_environment, @project).to_s, - "can-create-environment" => can?(current_user, :create_environment, @project).to_s, - "new-environment-path" => new_project_environment_path(@project), - "help-page-path" => help_page_path("ci/environments/index.md"), - "project-path" => @project.full_path, - "project-id" => @project.id, - "default-branch-name" => @project.default_branch_or_main } } -- else - #environments-list-view{ data: { environments_data: environments_list_data, - "can-read-environment" => can?(current_user, :read_environment, @project).to_s, - "can-create-environment" => can?(current_user, :create_environment, @project).to_s, - "new-environment-path" => new_project_environment_path(@project), - "help-page-path" => help_page_path("ci/environments/index.md"), - "project-path" => @project.full_path, - "project-id" => @project.id, - "default-branch-name" => @project.default_branch_or_main } } +#environments-table{ data: { endpoint: project_environments_path(@project, format: :json), + "can-read-environment" => can?(current_user, :read_environment, @project).to_s, + "can-create-environment" => can?(current_user, :create_environment, @project).to_s, + "new-environment-path" => new_project_environment_path(@project), + "help-page-path" => help_page_path("ci/environments/index.md"), + "project-path" => @project.full_path, + "project-id" => @project.id, + "default-branch-name" => @project.default_branch_or_main } } |