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>2023-06-05 21:09:44 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2023-06-05 21:09:44 +0300
commit2f1a81fd16ff9968d6b986f8a407d963bc2218f9 (patch)
treed079c1abc2bc282e749a676651c0f02d288874f3 /app
parent18e9429b63f9a095b1ba3606856537b9ca291eac (diff)
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app')
-rw-r--r--app/assets/javascripts/diffs/components/tree_list.vue4
-rw-r--r--app/assets/javascripts/environments/components/kubernetes_overview.vue20
-rw-r--r--app/assets/javascripts/environments/components/kubernetes_pods.vue24
-rw-r--r--app/assets/javascripts/environments/components/kubernetes_status_bar.vue39
-rw-r--r--app/assets/javascripts/environments/components/kubernetes_summary.vue19
-rw-r--r--app/assets/javascripts/environments/components/kubernetes_tabs.vue7
-rw-r--r--app/assets/javascripts/environments/constants.js19
-rw-r--r--app/assets/javascripts/pages/projects/shared/permissions/components/ci_catalog_settings.vue165
-rw-r--r--app/assets/javascripts/pages/projects/shared/permissions/components/settings_panel.vue17
-rw-r--r--app/assets/javascripts/pages/projects/shared/permissions/graphql/mutations/catalog_resources_create.mutation.graphql5
-rw-r--r--app/assets/javascripts/pages/projects/shared/permissions/graphql/queries/get_ci_catalog_settings.query.graphql6
-rw-r--r--app/assets/javascripts/pages/projects/shared/permissions/index.js11
-rw-r--r--app/assets/javascripts/pipelines/components/pipeline_details_header.vue6
-rw-r--r--app/assets/javascripts/pipelines/components/pipelines_list/time_ago.vue27
-rw-r--r--app/assets/javascripts/pipelines/graphql/queries/get_pipeline_header_data.query.graphql1
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/deployment/deployment_action_button.vue69
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/deployment/deployment_actions.vue34
-rw-r--r--app/assets/javascripts/whats_new/components/app.vue5
-rw-r--r--app/assets/stylesheets/components/whats_new.scss1
-rw-r--r--app/assets/stylesheets/page_bundles/merge_requests.scss4
-rw-r--r--app/controllers/projects/merge_requests_controller.rb1
-rw-r--r--app/controllers/projects_controller.rb1
-rw-r--r--app/graphql/resolvers/audit_events/audit_event_definitions_resolver.rb13
-rw-r--r--app/graphql/types/audit_events/definition_type.rb50
-rw-r--r--app/graphql/types/query_type.rb6
-rw-r--r--app/helpers/ci/catalog/resources_helper.rb4
-rw-r--r--app/helpers/nav_helper.rb2
-rw-r--r--app/helpers/projects_helper.rb3
-rw-r--r--app/policies/audit_events/definition_policy.rb11
-rw-r--r--app/views/layouts/header/_current_user_dropdown.html.haml5
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