diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2023-06-05 21:09:44 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2023-06-05 21:09:44 +0300 |
commit | 2f1a81fd16ff9968d6b986f8a407d963bc2218f9 (patch) | |
tree | d079c1abc2bc282e749a676651c0f02d288874f3 /app | |
parent | 18e9429b63f9a095b1ba3606856537b9ca291eac (diff) |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app')
30 files changed, 491 insertions, 88 deletions
diff --git a/app/assets/javascripts/diffs/components/tree_list.vue b/app/assets/javascripts/diffs/components/tree_list.vue index 4cc60df0ecb..b9bfceee6b4 100644 --- a/app/assets/javascripts/diffs/components/tree_list.vue +++ b/app/assets/javascripts/diffs/components/tree_list.vue @@ -170,7 +170,7 @@ export default { > <div class="gl-pb-3 position-relative tree-list-search d-flex"> <div class="flex-fill d-flex"> - <gl-icon name="search" class="gl-absolute gl-top-5 tree-list-icon" /> + <gl-icon name="search" class="gl-absolute gl-top-3 gl-left-3 tree-list-icon" /> <label for="diff-tree-search" class="sr-only">{{ $options.searchPlaceholder }}</label> <input id="diff-tree-search" @@ -189,7 +189,7 @@ export default { class="position-absolute bg-transparent tree-list-icon tree-list-clear-icon border-0 p-0" @click="clearSearch" > - <gl-icon name="close" /> + <gl-icon name="close" class="gl-absolute gl-top-3 gl-right-1 tree-list-icon" /> </button> </div> </div> diff --git a/app/assets/javascripts/environments/components/kubernetes_overview.vue b/app/assets/javascripts/environments/components/kubernetes_overview.vue index a849adfc755..b9e6548f640 100644 --- a/app/assets/javascripts/environments/components/kubernetes_overview.vue +++ b/app/assets/javascripts/environments/components/kubernetes_overview.vue @@ -6,6 +6,7 @@ import { getIdFromGraphQLId } from '~/graphql_shared/utils'; import KubernetesAgentInfo from './kubernetes_agent_info.vue'; import KubernetesPods from './kubernetes_pods.vue'; import KubernetesTabs from './kubernetes_tabs.vue'; +import KubernetesStatusBar from './kubernetes_status_bar.vue'; export default { components: { @@ -15,6 +16,7 @@ export default { KubernetesAgentInfo, KubernetesPods, KubernetesTabs, + KubernetesStatusBar, }, inject: ['kasTunnelUrl'], props: { @@ -32,6 +34,9 @@ export default { return { isVisible: false, error: '', + hasFailedState: false, + podsLoading: false, + workloadTypesLoading: false, }; }, computed: { @@ -52,6 +57,14 @@ export default { }, }; }, + clusterHealthStatus() { + const clusterDataLoading = this.podsLoading || this.workloadTypesLoading; + if (clusterDataLoading) { + return ''; + } + + return this.hasFailedState ? 'error' : 'success'; + }, }, methods: { toggleCollapse() { @@ -82,6 +95,7 @@ export default { </p> <gl-collapse :visible="isVisible" class="gl-md-pl-7 gl-md-pr-5 gl-mt-4"> <template v-if="isVisible"> + <kubernetes-status-bar :cluster-health-status="clusterHealthStatus" class="gl-mb-4" /> <kubernetes-agent-info :cluster-agent="clusterAgent" class="gl-mb-5" /> <gl-alert v-if="error" variant="danger" :dismissible="false" class="gl-mb-5"> @@ -92,12 +106,16 @@ export default { :configuration="k8sAccessConfiguration" :namespace="namespace" class="gl-mb-5" - @cluster-error="onClusterError" /> + @cluster-error="onClusterError" + @loading="podsLoading = $event" + @failed="hasFailedState = true" /> <kubernetes-tabs :configuration="k8sAccessConfiguration" :namespace="namespace" class="gl-mb-5" @cluster-error="onClusterError" + @loading="workloadTypesLoading = $event" + @failed="hasFailedState = true" /></template> </gl-collapse> </div> diff --git a/app/assets/javascripts/environments/components/kubernetes_pods.vue b/app/assets/javascripts/environments/components/kubernetes_pods.vue index a153331ee58..aded3a4d0c4 100644 --- a/app/assets/javascripts/environments/components/kubernetes_pods.vue +++ b/app/assets/javascripts/environments/components/kubernetes_pods.vue @@ -3,6 +3,7 @@ import { GlLoadingIcon } from '@gitlab/ui'; import { GlSingleStat } from '@gitlab/ui/dist/charts'; import { s__ } from '~/locale'; import k8sPodsQuery from '../graphql/queries/k8s_pods.query.graphql'; +import { PHASE_RUNNING, PHASE_PENDING, PHASE_SUCCEEDED, PHASE_FAILED } from '../constants'; export default { components: { @@ -25,6 +26,9 @@ export default { this.error = error; this.$emit('cluster-error', this.error); }, + watchLoading(isLoading) { + this.$emit('loading', isLoading); + }, }, }, props: { @@ -42,41 +46,39 @@ export default { error: '', }; }, - computed: { podStats() { if (!this.k8sPods) return null; return [ { - // eslint-disable-next-line @gitlab/require-i18n-strings - value: this.getPodsByPhase('Running'), + value: this.countPodsByPhase(PHASE_RUNNING), title: this.$options.i18n.runningPods, }, { - // eslint-disable-next-line @gitlab/require-i18n-strings - value: this.getPodsByPhase('Pending'), + value: this.countPodsByPhase(PHASE_PENDING), title: this.$options.i18n.pendingPods, }, { - // eslint-disable-next-line @gitlab/require-i18n-strings - value: this.getPodsByPhase('Succeeded'), + value: this.countPodsByPhase(PHASE_SUCCEEDED), title: this.$options.i18n.succeededPods, }, { - // eslint-disable-next-line @gitlab/require-i18n-strings - value: this.getPodsByPhase('Failed'), + value: this.countPodsByPhase(PHASE_FAILED), title: this.$options.i18n.failedPods, }, ]; }, loading() { - return this.$apollo.queries.k8sPods.loading; + return this.$apollo?.queries?.k8sPods?.loading; }, }, methods: { - getPodsByPhase(phase) { + countPodsByPhase(phase) { const filteredPods = this.k8sPods.filter((item) => item.status.phase === phase); + if (phase === PHASE_FAILED && filteredPods.length) { + this.$emit('failed'); + } return filteredPods.length; }, }, diff --git a/app/assets/javascripts/environments/components/kubernetes_status_bar.vue b/app/assets/javascripts/environments/components/kubernetes_status_bar.vue new file mode 100644 index 00000000000..94cd7438e46 --- /dev/null +++ b/app/assets/javascripts/environments/components/kubernetes_status_bar.vue @@ -0,0 +1,39 @@ +<script> +import { GlLoadingIcon, GlBadge } from '@gitlab/ui'; +import { s__ } from '~/locale'; +import { HEALTH_BADGES } from '../constants'; + +export default { + components: { + GlLoadingIcon, + GlBadge, + }, + props: { + clusterHealthStatus: { + required: false, + type: String, + default: '', + validator(val) { + return ['error', 'success', ''].includes(val); + }, + }, + }, + computed: { + healthBadge() { + return HEALTH_BADGES[this.clusterHealthStatus]; + }, + }, + i18n: { + healthLabel: s__('Environment|Environment health'), + }, +}; +</script> +<template> + <div class="gl-display-flex gl-align-items-center gl-mr-3 gl-mb-2"> + <span class="gl-font-sm gl-font-monospace gl-mr-3">{{ $options.i18n.healthLabel }}</span> + <gl-loading-icon v-if="!clusterHealthStatus" size="sm" inline /> + <gl-badge v-else-if="healthBadge" :variant="healthBadge.variant"> + {{ healthBadge.text }} + </gl-badge> + </div> +</template> diff --git a/app/assets/javascripts/environments/components/kubernetes_summary.vue b/app/assets/javascripts/environments/components/kubernetes_summary.vue index 85fc1c1a07d..b00e82809f6 100644 --- a/app/assets/javascripts/environments/components/kubernetes_summary.vue +++ b/app/assets/javascripts/environments/components/kubernetes_summary.vue @@ -32,6 +32,12 @@ export default { error(error) { this.$emit('cluster-error', error); }, + result() { + this.checkFailed(); + }, + watchLoading(isLoading) { + this.$emit('loading', isLoading); + }, }, }, props: { @@ -46,7 +52,7 @@ export default { }, computed: { summaryLoading() { - return this.$apollo.queries.k8sWorkloads.loading; + return this.$apollo?.queries?.k8sWorkloads?.loading; }, summaryCount() { return this.k8sWorkloads ? Object.values(this.k8sWorkloads).flat().length : 0; @@ -128,6 +134,17 @@ export default { }; }, }, + methods: { + checkFailed() { + const failed = this.summaryObjects.some((workloadType) => { + return workloadType.items?.failed?.length > 0; + }); + + if (failed) { + this.$emit('failed'); + } + }, + }, i18n: { summaryTitle: s__('Environment|Summary'), deployments: s__('Environment|Deployments'), diff --git a/app/assets/javascripts/environments/components/kubernetes_tabs.vue b/app/assets/javascripts/environments/components/kubernetes_tabs.vue index b900c23b2b7..4492d209e3b 100644 --- a/app/assets/javascripts/environments/components/kubernetes_tabs.vue +++ b/app/assets/javascripts/environments/components/kubernetes_tabs.vue @@ -134,7 +134,12 @@ export default { </script> <template> <gl-tabs> - <kubernetes-summary :namespace="namespace" :configuration="configuration" /> + <kubernetes-summary + :namespace="namespace" + :configuration="configuration" + @loading="$emit('loading', $event)" + @failed="$emit('failed')" + /> <gl-tab> <template #title> diff --git a/app/assets/javascripts/environments/constants.js b/app/assets/javascripts/environments/constants.js index 448cee530f6..2b178964c37 100644 --- a/app/assets/javascripts/environments/constants.js +++ b/app/assets/javascripts/environments/constants.js @@ -89,3 +89,22 @@ export const ENVIRONMENT_NEW_HELP_TEXT = __( export const ENVIRONMENT_EDIT_HELP_TEXT = ENVIRONMENT_NEW_HELP_TEXT; export const SERVICES_LIMIT_PER_PAGE = 10; + +export const CLUSTER_STATUS_HEALTHY_TEXT = s__('Environment|Healthy'); +export const CLUSTER_STATUS_UNHEALTHY_TEXT = s__('Environment|Unhealthy'); + +export const HEALTH_BADGES = { + success: { + variant: 'success', + text: CLUSTER_STATUS_HEALTHY_TEXT, + }, + error: { + variant: 'danger', + text: CLUSTER_STATUS_UNHEALTHY_TEXT, + }, +}; + +export const PHASE_RUNNING = 'Running'; +export const PHASE_PENDING = 'Pending'; +export const PHASE_SUCCEEDED = 'Succeeded'; +export const PHASE_FAILED = 'Failed'; diff --git a/app/assets/javascripts/pages/projects/shared/permissions/components/ci_catalog_settings.vue b/app/assets/javascripts/pages/projects/shared/permissions/components/ci_catalog_settings.vue new file mode 100644 index 00000000000..ed5ba3c2653 --- /dev/null +++ b/app/assets/javascripts/pages/projects/shared/permissions/components/ci_catalog_settings.vue @@ -0,0 +1,165 @@ +<script> +import { GlBadge, GlLink, GlLoadingIcon, GlModal, GlSprintf, GlToggle } from '@gitlab/ui'; +import { createAlert, VARIANT_INFO } from '~/alert'; +import { __, s__ } from '~/locale'; +import { helpPagePath } from '~/helpers/help_page_helper'; + +import getCiCatalogSettingsQuery from '../graphql/queries/get_ci_catalog_settings.query.graphql'; +import catalogResourcesCreate from '../graphql/mutations/catalog_resources_create.mutation.graphql'; + +export const i18n = { + badgeText: __('Experiment'), + catalogResourceQueryError: s__( + 'CiCatalog|There was a problem fetching the CI/CD Catalog setting.', + ), + catalogResourceMutationError: s__( + 'CiCatalog|There was a problem marking the project as a CI/CD Catalog resource.', + ), + catalogResourceMutationSuccess: s__('CiCatalog|This project is now a CI/CD Catalog resource.'), + ciCatalogLabel: s__('CiCatalog|CI/CD Catalog resource'), + ciCatalogHelpText: s__( + 'CiCatalog|Mark project as a CI/CD Catalog resource. %{linkStart}What is the CI/CD Catalog?%{linkEnd}', + ), + modal: { + actionPrimary: { + text: s__('CiCatalog|Mark project as a CI/CD Catalog resource'), + }, + actionCancel: { + text: __('Cancel'), + }, + body: s__( + 'CiCatalog|This project will be marked as a CI/CD Catalog resource and will be visible in the CI/CD Catalog. This action is not reversible.', + ), + title: s__('CiCatalog|Mark project as a CI/CD Catalog resource'), + }, + readMeHelpText: s__( + 'CiCatalog|The project must contain a README.md file and a template.yml file. When enabled, the repository is available in the CI/CD Catalog.', + ), +}; + +export const ciCatalogHelpPath = helpPagePath('ci/components/index', { + anchor: 'components-catalog', +}); + +export default { + i18n, + components: { + GlBadge, + GlLink, + GlLoadingIcon, + GlModal, + GlSprintf, + GlToggle, + }, + props: { + fullPath: { + type: String, + required: true, + }, + }, + data() { + return { + ciCatalogHelpPath, + isCatalogResource: false, + showCatalogResourceModal: false, + }; + }, + apollo: { + isCatalogResource: { + query: getCiCatalogSettingsQuery, + variables() { + return { + fullPath: this.fullPath, + }; + }, + update({ project }) { + return project?.isCatalogResource || false; + }, + error() { + createAlert({ message: this.$options.i18n.catalogResourceQueryError }); + }, + }, + }, + computed: { + isLoading() { + return this.$apollo.queries.isCatalogResource.loading; + }, + }, + methods: { + async markProjectAsCatalogResource() { + try { + const { + data: { + catalogResourcesCreate: { errors }, + }, + } = await this.$apollo.mutate({ + mutation: catalogResourcesCreate, + variables: { input: { projectPath: this.fullPath } }, + }); + + if (errors.length) { + throw new Error(errors[0]); + } + + this.isCatalogResource = true; + createAlert({ + message: this.$options.i18n.catalogResourceMutationSuccess, + variant: VARIANT_INFO, + }); + } catch (error) { + const message = error.message || this.$options.i18n.catalogResourceMutationError; + createAlert({ message }); + } + }, + onCatalogResourceEnabledToggled() { + this.showCatalogResourceModal = true; + }, + onModalCanceled() { + this.showCatalogResourceModal = false; + }, + }, +}; +</script> + +<template> + <div> + <gl-loading-icon v-if="isLoading" /> + <div v-else data-testid="ci-catalog-settings"> + <div> + <label class="gl-mb-1 gl-mr-2"> + {{ $options.i18n.ciCatalogLabel }} + </label> + <gl-badge size="sm" variant="info"> {{ $options.i18n.badgeText }} </gl-badge> + </div> + <gl-sprintf :message="$options.i18n.ciCatalogHelpText"> + <template #link="{ content }"> + <gl-link :href="ciCatalogHelpPath" target="_blank">{{ content }}</gl-link> + </template> + </gl-sprintf> + <gl-toggle + class="gl-my-2" + :disabled="isCatalogResource" + :value="isCatalogResource" + :label="$options.i18n.ciCatalogLabel" + label-position="hidden" + name="ci_resource_enabled" + @change="onCatalogResourceEnabledToggled" + /> + <div class="gl-text-secondary"> + {{ $options.i18n.readMeHelpText }} + </div> + <gl-modal + :visible="showCatalogResourceModal" + modal-id="mark-as-catalog-resource" + size="sm" + :title="$options.i18n.modal.title" + :action-cancel="$options.i18n.modal.actionCancel" + :action-primary="$options.i18n.modal.actionPrimary" + @canceled="onModalCanceled" + @primary="markProjectAsCatalogResource" + > + {{ $options.i18n.modal.body }} + </gl-modal> + </div> + </div> +</template> diff --git a/app/assets/javascripts/pages/projects/shared/permissions/components/settings_panel.vue b/app/assets/javascripts/pages/projects/shared/permissions/components/settings_panel.vue index 84a5d980d39..c54596488af 100644 --- a/app/assets/javascripts/pages/projects/shared/permissions/components/settings_panel.vue +++ b/app/assets/javascripts/pages/projects/shared/permissions/components/settings_panel.vue @@ -20,6 +20,7 @@ import { import { toggleHiddenClassBySelector } from '../external'; import ProjectFeatureSetting from './project_feature_setting.vue'; import ProjectSettingRow from './project_setting_row.vue'; +import CiCatalogSettings from './ci_catalog_settings.vue'; const FEATURE_ACCESS_LEVEL_ANONYMOUS = [30, s__('ProjectSettings|Everyone')]; @@ -34,6 +35,7 @@ export default { ...CVE_ID_REQUEST_BUTTON_I18N, analyticsLabel: s__('ProjectSettings|Analytics'), containerRegistryLabel: s__('ProjectSettings|Container registry'), + ciCdLabel: __('CI/CD'), forksLabel: s__('ProjectSettings|Forks'), issuesLabel: s__('ProjectSettings|Issues'), lfsLabel: s__('ProjectSettings|Git Large File Storage (LFS)'), @@ -62,7 +64,6 @@ export default { 'ProjectSettings|Track machine learning model experiments and artifacts.', ), pagesLabel: s__('ProjectSettings|Pages'), - ciCdLabel: __('CI/CD'), repositoryLabel: s__('ProjectSettings|Repository'), requirementsLabel: s__('ProjectSettings|Requirements'), releasesLabel: s__('ProjectSettings|Releases'), @@ -84,6 +85,7 @@ export default { modelExperimentsHelpPath, components: { + CiCatalogSettings, ProjectFeatureSetting, ProjectSettingRow, GlButton, @@ -106,6 +108,11 @@ export default { required: false, default: false, }, + canAddCatalogResource: { + type: Boolean, + required: false, + default: false, + }, currentSettings: { type: Object, required: true, @@ -365,6 +372,9 @@ export default { packageRegistryApiForEveryoneEnabledShown() { return this.visibilityLevel !== VISIBILITY_LEVEL_PUBLIC_INTEGER; }, + monitorOperationsFeatureAccessLevelOptions() { + return this.featureAccessLevelOptions.filter(([value]) => value <= this.monitorAccessLevel); + }, }, watch: { @@ -985,6 +995,11 @@ export default { /> </project-setting-row> </div> + <ci-catalog-settings + v-if="canAddCatalogResource" + class="gl-mb-5" + :full-path="confirmationPhrase" + /> <project-setting-row v-if="canDisableEmails" ref="email-settings" class="mb-3"> <label class="js-emails-disabled"> <input :value="emailsDisabled" type="hidden" name="project[emails_disabled]" /> diff --git a/app/assets/javascripts/pages/projects/shared/permissions/graphql/mutations/catalog_resources_create.mutation.graphql b/app/assets/javascripts/pages/projects/shared/permissions/graphql/mutations/catalog_resources_create.mutation.graphql new file mode 100644 index 00000000000..c3b73ebf248 --- /dev/null +++ b/app/assets/javascripts/pages/projects/shared/permissions/graphql/mutations/catalog_resources_create.mutation.graphql @@ -0,0 +1,5 @@ +mutation catalogResourcesCreate($input: CatalogResourcesCreateInput!) { + catalogResourcesCreate(input: $input) { + errors + } +} diff --git a/app/assets/javascripts/pages/projects/shared/permissions/graphql/queries/get_ci_catalog_settings.query.graphql b/app/assets/javascripts/pages/projects/shared/permissions/graphql/queries/get_ci_catalog_settings.query.graphql new file mode 100644 index 00000000000..0de06028386 --- /dev/null +++ b/app/assets/javascripts/pages/projects/shared/permissions/graphql/queries/get_ci_catalog_settings.query.graphql @@ -0,0 +1,6 @@ +query getCiCatalogSettings($fullPath: ID!) { + project(fullPath: $fullPath) { + id + isCatalogResource + } +} diff --git a/app/assets/javascripts/pages/projects/shared/permissions/index.js b/app/assets/javascripts/pages/projects/shared/permissions/index.js index de8b1cc400e..4b4589dc46c 100644 --- a/app/assets/javascripts/pages/projects/shared/permissions/index.js +++ b/app/assets/javascripts/pages/projects/shared/permissions/index.js @@ -1,8 +1,17 @@ import Vue from 'vue'; +import VueApollo from 'vue-apollo'; +import createDefaultClient from '~/lib/graphql'; + import { parseBoolean } from '~/lib/utils/common_utils'; import settingsPanel from './components/settings_panel.vue'; +Vue.use(VueApollo); + export default function initProjectPermissionsSettings() { + const apolloProvider = new VueApollo({ + defaultClient: createDefaultClient(), + }); + const mountPoint = document.querySelector('.js-project-permissions-form'); const componentPropsEl = document.querySelector('.js-project-permissions-form-data'); const componentProps = JSON.parse(componentPropsEl.innerHTML); @@ -19,6 +28,8 @@ export default function initProjectPermissionsSettings() { return new Vue({ el: mountPoint, + name: 'ProjectPermissionsRoot', + apolloProvider, provide: { additionalInformation, confirmDangerMessage, diff --git a/app/assets/javascripts/pipelines/components/pipeline_details_header.vue b/app/assets/javascripts/pipelines/components/pipeline_details_header.vue index 3030a14d1d5..3c10b766311 100644 --- a/app/assets/javascripts/pipelines/components/pipeline_details_header.vue +++ b/app/assets/javascripts/pipelines/components/pipeline_details_header.vue @@ -259,6 +259,9 @@ export default { commitPath() { return this.pipeline?.commit?.webPath || ''; }, + commitTitle() { + return this.pipeline?.commit?.title || ''; + }, totalJobsText() { return sprintf(__('%{jobs} Jobs'), { jobs: this.totalJobs, @@ -371,6 +374,9 @@ export default { <div v-else class="gl-display-flex gl-justify-content-space-between"> <div> <h3 v-if="name" class="gl-mt-0 gl-mb-2" data-testid="pipeline-name">{{ name }}</h3> + <h3 v-else class="gl-mt-0 gl-mb-2" data-testid="pipeline-commit-title"> + {{ commitTitle }} + </h3> <div> <ci-badge-link :status="detailedStatus" /> <div class="gl-ml-2 gl-mb-2 gl-display-inline-block gl-h-6"> diff --git a/app/assets/javascripts/pipelines/components/pipelines_list/time_ago.vue b/app/assets/javascripts/pipelines/components/pipelines_list/time_ago.vue index b7c812162b1..5fd7b8251b2 100644 --- a/app/assets/javascripts/pipelines/components/pipelines_list/time_ago.vue +++ b/app/assets/javascripts/pipelines/components/pipelines_list/time_ago.vue @@ -36,38 +36,11 @@ export default { finishedTime() { return this.pipeline?.details?.finished_at || this.pipeline?.finishedAt; }, - showInProgress() { - return !this.duration && !this.finishedTime && !this.skipped; - }, - showSkipped() { - return !this.duration && !this.finishedTime && this.skipped; - }, - skipped() { - return this.pipeline?.details?.status?.label === 'skipped'; - }, - stuck() { - return this.pipeline?.flags?.stuck; - }, }, }; </script> <template> <div class="gl-display-flex gl-flex-direction-column time-ago" :class="fontSize"> - <span - v-if="showInProgress" - class="gl-display-inline-flex gl-align-items-center" - data-testid="pipeline-in-progress" - > - <gl-icon v-if="stuck" name="warning" class="gl-mr-2" :size="12" data-testid="warning-icon" /> - <gl-icon v-else name="hourglass" class="gl-mr-2" :size="12" data-testid="hourglass-icon" /> - {{ s__('Pipeline|In progress') }} - </span> - - <span v-if="showSkipped" data-testid="pipeline-skipped"> - <gl-icon name="status_skipped_borderless" /> - {{ s__('Pipeline|Skipped') }} - </span> - <p v-if="duration" class="duration gl-display-inline-flex gl-align-items-center"> <gl-icon name="timer" class="gl-mr-2" :size="12" /> {{ durationFormatted }} diff --git a/app/assets/javascripts/pipelines/graphql/queries/get_pipeline_header_data.query.graphql b/app/assets/javascripts/pipelines/graphql/queries/get_pipeline_header_data.query.graphql index a47df2c0197..312d71e6e84 100644 --- a/app/assets/javascripts/pipelines/graphql/queries/get_pipeline_header_data.query.graphql +++ b/app/assets/javascripts/pipelines/graphql/queries/get_pipeline_header_data.query.graphql @@ -35,6 +35,7 @@ query getPipelineHeaderData($fullPath: ID!, $iid: ID!) { commit { id shortId + title webPath } finishedAt diff --git a/app/assets/javascripts/vue_merge_request_widget/components/deployment/deployment_action_button.vue b/app/assets/javascripts/vue_merge_request_widget/components/deployment/deployment_action_button.vue index bdd46d6a656..a3d5a6bed11 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/deployment/deployment_action_button.vue +++ b/app/assets/javascripts/vue_merge_request_widget/components/deployment/deployment_action_button.vue @@ -1,7 +1,6 @@ <script> import { GlTooltipDirective, GlButton } from '@gitlab/ui'; -import { __ } from '~/locale'; -import { RUNNING } from './constants'; +import { RUNNING, WILL_DEPLOY } from './constants'; export default { name: 'DeploymentActionButton', @@ -42,40 +41,50 @@ export default { }, computed: { isActionInProgress() { - return Boolean(this.computedDeploymentStatus === RUNNING || this.actionInProgress); - }, - actionInProgressTooltip() { - switch (this.actionInProgress) { - case this.actionsConfiguration.actionName: - return this.actionsConfiguration.busyText; - case null: - return ''; - default: - return __('Another action is currently in progress'); - } + return Boolean( + this.computedDeploymentStatus === RUNNING || + this.computedDeploymentStatus === WILL_DEPLOY || + this.actionInProgress, + ); }, isLoading() { - return this.actionInProgress === this.actionsConfiguration.actionName; + return ( + this.actionInProgress === this.actionsConfiguration.actionName || + this.computedDeploymentStatus === WILL_DEPLOY + ); }, }, }; </script> <template> - <span v-gl-tooltip :title="actionInProgressTooltip" class="gl-display-inline-block" tabindex="0"> - <gl-button - v-gl-tooltip - category="primary" - size="small" - :title="buttonTitle" - :aria-label="buttonTitle" - :loading="isLoading" - :disabled="isActionInProgress" - :class="`inline gl-ml-3 ${containerClasses}`" - :icon="icon" - @click="$emit('click')" - > - <slot> </slot> - </gl-button> - </span> + <gl-button + v-if="isLoading || isActionInProgress" + category="primary" + size="small" + :title="buttonTitle" + :aria-label="buttonTitle" + :loading="isLoading" + :disabled="isActionInProgress" + :class="`inline gl-ml-3 ${containerClasses}`" + :icon="icon" + @click="$emit('click')" + > + <slot> </slot> + </gl-button> + <gl-button + v-else + v-gl-tooltip.hover + category="primary" + size="small" + :title="buttonTitle" + :aria-label="buttonTitle" + :loading="isLoading" + :disabled="isActionInProgress" + :class="`inline gl-ml-3 ${containerClasses}`" + :icon="icon" + @click="$emit('click')" + > + <slot> </slot> + </gl-button> </template> diff --git a/app/assets/javascripts/vue_merge_request_widget/components/deployment/deployment_actions.vue b/app/assets/javascripts/vue_merge_request_widget/components/deployment/deployment_actions.vue index 306ed664326..e79d2db4b5a 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/deployment/deployment_actions.vue +++ b/app/assets/javascripts/vue_merge_request_widget/components/deployment/deployment_actions.vue @@ -71,11 +71,25 @@ export default { return this.deployment.details?.playable_build?.play_path; }, redeployPath() { + if (this.redeployMrWidgetFeatureFlagEnabled) { + return this.deployment.retry_url; + } return this.deployment.details?.playable_build?.retry_path; }, stopUrl() { return this.deployment.stop_url; }, + environmentAvailable() { + return Boolean(this.deployment.environment_available); + }, + redeployMrWidgetFeatureFlagEnabled() { + return this.glFeatures.reviewAppsRedeployMrWidget; + }, + showDeploymentActionButton() { + return ( + this.redeployPath && !this.environmentAvailable && this.redeployMrWidgetFeatureFlagEnabled + ); + }, }, actionsConfiguration: { [STOPPING]: { @@ -124,6 +138,10 @@ export default { MRWidgetService.executeInlineAction(endpoint) .then((resp) => { + if (this.redeployMrWidgetFeatureFlagEnabled) { + return; + } + const redirectUrl = resp?.data?.redirect_url; if (redirectUrl) { visitUrl(redirectUrl); @@ -167,7 +185,7 @@ export default { <span>{{ $options.actionsConfiguration[constants.DEPLOYING].buttonText }}</span> </deployment-action-button> <deployment-action-button - v-if="canBeManuallyRedeployed" + v-if="canBeManuallyRedeployed && !redeployMrWidgetFeatureFlagEnabled" :action-in-progress="actionInProgress" :actions-configuration="$options.actionsConfiguration[constants.REDEPLOYING]" :computed-deployment-status="computedDeploymentStatus" @@ -178,12 +196,12 @@ export default { <span>{{ $options.actionsConfiguration[constants.REDEPLOYING].buttonText }}</span> </deployment-action-button> <deployment-view-button - v-if="hasExternalUrls" + v-if="hasExternalUrls && environmentAvailable" :app-button-text="appButtonText" :deployment="deployment" /> <deployment-action-button - v-if="stopUrl" + v-if="stopUrl && environmentAvailable" :action-in-progress="actionInProgress" :computed-deployment-status="computedDeploymentStatus" :actions-configuration="$options.actionsConfiguration[constants.STOPPING]" @@ -192,5 +210,15 @@ export default { container-classes="js-stop-env" @click="stopEnvironment" /> + <deployment-action-button + v-if="showDeploymentActionButton" + :action-in-progress="actionInProgress" + :computed-deployment-status="computedDeploymentStatus" + :actions-configuration="$options.actionsConfiguration[constants.REDEPLOYING]" + :button-title="$options.actionsConfiguration[constants.REDEPLOYING].buttonText" + :icon="$options.btnIcons.repeat" + container-classes="js-redeploy-action" + @click="redeploy" + /> </div> </template> diff --git a/app/assets/javascripts/whats_new/components/app.vue b/app/assets/javascripts/whats_new/components/app.vue index a439675d467..dd5d4edda59 100644 --- a/app/assets/javascripts/whats_new/components/app.vue +++ b/app/assets/javascripts/whats_new/components/app.vue @@ -2,6 +2,7 @@ import { GlDrawer, GlInfiniteScroll, GlResizeObserverDirective } from '@gitlab/ui'; import { mapState, mapActions } from 'vuex'; import Tracking from '~/tracking'; +import { getContentWrapperHeight } from '~/lib/utils/dom_utils'; import { getDrawerBodyHeight } from '../utils/get_drawer_body_height'; import Feature from './feature.vue'; import SkeletonLoader from './skeleton_loader.vue'; @@ -28,6 +29,9 @@ export default { }, computed: { ...mapState(['open', 'features', 'pageInfo', 'drawerBodyHeight', 'fetching']), + getDrawerHeaderHeight() { + return getContentWrapperHeight(); + }, }, mounted() { this.openDrawer(this.versionDigest); @@ -69,6 +73,7 @@ export default { ref="drawer" v-gl-resize-observer="handleResize" class="whats-new-drawer gl-reset-line-height" + :header-height="getDrawerHeaderHeight" :z-index="700" :open="open" @close="closeDrawer" diff --git a/app/assets/stylesheets/components/whats_new.scss b/app/assets/stylesheets/components/whats_new.scss index 35c619a2e2f..f8160c04031 100644 --- a/app/assets/stylesheets/components/whats_new.scss +++ b/app/assets/stylesheets/components/whats_new.scss @@ -1,5 +1,4 @@ .whats-new-drawer { - margin-top: calc(#{$header-height} + #{$calc-application-bars-height}); @include gl-shadow-none; overflow-y: hidden; width: 500px; diff --git a/app/assets/stylesheets/page_bundles/merge_requests.scss b/app/assets/stylesheets/page_bundles/merge_requests.scss index b7d9650ab27..c59bdd70ba8 100644 --- a/app/assets/stylesheets/page_bundles/merge_requests.scss +++ b/app/assets/stylesheets/page_bundles/merge_requests.scss @@ -301,10 +301,6 @@ $tabs-holder-z-index: 250; } .tree-list-icon { - top: 50%; - left: 10px; - transform: translateY(-50%); - &, svg { fill: var(--gray-400, $gray-400); diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb index 0ce52f75913..2f37382da73 100644 --- a/app/controllers/projects/merge_requests_controller.rb +++ b/app/controllers/projects/merge_requests_controller.rb @@ -52,6 +52,7 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo push_frontend_feature_flag(:auto_merge_labels_mr_widget, project) push_force_frontend_feature_flag(:summarize_my_code_review, summarize_my_code_review_enabled?) push_frontend_feature_flag(:mr_activity_filters, current_user) + push_frontend_feature_flag(:review_apps_redeploy_mr_widget, project) end around_action :allow_gitaly_ref_name_caching, only: [:index, :show, :diffs, :discussions] diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb index 910b894675d..e925e16c004 100644 --- a/app/controllers/projects_controller.rb +++ b/app/controllers/projects_controller.rb @@ -41,6 +41,7 @@ class ProjectsController < Projects::ApplicationController push_frontend_feature_flag(:synchronize_fork, @project&.fork_source) push_frontend_feature_flag(:remove_monitor_metrics, @project) push_frontend_feature_flag(:explain_code_chat, current_user) + push_frontend_feature_flag(:ci_namespace_catalog_experimental, @project) push_licensed_feature(:file_locks) if @project.present? && @project.licensed_feature_available?(:file_locks) push_licensed_feature(:security_orchestration_policies) if @project.present? && @project.licensed_feature_available?(:security_orchestration_policies) push_force_frontend_feature_flag(:work_items, @project&.work_items_feature_flag_enabled?) diff --git a/app/graphql/resolvers/audit_events/audit_event_definitions_resolver.rb b/app/graphql/resolvers/audit_events/audit_event_definitions_resolver.rb new file mode 100644 index 00000000000..230301ca5da --- /dev/null +++ b/app/graphql/resolvers/audit_events/audit_event_definitions_resolver.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +module Resolvers + module AuditEvents + class AuditEventDefinitionsResolver < BaseResolver + type [Types::AuditEvents::DefinitionType], null: false + + def resolve + Gitlab::Audit::Type::Definition.definitions.values + end + end + end +end diff --git a/app/graphql/types/audit_events/definition_type.rb b/app/graphql/types/audit_events/definition_type.rb new file mode 100644 index 00000000000..94b88733798 --- /dev/null +++ b/app/graphql/types/audit_events/definition_type.rb @@ -0,0 +1,50 @@ +# frozen_string_literal: true + +module Types + module AuditEvents + class DefinitionType < ::Types::BaseObject + graphql_name 'AuditEventDefinition' + description 'Represents the YAML definitions for audit events defined ' \ + 'under ee/config/audit_events/types/<event-type-name>.yml ' \ + 'and config/audit_events/types/<event-type-name>.yml.' + + authorize :audit_event_definitions + + field :name, GraphQL::Types::String, + null: false, + description: 'Key name of the audit event.' + + field :description, GraphQL::Types::String, + null: false, + description: 'Description of what action the audit event tracks.' + + field :introduced_by_issue, GraphQL::Types::String, + null: true, + description: 'Link to the issue introducing the event. For older' \ + 'audit events, it can be a commit URL rather than a' \ + 'merge request URL.' + + field :introduced_by_mr, GraphQL::Types::String, + null: true, + description: 'Link to the merge request introducing the event. For' \ + 'older audit events, it can be a commit URL rather than' \ + 'a merge request URL.' + + field :feature_category, GraphQL::Types::String, + null: false, + description: 'Feature category associated with the event.' + + field :milestone, GraphQL::Types::String, + null: false, + description: 'Milestone the event was introduced in.' + + field :saved_to_database, GraphQL::Types::Boolean, + null: false, + description: 'Indicates if the event is saved to PostgreSQL database.' + + field :streamed, GraphQL::Types::Boolean, + null: false, + description: 'Indicates if the event is streamed to an external destination.' + end + end +end diff --git a/app/graphql/types/query_type.rb b/app/graphql/types/query_type.rb index 20dce54d740..b26e447f622 100644 --- a/app/graphql/types/query_type.rb +++ b/app/graphql/types/query_type.rb @@ -165,6 +165,12 @@ module Types alpha: { milestone: '15.1' }, description: 'Find a work item.' + field :audit_event_definitions, + Types::AuditEvents::DefinitionType.connection_type, + null: false, + description: 'Definitions for all audit events available on the instance.', + resolver: Resolvers::AuditEvents::AuditEventDefinitionsResolver + def design_management DesignManagementObject.new(nil) end diff --git a/app/helpers/ci/catalog/resources_helper.rb b/app/helpers/ci/catalog/resources_helper.rb index 9f70410f17f..bc77e0cd33a 100644 --- a/app/helpers/ci/catalog/resources_helper.rb +++ b/app/helpers/ci/catalog/resources_helper.rb @@ -3,6 +3,10 @@ module Ci module Catalog module ResourcesHelper + def can_add_catalog_resource?(_project) + false + end + def can_view_namespace_catalog?(_project) false end diff --git a/app/helpers/nav_helper.rb b/app/helpers/nav_helper.rb index 4f30b555ba0..c7864c1d45f 100644 --- a/app/helpers/nav_helper.rb +++ b/app/helpers/nav_helper.rb @@ -87,8 +87,6 @@ module NavHelper end def show_super_sidebar?(user = current_user) - return false unless Feature.enabled?(:super_sidebar_nav, user) - # The new sidebar is not enabled for anonymous use # Once we enable the new sidebar by default, this # should return true diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb index 7445d91ec04..6f675e1120c 100644 --- a/app/helpers/projects_helper.rb +++ b/app/helpers/projects_helper.rb @@ -421,8 +421,9 @@ module ProjectsHelper packagesAvailable: ::Gitlab.config.packages.enabled, packagesHelpPath: help_page_path('user/packages/index'), currentSettings: project_permissions_settings(project), - canDisableEmails: can_disable_emails?(project, current_user), + canAddCatalogResource: can_add_catalog_resource?(project), canChangeVisibilityLevel: can_change_visibility_level?(project, current_user), + canDisableEmails: can_disable_emails?(project, current_user), allowedVisibilityOptions: project_allowed_visibility_levels(project), visibilityHelpPath: help_page_path('user/public_access'), registryAvailable: Gitlab.config.registry.enabled, diff --git a/app/policies/audit_events/definition_policy.rb b/app/policies/audit_events/definition_policy.rb new file mode 100644 index 00000000000..4109c59fb77 --- /dev/null +++ b/app/policies/audit_events/definition_policy.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +module AuditEvents + class DefinitionPolicy < ::BasePolicy + condition(:read_audit_events_definitions_enabled) do + true + end + + rule { read_audit_events_definitions_enabled }.enable :audit_event_definitions + end +end diff --git a/app/views/layouts/header/_current_user_dropdown.html.haml b/app/views/layouts/header/_current_user_dropdown.html.haml index 1739dee1511..65dbafc19da 100644 --- a/app/views/layouts/header/_current_user_dropdown.html.haml +++ b/app/views/layouts/header/_current_user_dropdown.html.haml @@ -42,9 +42,8 @@ %li.d-md-none = link_to _("Switch to GitLab Next"), Gitlab::Saas.canary_toggle_com_url, data: { track_action: "click_link", track_label: "switch_to_canary", track_property: "navigation_top" } - - if Feature.enabled?(:super_sidebar_nav, current_user) - %li.divider - .js-new-nav-toggle{ data: { enabled: show_super_sidebar?.to_s, endpoint: profile_preferences_url} } + %li.divider + .js-new-nav-toggle{ data: { enabled: show_super_sidebar?.to_s, endpoint: profile_preferences_url} } - if current_user_menu?(:sign_out) %li.divider |