Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
18e9429b63
commit
2f1a81fd16
|
|
@ -103,7 +103,6 @@ proper-names:
|
|||
"OAuth",
|
||||
"OAuth 2",
|
||||
"OmniAuth",
|
||||
"Omnibus GitLab",
|
||||
"OpenID",
|
||||
"OpenShift",
|
||||
"PgBouncer",
|
||||
|
|
|
|||
|
|
@ -1065,7 +1065,6 @@ Layout/ArgumentAlignment:
|
|||
- 'ee/spec/features/groups/settings/protected_environments_spec.rb'
|
||||
- 'ee/spec/features/groups/usage_quotas/pipelines_tab_spec.rb'
|
||||
- 'ee/spec/features/markdown/metrics_spec.rb'
|
||||
- 'ee/spec/features/merge_request/user_merges_with_namespace_storage_limits_spec.rb'
|
||||
- 'ee/spec/features/merge_request/user_sees_approval_widget_spec.rb'
|
||||
- 'ee/spec/features/namespace_user_cap_reached_alert_spec.rb'
|
||||
- 'ee/spec/features/projects/environments/environment_spec.rb'
|
||||
|
|
@ -1665,30 +1664,6 @@ Layout/ArgumentAlignment:
|
|||
- 'qa/qa/specs/features/ee/browser_ui/11_fulfillment/purchase/user_registration_billing_spec.rb'
|
||||
- 'qa/qa/specs/features/ee/browser_ui/11_fulfillment/saas_user_limit_experience_spec.rb'
|
||||
- 'qa/qa/specs/features/ee/browser_ui/11_fulfillment/utilization/free_namespace_storage_spec.rb'
|
||||
- 'qa/qa/specs/features/ee/browser_ui/12_systems/geo/database_delete_replication_spec.rb'
|
||||
- 'qa/qa/specs/features/ee/browser_ui/12_systems/geo/geo_replication_ci_job_log_artifacts_spec.rb'
|
||||
- 'qa/qa/specs/features/ee/browser_ui/12_systems/geo/geo_replication_maven_package_spec.rb'
|
||||
- 'qa/qa/specs/features/ee/browser_ui/12_systems/geo/geo_replication_npm_registry_spec.rb'
|
||||
- 'qa/qa/specs/features/ee/browser_ui/12_systems/geo/geo_replication_project_snippets_spec.rb'
|
||||
- 'qa/qa/specs/features/ee/browser_ui/12_systems/geo/http_push_spec.rb'
|
||||
- 'qa/qa/specs/features/ee/browser_ui/12_systems/geo/http_push_to_secondary_spec.rb'
|
||||
- 'qa/qa/specs/features/ee/browser_ui/12_systems/geo/ssh_push_spec.rb'
|
||||
- 'qa/qa/specs/features/ee/browser_ui/12_systems/geo/ssh_push_to_secondary_spec.rb'
|
||||
- 'qa/qa/specs/features/ee/browser_ui/12_systems/geo/wiki_http_push_spec.rb'
|
||||
- 'qa/qa/specs/features/ee/browser_ui/12_systems/geo/wiki_http_push_to_secondary_spec.rb'
|
||||
- 'qa/qa/specs/features/ee/browser_ui/12_systems/geo/wiki_ssh_push_spec.rb'
|
||||
- 'qa/qa/specs/features/ee/browser_ui/12_systems/geo/wiki_ssh_push_to_secondary_spec.rb'
|
||||
- 'qa/qa/specs/features/ee/browser_ui/13_secure/merge_request_license_widget_spec.rb'
|
||||
- 'qa/qa/specs/features/ee/browser_ui/15_growth/free_trial_spec.rb'
|
||||
- 'qa/qa/specs/features/ee/browser_ui/1_manage/group/group_saml_enforced_sso_git_access_spec.rb'
|
||||
- 'qa/qa/specs/features/ee/browser_ui/1_manage/group/group_saml_non_enforced_sso_spec.rb'
|
||||
- 'qa/qa/specs/features/ee/browser_ui/1_manage/group/restrict_by_ip_address_spec.rb'
|
||||
- 'qa/qa/specs/features/ee/browser_ui/3_create/group_wiki/delete_group_wiki_page_spec.rb'
|
||||
- 'qa/qa/specs/features/ee/browser_ui/3_create/repository/file_locking_spec.rb'
|
||||
- 'qa/qa/specs/features/ee/browser_ui/3_create/repository/project_templates_spec.rb'
|
||||
- 'qa/qa/specs/features/ee/browser_ui/3_create/repository/push_rules_spec.rb'
|
||||
- 'qa/qa/specs/features/ee/browser_ui/4_verify/job_trace_archival_spec.rb'
|
||||
- 'qa/qa/specs/features/ee/browser_ui/5_package/dependency_proxy_sso_spec.rb'
|
||||
- 'qa/qa/vendor/jira/jira_api.rb'
|
||||
- 'qa/spec/support/loglinking_spec.rb'
|
||||
- 'rubocop/cop/gitlab/finder_with_find_by.rb'
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
},
|
||||
},
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
@ -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'),
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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';
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
@ -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]" />
|
||||
|
|
|
|||
|
|
@ -0,0 +1,5 @@
|
|||
mutation catalogResourcesCreate($input: CatalogResourcesCreateInput!) {
|
||||
catalogResourcesCreate(input: $input) {
|
||||
errors
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
query getCiCatalogSettings($fullPath: ID!) {
|
||||
project(fullPath: $fullPath) {
|
||||
id
|
||||
isCatalogResource
|
||||
}
|
||||
}
|
||||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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">
|
||||
|
|
|
|||
|
|
@ -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 }}
|
||||
|
|
|
|||
|
|
@ -35,6 +35,7 @@ query getPipelineHeaderData($fullPath: ID!, $iid: ID!) {
|
|||
commit {
|
||||
id
|
||||
shortId
|
||||
title
|
||||
webPath
|
||||
}
|
||||
finishedAt
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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]
|
||||
|
|
|
|||
|
|
@ -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?)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -1,8 +0,0 @@
|
|||
---
|
||||
name: super_sidebar_nav
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/101910
|
||||
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/381456
|
||||
milestone: '15.7'
|
||||
type: development
|
||||
group: group::foundations
|
||||
default_enabled: true
|
||||
|
|
@ -18,7 +18,7 @@ if Rails.env.production?
|
|||
else
|
||||
ActiveSupport::Deprecation.silenced = false
|
||||
ActiveSupport::Deprecation.behavior = [:stderr, :notify]
|
||||
ActiveSupport::Deprecation.disallowed_behavior = :raise
|
||||
ActiveSupport::Deprecation.disallowed_behavior = [:stderr, :raise]
|
||||
|
||||
rails7_deprecation_warnings = [
|
||||
# https://gitlab.com/gitlab-org/gitlab/-/issues/366910
|
||||
|
|
|
|||
|
|
@ -33,6 +33,8 @@ swap:
|
|||
n/a: "not applicable"
|
||||
navigate to: "go to"
|
||||
OAuth2: "OAuth 2.0"
|
||||
omnibus gitlab: "Linux package"
|
||||
'omnibus(?!\))': "Linux package"
|
||||
once that: "after that"
|
||||
once the: "after the"
|
||||
once you: "after you"
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ swap:
|
|||
GitLabber: GitLab team member
|
||||
GitLabbers: GitLab team members
|
||||
GitLab-shell: GitLab Shell
|
||||
gitlab omnibus: Omnibus GitLab
|
||||
gitlab omnibus: Linux package
|
||||
param: parameter
|
||||
params: parameters
|
||||
pg: PostgreSQL
|
||||
|
|
|
|||
|
|
@ -57,6 +57,16 @@ four standard [pagination arguments](#connection-pagination-arguments):
|
|||
| <a id="queryaimessagesrequestids"></a>`requestIds` | [`[ID!]`](#id) | Array of request IDs to fetch. |
|
||||
| <a id="queryaimessagesroles"></a>`roles` | [`[AiCachedMessageRole!]`](#aicachedmessagerole) | Array of roles to fetch. |
|
||||
|
||||
### `Query.auditEventDefinitions`
|
||||
|
||||
Definitions for all audit events available on the instance.
|
||||
|
||||
Returns [`AuditEventDefinitionConnection!`](#auditeventdefinitionconnection).
|
||||
|
||||
This field returns a [connection](#connections). It accepts the
|
||||
four standard [pagination arguments](#connection-pagination-arguments):
|
||||
`before: String`, `after: String`, `first: Int`, `last: Int`.
|
||||
|
||||
### `Query.boardList`
|
||||
|
||||
Find an issue board list.
|
||||
|
|
@ -7411,6 +7421,29 @@ The edge type for [`ApprovalProjectRule`](#approvalprojectrule).
|
|||
| <a id="approvalprojectruleedgecursor"></a>`cursor` | [`String!`](#string) | A cursor for use in pagination. |
|
||||
| <a id="approvalprojectruleedgenode"></a>`node` | [`ApprovalProjectRule`](#approvalprojectrule) | The item at the end of the edge. |
|
||||
|
||||
#### `AuditEventDefinitionConnection`
|
||||
|
||||
The connection type for [`AuditEventDefinition`](#auditeventdefinition).
|
||||
|
||||
##### Fields
|
||||
|
||||
| Name | Type | Description |
|
||||
| ---- | ---- | ----------- |
|
||||
| <a id="auditeventdefinitionconnectionedges"></a>`edges` | [`[AuditEventDefinitionEdge]`](#auditeventdefinitionedge) | A list of edges. |
|
||||
| <a id="auditeventdefinitionconnectionnodes"></a>`nodes` | [`[AuditEventDefinition]`](#auditeventdefinition) | A list of nodes. |
|
||||
| <a id="auditeventdefinitionconnectionpageinfo"></a>`pageInfo` | [`PageInfo!`](#pageinfo) | Information to aid in pagination. |
|
||||
|
||||
#### `AuditEventDefinitionEdge`
|
||||
|
||||
The edge type for [`AuditEventDefinition`](#auditeventdefinition).
|
||||
|
||||
##### Fields
|
||||
|
||||
| Name | Type | Description |
|
||||
| ---- | ---- | ----------- |
|
||||
| <a id="auditeventdefinitionedgecursor"></a>`cursor` | [`String!`](#string) | A cursor for use in pagination. |
|
||||
| <a id="auditeventdefinitionedgenode"></a>`node` | [`AuditEventDefinition`](#auditeventdefinition) | The item at the end of the edge. |
|
||||
|
||||
#### `AuditEventStreamingHeaderConnection`
|
||||
|
||||
The connection type for [`AuditEventStreamingHeader`](#auditeventstreamingheader).
|
||||
|
|
@ -12073,6 +12106,23 @@ Represents a vulnerability asset type.
|
|||
| <a id="assettypetype"></a>`type` | [`String!`](#string) | Type of the asset. |
|
||||
| <a id="assettypeurl"></a>`url` | [`String!`](#string) | URL of the asset. |
|
||||
|
||||
### `AuditEventDefinition`
|
||||
|
||||
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.
|
||||
|
||||
#### Fields
|
||||
|
||||
| Name | Type | Description |
|
||||
| ---- | ---- | ----------- |
|
||||
| <a id="auditeventdefinitiondescription"></a>`description` | [`String!`](#string) | Description of what action the audit event tracks. |
|
||||
| <a id="auditeventdefinitionfeaturecategory"></a>`featureCategory` | [`String!`](#string) | Feature category associated with the event. |
|
||||
| <a id="auditeventdefinitionintroducedbyissue"></a>`introducedByIssue` | [`String`](#string) | Link to the issue introducing the event. For olderaudit events, it can be a commit URL rather than amerge request URL. |
|
||||
| <a id="auditeventdefinitionintroducedbymr"></a>`introducedByMr` | [`String`](#string) | Link to the merge request introducing the event. Forolder audit events, it can be a commit URL rather thana merge request URL. |
|
||||
| <a id="auditeventdefinitionmilestone"></a>`milestone` | [`String!`](#string) | Milestone the event was introduced in. |
|
||||
| <a id="auditeventdefinitionname"></a>`name` | [`String!`](#string) | Key name of the audit event. |
|
||||
| <a id="auditeventdefinitionsavedtodatabase"></a>`savedToDatabase` | [`Boolean!`](#boolean) | Indicates if the event is saved to PostgreSQL database. |
|
||||
| <a id="auditeventdefinitionstreamed"></a>`streamed` | [`Boolean!`](#boolean) | Indicates if the event is streamed to an external destination. |
|
||||
|
||||
### `AuditEventStreamingHeader`
|
||||
|
||||
Represents a HTTP header key/value that belongs to an audit streaming destination.
|
||||
|
|
@ -13244,6 +13294,35 @@ Returns [`CommitParentNames`](#commitparentnames).
|
|||
| ---- | ---- | ----------- |
|
||||
| <a id="commitreferencestippingtagslimit"></a>`limit` | [`Int!`](#int) | Number of ref names to return. |
|
||||
|
||||
### `ComparedSecurityReport`
|
||||
|
||||
Represents compared security report.
|
||||
|
||||
#### Fields
|
||||
|
||||
| Name | Type | Description |
|
||||
| ---- | ---- | ----------- |
|
||||
| <a id="comparedsecurityreportadded"></a>`added` **{warning-solid}** | [`[ComparedSecurityReportFinding!]`](#comparedsecurityreportfinding) | **Introduced** in 16.1. This feature is an Experiment. It can be changed or removed at any time. New vulnerability findings. |
|
||||
| <a id="comparedsecurityreportbasereportcreatedat"></a>`baseReportCreatedAt` | [`Time`](#time) | Time of the base report creation. |
|
||||
| <a id="comparedsecurityreportbasereportoutofdate"></a>`baseReportOutOfDate` | [`Boolean`](#boolean) | Indicates whether the base report out of date. |
|
||||
| <a id="comparedsecurityreportfixed"></a>`fixed` **{warning-solid}** | [`[ComparedSecurityReportFinding!]`](#comparedsecurityreportfinding) | **Introduced** in 16.1. This feature is an Experiment. It can be changed or removed at any time. Fixed vulnerability findings. |
|
||||
| <a id="comparedsecurityreportheadreportcreatedat"></a>`headReportCreatedAt` | [`Time`](#time) | Time of the base report creation. |
|
||||
|
||||
### `ComparedSecurityReportFinding`
|
||||
|
||||
Represents finding.
|
||||
|
||||
#### Fields
|
||||
|
||||
| Name | Type | Description |
|
||||
| ---- | ---- | ----------- |
|
||||
| <a id="comparedsecurityreportfindingdescription"></a>`description` | [`String`](#string) | Description of the vulnerability finding. |
|
||||
| <a id="comparedsecurityreportfindingfoundbypipelineiid"></a>`foundByPipelineIid` | [`String`](#string) | IID of the pipeline. |
|
||||
| <a id="comparedsecurityreportfindingseverity"></a>`severity` | [`VulnerabilitySeverity`](#vulnerabilityseverity) | Severity of the vulnerability finding. |
|
||||
| <a id="comparedsecurityreportfindingstate"></a>`state` | [`VulnerabilityState`](#vulnerabilitystate) | Finding status. |
|
||||
| <a id="comparedsecurityreportfindingtitle"></a>`title` | [`String`](#string) | Title of the vulnerability finding. |
|
||||
| <a id="comparedsecurityreportfindinguuid"></a>`uuid` | [`String`](#string) | UUIDv5 digest based on the vulnerability's report type, primary identifier, location, fingerprint, project identifier. |
|
||||
|
||||
### `ComplianceFramework`
|
||||
|
||||
Represents a ComplianceFramework associated with a Project.
|
||||
|
|
@ -15054,6 +15133,18 @@ Describes an external status check.
|
|||
| <a id="fileuploadpath"></a>`path` | [`String!`](#string) | Path of the upload. |
|
||||
| <a id="fileuploadsize"></a>`size` | [`Int!`](#int) | Size of the upload in bytes. |
|
||||
|
||||
### `FindingReportsComparer`
|
||||
|
||||
Represents security reports comparison for vulnerability findings.
|
||||
|
||||
#### Fields
|
||||
|
||||
| Name | Type | Description |
|
||||
| ---- | ---- | ----------- |
|
||||
| <a id="findingreportscomparerreport"></a>`report` **{warning-solid}** | [`ComparedSecurityReport`](#comparedsecurityreport) | **Introduced** in 16.1. This feature is an Experiment. It can be changed or removed at any time. Compared security report. |
|
||||
| <a id="findingreportscomparerstatus"></a>`status` | [`FindingReportsComparerStatus`](#findingreportscomparerstatus) | Comparison status. |
|
||||
| <a id="findingreportscomparerstatusreason"></a>`statusReason` | [`String`](#string) | Text explaining the status. |
|
||||
|
||||
### `Forecast`
|
||||
|
||||
Information about specific forecast created.
|
||||
|
|
@ -17314,6 +17405,22 @@ Returns [`[DiffStats!]`](#diffstats).
|
|||
| ---- | ---- | ----------- |
|
||||
| <a id="mergerequestdiffstatspath"></a>`path` | [`String`](#string) | Specific file path. |
|
||||
|
||||
##### `MergeRequest.findingReportsComparer`
|
||||
|
||||
Vulnerability finding reports comparison reported on the merge request.
|
||||
|
||||
WARNING:
|
||||
**Introduced** in 16.1.
|
||||
This feature is an Experiment. It can be changed or removed at any time.
|
||||
|
||||
Returns [`FindingReportsComparer`](#findingreportscomparer).
|
||||
|
||||
###### Arguments
|
||||
|
||||
| Name | Type | Description |
|
||||
| ---- | ---- | ----------- |
|
||||
| <a id="mergerequestfindingreportscomparerreporttype"></a>`reportType` | [`ComparableSecurityReportType!`](#comparablesecurityreporttype) | Filter vulnerability findings by report type. |
|
||||
|
||||
##### `MergeRequest.pipelines`
|
||||
|
||||
Pipelines for the merge request. Note: for performance reasons, no more than the most recent 500 pipelines will be returned.
|
||||
|
|
@ -24450,6 +24557,20 @@ Mode of a commit action.
|
|||
| <a id="commitencodingbase64"></a>`BASE64` | Base64 encoding. |
|
||||
| <a id="commitencodingtext"></a>`TEXT` | Text encoding. |
|
||||
|
||||
### `ComparableSecurityReportType`
|
||||
|
||||
Comparable security report type.
|
||||
|
||||
| Value | Description |
|
||||
| ----- | ----------- |
|
||||
| <a id="comparablesecurityreporttypeapi_fuzzing"></a>`API_FUZZING` | API Fuzzing report. |
|
||||
| <a id="comparablesecurityreporttypecontainer_scanning"></a>`CONTAINER_SCANNING` | Container Scanning report. |
|
||||
| <a id="comparablesecurityreporttypecoverage_fuzzing"></a>`COVERAGE_FUZZING` | Coverage Fuzzing report. |
|
||||
| <a id="comparablesecurityreporttypedast"></a>`DAST` | DAST report. |
|
||||
| <a id="comparablesecurityreporttypedependency_scanning"></a>`DEPENDENCY_SCANNING` | Dependency Scanning report. |
|
||||
| <a id="comparablesecurityreporttypesast"></a>`SAST` | SAST report. |
|
||||
| <a id="comparablesecurityreporttypesecret_detection"></a>`SECRET_DETECTION` | Secret Detection report. |
|
||||
|
||||
### `ComplianceFrameworkPresenceFilter`
|
||||
|
||||
ComplianceFramework of a project for filtering.
|
||||
|
|
@ -24965,6 +25086,16 @@ Event action.
|
|||
| <a id="eventactionreopened"></a>`REOPENED` | Reopened action. |
|
||||
| <a id="eventactionupdated"></a>`UPDATED` | Updated action. |
|
||||
|
||||
### `FindingReportsComparerStatus`
|
||||
|
||||
Report comparison status.
|
||||
|
||||
| Value | Description |
|
||||
| ----- | ----------- |
|
||||
| <a id="findingreportscomparerstatuserror"></a>`ERROR` | An error happened while generating the report. |
|
||||
| <a id="findingreportscomparerstatusparsed"></a>`PARSED` | Report is generated. |
|
||||
| <a id="findingreportscomparerstatusparsing"></a>`PARSING` | Report is being generated. |
|
||||
|
||||
### `ForecastStatus`
|
||||
|
||||
List of statuses for forecasting model.
|
||||
|
|
|
|||
|
|
@ -190,13 +190,12 @@ After components are added to a components repository, they can immediately be [
|
|||
However, this repository is not discoverable. You must mark this project as a catalog resource to allow it to be visible in the CI Catalog
|
||||
so other users can discover it.
|
||||
|
||||
To mark a project as a catalog resource, run the following [graphQL](../../api/graphql/index.md)
|
||||
mutation:
|
||||
To mark a project as a catalog resource:
|
||||
|
||||
```graphql
|
||||
mutation {
|
||||
catalogResourcesCreate(input: { projectPath: "path-to-project"}) {
|
||||
errors
|
||||
}
|
||||
}
|
||||
```
|
||||
1. On the top bar, select **Main menu > Projects** and find your project.
|
||||
1. On the left sidebar, select **Settings > General**.
|
||||
1. Expand **Visibility, project features, permissions**.
|
||||
1. Scroll down to **CI/CD Catalog resource** and select the toggle to mark the project as a catalog resource.
|
||||
|
||||
NOTE:
|
||||
This action is not reversible.
|
||||
|
|
|
|||
|
|
@ -180,7 +180,7 @@ sentry_dsn = "X"
|
|||
MachineOptions = [
|
||||
"google-project=PROJECT",
|
||||
"google-disk-size=25",
|
||||
"google-machine-type=n1-standard-1",
|
||||
"google-machine-type=n2d-standard-2",
|
||||
"google-username=core",
|
||||
"google-tags=gitlab-com,srm",
|
||||
"google-use-internal-ip",
|
||||
|
|
|
|||
|
|
@ -68,7 +68,7 @@ All AI features are experimental.
|
|||
1. Enable the specific feature flag for the feature you want to test
|
||||
1. Set the required access token. To receive an access token:
|
||||
1. For Vertex, follow the [instructions below](#configure-gcp-vertex-access).
|
||||
1. For all other providers, create an access request where `@m_gill`, `@wayne`, and `@timzallmann` are the tech stack owners.
|
||||
1. For all other providers, like Anthropic or OpenAI, create an access request where `@m_gill`, `@wayne`, and `@timzallmann` are the tech stack owners.
|
||||
|
||||
### Set up the embedding database
|
||||
|
||||
|
|
@ -84,7 +84,7 @@ For features that use the embedding database, additional setup is needed.
|
|||
1. Run `gdk reconfigure`
|
||||
1. Run database migrations to create the embedding database
|
||||
|
||||
### Setup for GitLab chat
|
||||
### Setup for GitLab documentation chat (legacy chat)
|
||||
|
||||
To populate the embedding database for GitLab chat:
|
||||
|
||||
|
|
|
|||
|
|
@ -28,12 +28,23 @@ The reviewer can:
|
|||
- Give you a second opinion on the chosen solution and implementation.
|
||||
- Help look for bugs, logic problems, or uncovered edge cases.
|
||||
|
||||
If the merge request is trivial to review (for example, fixing a typo or a tiny refactor that doesn't change the behavior or any data),
|
||||
you can skip the reviewer step and directly ask a [maintainer](https://about.gitlab.com/handbook/engineering/workflow/code-review/#maintainer).
|
||||
Otherwise, a merge request should always be first reviewed by a reviewer in each
|
||||
[category (e.g. backend, database)](#approval-guidelines)
|
||||
the MR touches, as maintainers may not have the relevant domain knowledge, and
|
||||
also to spread the workload.
|
||||
If the merge request is small and straightforward to review, you can skip the reviewer step and
|
||||
directly ask a
|
||||
[maintainer](https://about.gitlab.com/handbook/engineering/workflow/code-review/#maintainer).
|
||||
|
||||
What constitutes "small and straightforward" is a gray area. Here are
|
||||
some examples of small and straightforward changes:
|
||||
|
||||
- Fixing a typo or making small copy changes ([example](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/121337#note_1399406719)).
|
||||
- A tiny refactor that doesn't change any behavior or data.
|
||||
- Removing references to a feature flag that has been default enabled for > 1 month.
|
||||
- Removing unused methods or classes.
|
||||
- A well-understood logic change that requires changes to < 5 lines of code.
|
||||
|
||||
Otherwise, a merge request should be first reviewed by a reviewer in each
|
||||
[category (for example: backend, database)](#approval-guidelines)
|
||||
the MR touches, as maintainers may not have the relevant domain knowledge. This
|
||||
also helps to spread the workload.
|
||||
|
||||
For assistance with security scans or comments, include the Application Security Team (`@gitlab-com/gl-security/appsec`).
|
||||
|
||||
|
|
@ -51,7 +62,9 @@ Some domain areas (like `Verify`) require an approval from a domain expert, base
|
|||
CODEOWNERS rules. Because CODEOWNERS sections are independent approval rules, we could have certain
|
||||
rules (for example `Verify`) that may be a subset of other more generic approval rules (for example `backend`).
|
||||
For a more efficient process, authors should look for domain-specific approvals before generic approvals.
|
||||
Domain-specific approvers may also be maintainers, and if so they should review the domain specifics and broader change at the same time and approve once for both roles.
|
||||
Domain-specific approvers may also be maintainers, and if so they should review
|
||||
the domain specifics and broader change at the same time and approve once for
|
||||
both roles.
|
||||
|
||||
Read more about [author responsibilities](#the-responsibility-of-the-merge-request-author) below.
|
||||
|
||||
|
|
@ -180,14 +193,14 @@ by a reviewer before passing it to a maintainer as described in the
|
|||
| A new service to GitLab (Puma, Sidekiq, Gitaly are examples) | [Product manager](https://about.gitlab.com/company/team/). See the [process for adding a service component to GitLab](adding_service_component.md) for details. |
|
||||
| Changes related to authentication or authorization | [Manage:Authentication and Authorization team member](https://about.gitlab.com/company/team/). Check the [code review section on the group page](https://about.gitlab.com/handbook/engineering/development/dev/manage/authentication-and-authorization/#additional-considerations) for more details. Patterns for files known to require review from the team are listed in the in the `Authentication and Authorization` section of the [`CODEOWNERS`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/.gitlab/CODEOWNERS) file, and the team will be listed in the approvers section of all merge requests that modify these files. |
|
||||
|
||||
- (*1*): Specs other than JavaScript specs are considered `~backend` code. Haml markup is considered `~frontend` code. However, Ruby code within Haml templates is considered `~backend` code. When in doubt, request both a frontend and backend review.
|
||||
- (*1*): Specs other than JavaScript specs are considered `~backend` code. Haml markup is considered `~frontend` code. However, Ruby code in Haml templates is considered `~backend` code. When in doubt, request both a frontend and backend review.
|
||||
- (*2*): We encourage you to seek guidance from a database maintainer if your merge
|
||||
request is potentially introducing expensive queries. It is most efficient to comment
|
||||
on the line of code in question with the SQL queries so they can give their advice.
|
||||
- (*3*): User-facing changes include both visual changes (regardless of how minor),
|
||||
and changes to the rendered DOM which impact how a screen reader may announce
|
||||
the content.
|
||||
- (*4*): End-to-end changes include all files within the `qa` directory.
|
||||
- (*4*): End-to-end changes include all files in the `qa` directory.
|
||||
|
||||
#### Acceptance checklist
|
||||
|
||||
|
|
@ -382,7 +395,7 @@ codebase, and not that of any specific domain, they can review, approve, and mer
|
|||
merge requests from any team and in any product area.
|
||||
|
||||
Maintainers are the DRI of assuring that the acceptance criteria of a merge request are reasonably met.
|
||||
In general, [quality is everyone’s responsibility](https://about.gitlab.com/handbook/engineering/quality/),
|
||||
In general, [quality is everyone's responsibility](https://about.gitlab.com/handbook/engineering/quality/),
|
||||
but maintainers of an MR are held responsible for **ensuring** that an MR meets those general quality standards.
|
||||
|
||||
If a maintainer feels that an MR is substantial enough, or requires a [domain expert](#domain-experts),
|
||||
|
|
|
|||
|
|
@ -15,10 +15,7 @@ the sidebar is a work in progress, and so is this documentation.
|
|||
|
||||
## Enable the new navigation sidebar
|
||||
|
||||
To enable the new navigation sidebar:
|
||||
|
||||
- Enable the `super_sidebar_nav` feature flag.
|
||||
- Select your avatar, then turn on the **New navigation** toggle.
|
||||
To enable the new navigation sidebar, select your avatar, then turn on the **New navigation** toggle.
|
||||
|
||||
## Adding items to the sidebar
|
||||
|
||||
|
|
|
|||
|
|
@ -481,6 +481,50 @@ for how it works.
|
|||
committed. More context can be found at:
|
||||
[Setting it to `false` to skip it](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/118938#note_1374688877)
|
||||
|
||||
##### Why do we have both the mirror project and validation project?
|
||||
|
||||
We have separate projects for a several reasons.
|
||||
|
||||
- **Security**: Previously, we had the mirror project only. However, to fully
|
||||
mitigate a [security issue](https://gitlab.com/gitlab-org/gitlab/-/issues/369898),
|
||||
we had to make the mirror project private.
|
||||
- **Isolation**: We want to run JH code in a completely isolated and standalone project.
|
||||
We should not run it under the `gitlab-org` group, which is where the mirror
|
||||
project is. The validation project is completely isolated.
|
||||
- **Cost**: We don't want to connect to JiHuLab.com from each merge request.
|
||||
It is more cost effective to mirror the code from JiHuLab.com to
|
||||
somewhere at GitLab.com, and have our merge requests fetch code from there.
|
||||
This means that the validation project can fetch code from the mirror, rather
|
||||
than from JiHuLab.com. The mirror project will periodically fetch from
|
||||
JiHuLab.com.
|
||||
- **Branch separation/security/efficiency**: We want to mirror all branches,
|
||||
so that we can fetch the corresponding JH branch from JiHuLab.com. However,
|
||||
we don't want to overwrite the `as-if-jh-code-sync` branch in the validation project,
|
||||
because we use it to control the validation pipeline and it has access to
|
||||
`AS_IF_JH_TOKEN`. However, we cannot mirror all branches except a single
|
||||
one. See [this issue](https://gitlab.com/gitlab-org/gitlab/-/issues/413032) for details.
|
||||
|
||||
Given this issue, the validation project is set to only mirror `master` and
|
||||
`main-jh`. Technically, we don't even need those branches, but we do want to
|
||||
keep the repository up-to-date with all the default branches so that when
|
||||
we push changes from the merge request, we only need to push changes from
|
||||
the merge request, which can be more efficient.
|
||||
|
||||
- Separation of concerns:
|
||||
- Validation project only has the following branches:
|
||||
- `master` and `main-jh` to keep changes up-to-date.
|
||||
- `as-if-jh-code-sync` for dependency synchronization.
|
||||
We should never mirror this.
|
||||
- `as-if-jh/*` branches from the merge requests.
|
||||
We should never mirror these.
|
||||
- All branches from the mirror project are all coming from JiHuLab.com.
|
||||
We never push anything to the mirror project, nor does it run any
|
||||
pipelines. CI/CD is disabled in the mirror project.
|
||||
|
||||
We can consider merging the two projects to simplify the
|
||||
setup and process, but we need to make sure that all of these reasons
|
||||
are no longer concerns.
|
||||
|
||||
### `rspec:undercoverage` job
|
||||
|
||||
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/74859) in GitLab 14.6.
|
||||
|
|
|
|||
|
|
@ -70,7 +70,7 @@ Bot users for groups are service accounts and do not count as licensed seats.
|
|||
|
||||
You can use the [group access tokens API](../api/group_access_tokens.md) to
|
||||
programmatically take action, such as
|
||||
[rotating a project access token](../api/group_access_tokens.md#rotate-a-group-access-token).
|
||||
[rotating a group access token](../api/group_access_tokens.md#rotate-a-group-access-token).
|
||||
|
||||
## Deploy tokens
|
||||
|
||||
|
|
|
|||
|
|
@ -444,7 +444,7 @@ However, as discussed in [the section about rebasing](#squashing-commits-with-re
|
|||
|
||||
Rebasing could create more work, as every time you rebase, you may need to resolve the same conflicts.
|
||||
Sometimes you can reuse recorded resolutions (`rerere`), but merging is better, because you only have to resolve conflicts once.
|
||||
You can read a more thorough explanation of the tradeoffs between merging and rebasing [here](https://git-scm.com/book/en/v2/Git-Branching-Rebasing#:~:text=Final%20commit%20history-,The,-Perils%20of%20Rebasing).
|
||||
The Git documentation has a thorough explanation of the [tradeoffs between merging and rebasing](https://git-scm.com/book/en/v2/Git-Branching-Rebasing#:~:text=Final%20commit%20history-,The,-Perils%20of%20Rebasing).
|
||||
|
||||
A good way to prevent creating many merge commits is to not frequently merge `main` into the feature branch.
|
||||
Three reasons to merge in `main`:
|
||||
|
|
@ -543,9 +543,17 @@ In GitLab Flow, your can include automated CI tests in your branch or merge requ
|
|||
|
||||
## Working with feature branches
|
||||
|
||||
When creating a feature branch, always branch from an up-to-date `main`.
|
||||
If you know before you start that your work depends on another branch, you can also branch from there.
|
||||
If you need to merge in another branch after starting, explain the reason in the merge commit.
|
||||
If you have not pushed your commits to a shared location yet, you can also incorporate changes by rebasing on `main` or another feature branch.
|
||||
Do not merge from upstream again if your code can work and merge cleanly without doing so.
|
||||
Merging only when needed prevents creating merge commits in your feature branch that later end up littering the `main` history.
|
||||
Some tips for working with feature branches:
|
||||
|
||||
- When you create a feature branch locally, always update your local copy of `main` before
|
||||
branching off from it.
|
||||
- When creating a feature branch, always branch from `main` unless you know your work
|
||||
depends on some other branch. For example, to create `feature-x-update`, branch from
|
||||
`feature-x` instead of `main`.
|
||||
- If you merge in another branch after starting, explain the reason in the merge commit.
|
||||
- If you have not pushed your branch upstream yet, you can still pull in new changes
|
||||
by rebasing your local feature branch against your local copy of its parent branch.
|
||||
- Do not merge recent changes from other branches into your local feature branch if your code
|
||||
can work and merge cleanly without those extra changes. Each time you merge commits into your
|
||||
feature branch, you add a merge commit to your feature branch. These merge commits
|
||||
later end up littering the history in your `main` branch.
|
||||
|
|
|
|||
|
|
@ -42,7 +42,7 @@ export GITLAB_ACTIVATION_CODE=your_activation_code
|
|||
|
||||
If you have a license, you can also import it when you install GitLab.
|
||||
|
||||
- **For installations from source**
|
||||
- For self-compiled installations:
|
||||
- Place the `Gitlab.gitlab-license` file in the `config/` directory.
|
||||
- To specify a custom location and filename for the license, set the
|
||||
`GITLAB_LICENSE_FILE` environment variable with the path to the file:
|
||||
|
|
@ -51,7 +51,7 @@ If you have a license, you can also import it when you install GitLab.
|
|||
export GITLAB_LICENSE_FILE="/path/to/license/file"
|
||||
```
|
||||
|
||||
- **For Omnibus package**
|
||||
- For Linux package installations:
|
||||
- Place the `Gitlab.gitlab-license` file in the `/etc/gitlab/` directory.
|
||||
- To specify a custom location and filename for the license, add this entry to `gitlab.rb`:
|
||||
|
||||
|
|
|
|||
|
|
@ -352,7 +352,7 @@ error, the [max attachment size](#max-attachment-size)
|
|||
is probably larger than the web server's allowed value.
|
||||
|
||||
To increase the max attachment size to 200 MB in a
|
||||
[Omnibus GitLab](https://docs.gitlab.com/omnibus/) install, you may need to
|
||||
[Linux package](https://docs.gitlab.com/omnibus/) install, you may need to
|
||||
add the line below to `/etc/gitlab/gitlab.rb` before increasing the max attachment size:
|
||||
|
||||
```ruby
|
||||
|
|
|
|||
|
|
@ -32,12 +32,12 @@ authorization service.
|
|||
|
||||
Whenever access is granted or denied this is logged in a log file called
|
||||
`external-policy-access-control.log`. Read more about the logs GitLab keeps in
|
||||
the [Omnibus GitLab documentation](https://docs.gitlab.com/omnibus/settings/logs.html).
|
||||
the [Linux package documentation](https://docs.gitlab.com/omnibus/settings/logs.html).
|
||||
|
||||
When using TLS Authentication with a self signed certificate, the CA certificate
|
||||
needs to be trusted by the OpenSSL installation. When using GitLab installed
|
||||
using Omnibus, learn to install a custom CA in the
|
||||
[Omnibus GitLab documentation](https://docs.gitlab.com/omnibus/settings/ssl/index.html).
|
||||
using the Linux package, learn to install a custom CA in the
|
||||
[Linux package documentation](https://docs.gitlab.com/omnibus/settings/ssl/index.html).
|
||||
Alternatively, learn where to install custom certificates by using
|
||||
`openssl version -d`.
|
||||
|
||||
|
|
|
|||
|
|
@ -131,7 +131,7 @@ GitLab. For example:
|
|||
- Set `Gitlab-Bypass-Rate-Limiting` to a value other than `1` on all requests that
|
||||
should be affected by rate limiting.
|
||||
1. Set the environment variable `GITLAB_THROTTLE_BYPASS_HEADER`.
|
||||
- For [Omnibus](https://docs.gitlab.com/omnibus/settings/environment-variables.html),
|
||||
- For [Linux package installations](https://docs.gitlab.com/omnibus/settings/environment-variables.html),
|
||||
set `'GITLAB_THROTTLE_BYPASS_HEADER' => 'Gitlab-Bypass-Rate-Limiting'` in `gitlab_rails['env']`.
|
||||
- For source installations, set `export GITLAB_THROTTLE_BYPASS_HEADER=Gitlab-Bypass-Rate-Limiting`
|
||||
in `/etc/default/gitlab`.
|
||||
|
|
@ -163,7 +163,7 @@ the `GITLAB_THROTTLE_USER_ALLOWLIST` environment variable. If you want
|
|||
users 1, 53 and 217 to bypass the authenticated request rate limiter,
|
||||
the allowlist configuration would be `1,53,217`.
|
||||
|
||||
- For [Omnibus](https://docs.gitlab.com/omnibus/settings/environment-variables.html),
|
||||
- For [Linux package installations](https://docs.gitlab.com/omnibus/settings/environment-variables.html),
|
||||
set `'GITLAB_THROTTLE_USER_ALLOWLIST' => '1,53,217'` in `gitlab_rails['env']`.
|
||||
- For source installations, set `export GITLAB_THROTTLE_USER_ALLOWLIST=1,53,217`
|
||||
in `/etc/default/gitlab`.
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ info: To determine the technical writer assigned to the Stage/Group associated w
|
|||
> - [Introduced](https://gitlab.com/groups/gitlab-org/-/epics/5784) the `ci_access` attribute in GitLab 14.3.
|
||||
> - The ability to authorize groups was [introduced](https://gitlab.com/groups/gitlab-org/-/epics/5784) in GitLab 14.3.
|
||||
> - [Moved](https://gitlab.com/groups/gitlab-org/-/epics/6290) to GitLab Free in 14.5.
|
||||
> - Support for Omnibus installations was [introduced](https://gitlab.com/gitlab-org/omnibus-gitlab/-/merge_requests/5686) in GitLab 14.5.
|
||||
> - Support for Linux package installations was [introduced](https://gitlab.com/gitlab-org/omnibus-gitlab/-/merge_requests/5686) in GitLab 14.5.
|
||||
> - The ability to switch between certificate-based clusters and agents was [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/335089) in GitLab 14.9. The certificate-based cluster context is always called `gitlab-deploy`.
|
||||
> - [Renamed](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/80508) from _CI/CD tunnel_ to _CI/CD workflow_ in GitLab 14.9.
|
||||
|
||||
|
|
|
|||
|
|
@ -468,7 +468,7 @@ and can't be configured on GitLab.com to expire. You can erase job logs
|
|||
|
||||
## GitLab.com at scale
|
||||
|
||||
In addition to the GitLab Enterprise Edition Omnibus install, GitLab.com uses
|
||||
In addition to the GitLab Enterprise Edition Linux package install, GitLab.com uses
|
||||
the following applications and settings to achieve scale. All settings are
|
||||
publicly available, as [Kubernetes configuration](https://gitlab.com/gitlab-com/gl-infra/k8s-workloads/gitlab-com)
|
||||
or [Chef cookbooks](https://gitlab.com/gitlab-cookbooks).
|
||||
|
|
|
|||
|
|
@ -108,7 +108,7 @@ Configure FortiAuthenticator in GitLab. On your GitLab server:
|
|||
|
||||
1. Open the configuration file.
|
||||
|
||||
For Omnibus GitLab:
|
||||
For Linux package installations:
|
||||
|
||||
```shell
|
||||
sudo editor /etc/gitlab/gitlab.rb
|
||||
|
|
@ -123,7 +123,7 @@ Configure FortiAuthenticator in GitLab. On your GitLab server:
|
|||
|
||||
1. Add the provider configuration:
|
||||
|
||||
For Omnibus package:
|
||||
For Linux package installations:
|
||||
|
||||
```ruby
|
||||
gitlab_rails['forti_authenticator_enabled'] = true
|
||||
|
|
@ -145,8 +145,8 @@ Configure FortiAuthenticator in GitLab. On your GitLab server:
|
|||
```
|
||||
|
||||
1. Save the configuration file.
|
||||
1. [Reconfigure](../../../administration/restart_gitlab.md#omnibus-gitlab-reconfigure) (Omnibus GitLab) or
|
||||
[restart](../../../administration/restart_gitlab.md#installations-from-source) (GitLab installed from source).
|
||||
1. [Reconfigure](../../../administration/restart_gitlab.md#omnibus-gitlab-reconfigure) (Linux package) or
|
||||
[restart](../../../administration/restart_gitlab.md#installations-from-source) (self-compiled installations).
|
||||
|
||||
### Enable one-time password using Duo
|
||||
|
||||
|
|
@ -174,7 +174,7 @@ On your GitLab server:
|
|||
|
||||
1. Open the configuration file.
|
||||
|
||||
For Omnibus GitLab:
|
||||
For Linux package installations:
|
||||
|
||||
```shell
|
||||
sudo editor /etc/gitlab/gitlab.rb
|
||||
|
|
@ -189,7 +189,7 @@ On your GitLab server:
|
|||
|
||||
1. Add the provider configuration:
|
||||
|
||||
For Omnibus package:
|
||||
For Linux package installations:
|
||||
|
||||
```ruby
|
||||
gitlab_rails['duo_auth_enabled'] = false
|
||||
|
|
@ -209,7 +209,7 @@ On your GitLab server:
|
|||
```
|
||||
|
||||
1. Save the configuration file.
|
||||
1. For Omnibus GitLab, [reconfigure GitLab](../../../administration/restart_gitlab.md#omnibus-gitlab-reconfigure).
|
||||
1. For Linux package installations, [reconfigure GitLab](../../../administration/restart_gitlab.md#omnibus-gitlab-reconfigure).
|
||||
For installations from source, [restart GitLab](../../../administration/restart_gitlab.md#installations-from-source).
|
||||
|
||||
### Enable one-time password using FortiToken Cloud
|
||||
|
|
@ -233,7 +233,7 @@ Configure FortiToken Cloud in GitLab. On your GitLab server:
|
|||
|
||||
1. Open the configuration file.
|
||||
|
||||
For Omnibus GitLab:
|
||||
For Linux package installations:
|
||||
|
||||
```shell
|
||||
sudo editor /etc/gitlab/gitlab.rb
|
||||
|
|
@ -248,7 +248,7 @@ Configure FortiToken Cloud in GitLab. On your GitLab server:
|
|||
|
||||
1. Add the provider configuration:
|
||||
|
||||
For Omnibus package:
|
||||
For Linux package installations:
|
||||
|
||||
```ruby
|
||||
gitlab_rails['forti_token_cloud_enabled'] = true
|
||||
|
|
@ -266,8 +266,8 @@ Configure FortiToken Cloud in GitLab. On your GitLab server:
|
|||
```
|
||||
|
||||
1. Save the configuration file.
|
||||
1. [Reconfigure](../../../administration/restart_gitlab.md#omnibus-gitlab-reconfigure) (Omnibus GitLab) or
|
||||
[restart](../../../administration/restart_gitlab.md#installations-from-source) (GitLab installed from source).
|
||||
1. [Reconfigure](../../../administration/restart_gitlab.md#omnibus-gitlab-reconfigure) (Linux package) or
|
||||
[restart](../../../administration/restart_gitlab.md#installations-from-source) (self-compiled installations).
|
||||
|
||||
### Set up a WebAuthn device
|
||||
|
||||
|
|
|
|||
|
|
@ -23,6 +23,8 @@ authorization provider, you do not need to choose a password. GitLab
|
|||
|
||||
## Change your password
|
||||
|
||||
> Password reset emails sent to any verified email address [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/16311) in GitLab 16.1.
|
||||
|
||||
You can change your password. GitLab enforces [password requirements](#password-requirements) when you choose your new
|
||||
password.
|
||||
|
||||
|
|
@ -33,8 +35,17 @@ password.
|
|||
1. In the **New password** and **Password confirmation** text box, enter your new password.
|
||||
1. Select **Save password**.
|
||||
|
||||
If you don't know your current password, select the **I forgot my password** link. A password reset email is sent to the
|
||||
account's **primary** email address.
|
||||
If you do not know your current password, select **I forgot my password**
|
||||
and complete the form. A password reset email is sent to the email address you
|
||||
enter into this form, provided that the email address is verified. If you enter an
|
||||
unverified email address into this form, no email is sent, and you see the following
|
||||
message:
|
||||
|
||||
> "If your email address exists in our database, you will receive a password recovery link at your email address in a few minutes."
|
||||
|
||||
NOTE:
|
||||
Your account can have more than one verified email address, and any email address
|
||||
associated with your account can be verified.
|
||||
|
||||
## Password requirements
|
||||
|
||||
|
|
|
|||
|
|
@ -17,9 +17,9 @@ separately configured [Mattermost notifications](mattermost.md).
|
|||
GitLab provides different ways to configure Mattermost slash commands. For any of these options,
|
||||
you must have Mattermost [3.4 or later](https://mattermost.com/blog/category/platform/releases/).
|
||||
|
||||
- **Omnibus GitLab installations**: Mattermost is bundled with
|
||||
[Omnibus GitLab](https://docs.gitlab.com/omnibus/). To configure Mattermost for Omnibus GitLab,
|
||||
read the [Omnibus GitLab Mattermost documentation](../../../integration/mattermost/index.md).
|
||||
- **Linux package installations**: Mattermost is bundled with
|
||||
[Linux package](https://docs.gitlab.com/omnibus/). To configure Mattermost for Linux package
|
||||
installations, read the [Linux package Mattermost documentation](../../../integration/mattermost/index.md).
|
||||
- **If Mattermost is installed on the same server as GitLab**, use the
|
||||
[automated configuration](#configure-automatically).
|
||||
- **For all other installations**, use the [manual configuration](#configure-manually).
|
||||
|
|
@ -133,7 +133,7 @@ The available slash commands for Mattermost are:
|
|||
## Related topics
|
||||
|
||||
- [Mattermost slash commands](https://developers.mattermost.com/integrate/slash-commands/)
|
||||
- [Omnibus GitLab Mattermost](../../../integration/mattermost/index.md)
|
||||
- [Linux package Mattermost](../../../integration/mattermost/index.md)
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
|
|
|
|||
|
|
@ -238,9 +238,10 @@ To configure a custom mailbox for Service Desk with IMAP, add the following snip
|
|||
|
||||
NOTE:
|
||||
In GitLab 15.3 and later, Service Desk uses `webhook` (internal API call) by default instead of enqueuing a Sidekiq job.
|
||||
To use `webhook` on an Omnibus installation running GitLab 15.3, you must generate a secret file.
|
||||
To use `webhook` on a Linux package installation running GitLab 15.3, you must generate a secret file.
|
||||
For more information, see [merge request 5927](https://gitlab.com/gitlab-org/omnibus-gitlab/-/merge_requests/5927).
|
||||
In GitLab 15.4, reconfiguring an Omnibus installation generates this secret file automatically, so no secret file configuration setting is needed.
|
||||
In GitLab 15.4, reconfiguring a Linux package installation generates this secret file automatically, so no
|
||||
secret file configuration setting is needed.
|
||||
For more information, see [issue 1462](https://gitlab.com/gitlab-com/gl-infra/scalability/-/issues/1462).
|
||||
|
||||
```ruby
|
||||
|
|
@ -419,7 +420,7 @@ Service Desk can be configured to read Microsoft Exchange Online mailboxes with
|
|||
Graph API instead of IMAP. Set up an OAuth 2.0 application for Microsoft Graph
|
||||
[the same way as for incoming email](../../administration/incoming_email.md#microsoft-graph).
|
||||
|
||||
- Example for Omnibus GitLab installations:
|
||||
- Example for Linux package installations:
|
||||
|
||||
```ruby
|
||||
gitlab_rails['service_desk_email_enabled'] = true
|
||||
|
|
@ -502,8 +503,8 @@ and the [common settings for `service_desk_email`](https://docs.gitlab.com/chart
|
|||
|
||||
#### Linux package (Omnibus)
|
||||
|
||||
In multi-node Linux package (Omnibus) environments, run `mail_room` only on one node. Run it either on a single
|
||||
`rails` node (for example, [Omnibus role](https://docs.gitlab.com/omnibus/roles/index.html) `application_role`)
|
||||
In multi-node Linux package installation environments, run `mail_room` only on one node. Run it either on a single
|
||||
`rails` node (for example, `application_role`)
|
||||
or completely separately.
|
||||
|
||||
##### Set up all nodes
|
||||
|
|
|
|||
|
|
@ -13,6 +13,10 @@ module Gitlab
|
|||
validate :validate_schema
|
||||
validate :validate_file_name
|
||||
|
||||
def self.declarative_policy_class
|
||||
'AuditEvents::DefinitionPolicy'
|
||||
end
|
||||
|
||||
InvalidAuditEventTypeError = Class.new(StandardError)
|
||||
|
||||
AUDIT_EVENT_TYPE_SCHEMA_PATH = Rails.root.join('config', 'audit_events', 'types', 'type_schema.json')
|
||||
|
|
|
|||
|
|
@ -6,53 +6,51 @@
|
|||
# Read more about this script on this blog post https://about.gitlab.com/2018/10/24/setting-up-gitlab-ci-for-android-projects/, by Jason Lenny
|
||||
# If you are interested in using Android with FastLane for publishing take a look at the Android-Fastlane template.
|
||||
|
||||
image: openjdk:8-jdk
|
||||
image: eclipse-temurin:17-jdk-jammy
|
||||
|
||||
variables:
|
||||
|
||||
# ANDROID_COMPILE_SDK is the version of Android you're compiling with.
|
||||
# It should match compileSdkVersion.
|
||||
ANDROID_COMPILE_SDK: "29"
|
||||
ANDROID_COMPILE_SDK: "33"
|
||||
|
||||
# ANDROID_BUILD_TOOLS is the version of the Android build tools you are using.
|
||||
# It should match buildToolsVersion.
|
||||
ANDROID_BUILD_TOOLS: "29.0.3"
|
||||
ANDROID_BUILD_TOOLS: "33.0.2"
|
||||
|
||||
# It's what version of the command line tools we're going to download from the official site.
|
||||
# Official Site-> https://developer.android.com/studio/index.html
|
||||
# There, look down below at the cli tools only, sdk tools package is of format:
|
||||
# commandlinetools-os_type-ANDROID_SDK_TOOLS_latest.zip
|
||||
# when the script was last modified for latest compileSdkVersion, it was which is written down below
|
||||
ANDROID_SDK_TOOLS: "6514223"
|
||||
ANDROID_SDK_TOOLS: "9477386"
|
||||
|
||||
# Packages installation before running script
|
||||
before_script:
|
||||
- apt-get --quiet update --yes
|
||||
- apt-get --quiet install --yes wget tar unzip lib32stdc++6 lib32z1
|
||||
- apt-get --quiet install --yes wget unzip
|
||||
|
||||
# Setup path as android_home for moving/exporting the downloaded sdk into it
|
||||
- export ANDROID_HOME="${PWD}/android-home"
|
||||
- export ANDROID_HOME="${PWD}/android-sdk-root"
|
||||
# Create a new directory at specified location
|
||||
- install -d $ANDROID_HOME
|
||||
# Here we are installing androidSDK tools from official source,
|
||||
# (the key thing here is the url from where you are downloading these sdk tool for command line, so please do note this url pattern there and here as well)
|
||||
# after that unzipping those tools and
|
||||
# then running a series of SDK manager commands to install necessary android SDK packages that'll allow the app to build
|
||||
- wget --output-document=$ANDROID_HOME/cmdline-tools.zip https://dl.google.com/android/repository/commandlinetools-linux-${ANDROID_SDK_TOOLS}_latest.zip
|
||||
# move to the archive at ANDROID_HOME
|
||||
- pushd $ANDROID_HOME
|
||||
- unzip -d cmdline-tools cmdline-tools.zip
|
||||
- popd
|
||||
- export PATH=$PATH:${ANDROID_HOME}/cmdline-tools/tools/bin/
|
||||
- wget --no-verbose --output-document=$ANDROID_HOME/cmdline-tools.zip https://dl.google.com/android/repository/commandlinetools-linux-${ANDROID_SDK_TOOLS}_latest.zip
|
||||
- unzip -q -d "$ANDROID_HOME/cmdline-tools" "$ANDROID_HOME/cmdline-tools.zip"
|
||||
- mv -T "$ANDROID_HOME/cmdline-tools/cmdline-tools" "$ANDROID_HOME/cmdline-tools/tools"
|
||||
- export PATH=$PATH:$ANDROID_HOME/cmdline-tools/latest/bin:$ANDROID_HOME/cmdline-tools/tools/bin
|
||||
|
||||
# Nothing fancy here, just checking sdkManager version
|
||||
- sdkmanager --version
|
||||
|
||||
# use yes to accept all licenses
|
||||
- yes | sdkmanager --sdk_root=${ANDROID_HOME} --licenses || true
|
||||
- sdkmanager --sdk_root=${ANDROID_HOME} "platforms;android-${ANDROID_COMPILE_SDK}"
|
||||
- sdkmanager --sdk_root=${ANDROID_HOME} "platform-tools"
|
||||
- sdkmanager --sdk_root=${ANDROID_HOME} "build-tools;${ANDROID_BUILD_TOOLS}"
|
||||
- yes | sdkmanager --licenses > /dev/null || true
|
||||
- sdkmanager "platforms;android-${ANDROID_COMPILE_SDK}"
|
||||
- sdkmanager "platform-tools"
|
||||
- sdkmanager "build-tools;${ANDROID_BUILD_TOOLS}"
|
||||
|
||||
# Not necessary, but just for surity
|
||||
- chmod +x ./gradlew
|
||||
|
|
@ -64,6 +62,11 @@ lintDebug:
|
|||
stage: build
|
||||
script:
|
||||
- ./gradlew -Pci --console=plain :app:lintDebug -PbuildDir=lint
|
||||
artifacts:
|
||||
paths:
|
||||
- app/lint/reports/lint-results-debug.html
|
||||
expose_as: "lint-report"
|
||||
when: always
|
||||
|
||||
# Make Project
|
||||
assembleDebug:
|
||||
|
|
@ -77,6 +80,7 @@ assembleDebug:
|
|||
|
||||
# Run all tests, if any fails, interrupt the pipeline(fail it)
|
||||
debugTests:
|
||||
needs: [lintDebug, assembleDebug]
|
||||
interruptible: true
|
||||
stage: test
|
||||
script:
|
||||
|
|
|
|||
|
|
@ -5376,9 +5376,6 @@ msgstr ""
|
|||
msgid "Anonymous"
|
||||
msgstr ""
|
||||
|
||||
msgid "Another action is currently in progress"
|
||||
msgstr ""
|
||||
|
||||
msgid "Another issue tracker is already in use. Only one issue tracker service can be active at a time"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -9577,6 +9574,9 @@ msgstr ""
|
|||
msgid "Ci config already present"
|
||||
msgstr ""
|
||||
|
||||
msgid "CiCatalog|CI/CD Catalog resource"
|
||||
msgstr ""
|
||||
|
||||
msgid "CiCatalog|CI/CD catalog"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -9589,6 +9589,12 @@ msgstr ""
|
|||
msgid "CiCatalog|Learn more"
|
||||
msgstr ""
|
||||
|
||||
msgid "CiCatalog|Mark project as a CI/CD Catalog resource"
|
||||
msgstr ""
|
||||
|
||||
msgid "CiCatalog|Mark project as a CI/CD Catalog resource. %{linkStart}What is the CI/CD Catalog?%{linkEnd}"
|
||||
msgstr ""
|
||||
|
||||
msgid "CiCatalog|Page %{currentPage} of %{totalPage}"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -9598,9 +9604,24 @@ msgstr ""
|
|||
msgid "CiCatalog|Repositories of pipeline components available in this namespace."
|
||||
msgstr ""
|
||||
|
||||
msgid "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."
|
||||
msgstr ""
|
||||
|
||||
msgid "CiCatalog|There was a problem fetching the CI/CD Catalog setting."
|
||||
msgstr ""
|
||||
|
||||
msgid "CiCatalog|There was a problem marking the project as a CI/CD Catalog resource."
|
||||
msgstr ""
|
||||
|
||||
msgid "CiCatalog|There was an error fetching CI/CD Catalog resources."
|
||||
msgstr ""
|
||||
|
||||
msgid "CiCatalog|This project is now a CI/CD Catalog resource."
|
||||
msgstr ""
|
||||
|
||||
msgid "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."
|
||||
msgstr ""
|
||||
|
||||
msgid "CiCatalog|We want to help you create and manage pipeline component repositories, while also making it easier to reuse pipeline configurations. Let us know how we're doing!"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -17400,12 +17421,18 @@ msgstr ""
|
|||
msgid "Environment|Deployments"
|
||||
msgstr ""
|
||||
|
||||
msgid "Environment|Environment health"
|
||||
msgstr ""
|
||||
|
||||
msgid "Environment|External IP"
|
||||
msgstr ""
|
||||
|
||||
msgid "Environment|Failed"
|
||||
msgstr ""
|
||||
|
||||
msgid "Environment|Healthy"
|
||||
msgstr ""
|
||||
|
||||
msgid "Environment|Jobs"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -17439,6 +17466,9 @@ msgstr ""
|
|||
msgid "Environment|Summary"
|
||||
msgstr ""
|
||||
|
||||
msgid "Environment|Unhealthy"
|
||||
msgstr ""
|
||||
|
||||
msgid "Epic"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -33678,9 +33708,6 @@ msgstr ""
|
|||
msgid "Pipeline|Failed"
|
||||
msgstr ""
|
||||
|
||||
msgid "Pipeline|In progress"
|
||||
msgstr ""
|
||||
|
||||
msgid "Pipeline|Manual"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -48932,21 +48959,9 @@ msgstr ""
|
|||
msgid "UsageQuota|User settings > Usage quotas"
|
||||
msgstr ""
|
||||
|
||||
msgid "UsageQuota|When you purchase additional storage, we automatically unlock projects that were locked if the storage limit was reached."
|
||||
msgstr ""
|
||||
|
||||
msgid "UsageQuota|Wiki content."
|
||||
msgstr ""
|
||||
|
||||
msgid "UsageQuota|You have consumed all of your additional storage. Purchase more to unlock projects over the limit."
|
||||
msgstr ""
|
||||
|
||||
msgid "UsageQuota|You have reached the free storage limit on %{projectsLockedText}. To unlock them, purchase additional storage."
|
||||
msgstr ""
|
||||
|
||||
msgid "UsageQuota|Your purchased storage is running low. To avoid locked projects, purchase more storage."
|
||||
msgstr ""
|
||||
|
||||
msgid "UsageTrends|Could not load the issues and merge requests chart. Please refresh the page to try again."
|
||||
msgstr ""
|
||||
|
||||
|
|
|
|||
|
|
@ -33,10 +33,13 @@ module Gitlab
|
|||
# Waits for subscription to be synced and UI to be updated
|
||||
#
|
||||
# @param subscription_plan [String]
|
||||
def wait_for_subscription(subscription_plan, page:)
|
||||
def wait_for_subscription(subscription_plan)
|
||||
::QA::Support::Waiter.wait_until(
|
||||
max_duration: ::QA::Support::Helpers::Zuora::ZUORA_TIMEOUT, sleep_interval: 2, reload_page: page,
|
||||
message: "Subscription plan '#{subscription_plan}' failed to appear") do
|
||||
max_duration: ::QA::Support::Helpers::Zuora::ZUORA_TIMEOUT,
|
||||
sleep_interval: 2,
|
||||
reload_page: Chemlab.configuration.browser.session,
|
||||
message: "Subscription plan '#{subscription_plan}' failed to appear"
|
||||
) do
|
||||
billing_plan_header.match?(/currently using the #{subscription_plan} saas plan/i)
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -82,6 +82,34 @@ module Gitlab
|
|||
|
||||
purchased_usage_total[/(\d+){2}.\d+/].to_f
|
||||
end
|
||||
|
||||
# Waits for additional CI minutes to be available on the page
|
||||
def wait_for_additional_ci_minutes_available
|
||||
::QA::Support::Waiter.wait_until(
|
||||
max_duration: ::QA::Support::Helpers::Zuora::ZUORA_TIMEOUT,
|
||||
sleep_interval: 2,
|
||||
reload_page: Chemlab.configuration.browser.session,
|
||||
message: 'Expected additional CI minutes but they did not appear.'
|
||||
) do
|
||||
additional_ci_minutes_added?
|
||||
end
|
||||
end
|
||||
|
||||
# Waits for additional CI minutes amount to match the expected number of minutes
|
||||
#
|
||||
# @param [String] minutes
|
||||
def wait_for_additional_ci_minute_limits(minutes)
|
||||
wait_for_additional_ci_minutes_available
|
||||
|
||||
::QA::Support::Waiter.wait_until(
|
||||
max_duration: ::QA::Support::Helpers::Zuora::ZUORA_TIMEOUT,
|
||||
sleep_interval: 2,
|
||||
reload_page: Chemlab.configuration.browser.session,
|
||||
message: "Expected additional CI minutes to equal #{minutes}"
|
||||
) do
|
||||
additional_ci_limits == minutes
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -211,7 +211,7 @@ module QA
|
|||
# Click by JS is needed to bypass the Moved MR actions popover
|
||||
# Change back to regular click_element when moved_mr_sidebar FF is removed
|
||||
# Rollout issue: https://gitlab.com/gitlab-org/gitlab/-/issues/385460
|
||||
click_by_javascript(find_element(:edit_title_button))
|
||||
click_by_javascript(find_element(:edit_title_button, skip_finished_loading_check: true))
|
||||
end
|
||||
|
||||
def fast_forward_not_possible?
|
||||
|
|
|
|||
|
|
@ -70,7 +70,6 @@ RSpec.describe 'Commit', feature_category: :source_code_management do
|
|||
context "when super sidebar is enabled" do
|
||||
before do
|
||||
user.update!(use_new_navigation: true)
|
||||
stub_feature_flags(super_sidebar_nav: true)
|
||||
end
|
||||
|
||||
it_behaves_like "single commit view"
|
||||
|
|
|
|||
|
|
@ -120,6 +120,7 @@ RSpec.describe 'Merge request > User sees deployment widget', :js, feature_categ
|
|||
end
|
||||
|
||||
before do
|
||||
stub_feature_flags(review_apps_redeploy_mr_widget: false)
|
||||
build.success!
|
||||
deployment.update!(on_stop: manual.name)
|
||||
visit project_merge_request_path(project, merge_request)
|
||||
|
|
@ -142,5 +143,56 @@ RSpec.describe 'Merge request > User sees deployment widget', :js, feature_categ
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'with stop action with the review_apps_redeploy_mr_widget feature flag turned on' do
|
||||
let(:manual) do
|
||||
create(:ci_build, :manual, pipeline: pipeline,
|
||||
name: 'close_app', environment: environment.name)
|
||||
end
|
||||
|
||||
before do
|
||||
stub_feature_flags(review_apps_redeploy_mr_widget: true)
|
||||
build.success!
|
||||
deployment.update!(on_stop: manual.name)
|
||||
visit project_merge_request_path(project, merge_request)
|
||||
wait_for_requests
|
||||
end
|
||||
|
||||
it 'displays the re-deploy button' do
|
||||
accept_gl_confirm(button_text: 'Stop environment') do
|
||||
find('.js-stop-env').click
|
||||
end
|
||||
|
||||
expect(page).to have_selector('.js-redeploy-action')
|
||||
end
|
||||
|
||||
context 'for reporter' do
|
||||
let(:role) { :reporter }
|
||||
|
||||
it 'does not show stop button' do
|
||||
expect(page).not_to have_selector('.js-stop-env')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'with redeploy action and with the review_apps_redeploy_mr_widget feature flag turned on' do
|
||||
before do
|
||||
stub_feature_flags(review_apps_redeploy_mr_widget: true)
|
||||
build.success!
|
||||
environment.update!(state: 'stopped')
|
||||
visit project_merge_request_path(project, merge_request)
|
||||
wait_for_requests
|
||||
end
|
||||
|
||||
it 'begins redeploying the deployment' do
|
||||
accept_gl_confirm(button_text: 'Re-deploy') do
|
||||
find('.js-redeploy-action').click
|
||||
end
|
||||
|
||||
wait_for_requests
|
||||
|
||||
expect(page).to have_content('Will deploy to')
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -7,75 +7,53 @@ RSpec.describe 'new navigation toggle', :js, feature_category: :navigation do
|
|||
|
||||
before do
|
||||
user.update!(use_new_navigation: user_preference)
|
||||
stub_feature_flags(super_sidebar_nav: new_nav_ff)
|
||||
sign_in(user)
|
||||
visit explore_projects_path
|
||||
end
|
||||
|
||||
context 'with feature flag off' do
|
||||
let(:new_nav_ff) { false }
|
||||
context 'when user has new nav disabled' do
|
||||
let(:user_preference) { false }
|
||||
|
||||
where(:user_preference) do
|
||||
[true, false]
|
||||
it 'allows to enable new nav', :aggregate_failures do
|
||||
within '.js-nav-user-dropdown' do
|
||||
find('a[data-toggle="dropdown"]').click
|
||||
expect(page).to have_content('Navigation redesign')
|
||||
|
||||
toggle = page.find('.gl-toggle:not(.is-checked)')
|
||||
toggle.click # reloads the page
|
||||
end
|
||||
|
||||
wait_for_requests
|
||||
|
||||
expect(user.reload.use_new_navigation).to eq true
|
||||
end
|
||||
|
||||
with_them do
|
||||
it 'shows old topbar user dropdown with no way to toggle to new nav' do
|
||||
within '.js-header-content .js-nav-user-dropdown' do
|
||||
find('a[data-toggle="dropdown"]').click
|
||||
expect(page).not_to have_content('Navigation redesign')
|
||||
end
|
||||
end
|
||||
it 'shows the old navigation' do
|
||||
expect(page).to have_selector('.js-navbar')
|
||||
expect(page).not_to have_selector('[data-testid="super-sidebar"]')
|
||||
end
|
||||
end
|
||||
|
||||
context 'with feature flag on' do
|
||||
let(:new_nav_ff) { true }
|
||||
context 'when user has new nav enabled' do
|
||||
let(:user_preference) { true }
|
||||
|
||||
context 'when user has new nav disabled' do
|
||||
let(:user_preference) { false }
|
||||
it 'allows to disable new nav', :aggregate_failures do
|
||||
within '[data-testid="super-sidebar"] [data-testid="user-dropdown"]' do
|
||||
click_button "#{user.name} user’s menu"
|
||||
expect(page).to have_content('Navigation redesign')
|
||||
|
||||
it 'allows to enable new nav', :aggregate_failures do
|
||||
within '.js-nav-user-dropdown' do
|
||||
find('a[data-toggle="dropdown"]').click
|
||||
expect(page).to have_content('Navigation redesign')
|
||||
|
||||
toggle = page.find('.gl-toggle:not(.is-checked)')
|
||||
toggle.click # reloads the page
|
||||
end
|
||||
|
||||
wait_for_requests
|
||||
|
||||
expect(user.reload.use_new_navigation).to eq true
|
||||
toggle = page.find('.gl-toggle.is-checked')
|
||||
toggle.click # reloads the page
|
||||
end
|
||||
|
||||
it 'shows the old navigation' do
|
||||
expect(page).to have_selector('.js-navbar')
|
||||
expect(page).not_to have_selector('[data-testid="super-sidebar"]')
|
||||
end
|
||||
wait_for_requests
|
||||
|
||||
expect(user.reload.use_new_navigation).to eq false
|
||||
end
|
||||
|
||||
context 'when user has new nav enabled' do
|
||||
let(:user_preference) { true }
|
||||
|
||||
it 'allows to disable new nav', :aggregate_failures do
|
||||
within '[data-testid="super-sidebar"] [data-testid="user-dropdown"]' do
|
||||
click_button "#{user.name} user’s menu"
|
||||
expect(page).to have_content('Navigation redesign')
|
||||
|
||||
toggle = page.find('.gl-toggle.is-checked')
|
||||
toggle.click # reloads the page
|
||||
end
|
||||
|
||||
wait_for_requests
|
||||
|
||||
expect(user.reload.use_new_navigation).to eq false
|
||||
end
|
||||
|
||||
it 'shows the new navigation' do
|
||||
expect(page).not_to have_selector('.js-navbar')
|
||||
expect(page).to have_selector('[data-testid="super-sidebar"]')
|
||||
end
|
||||
it 'shows the new navigation' do
|
||||
expect(page).not_to have_selector('.js-navbar')
|
||||
expect(page).to have_selector('[data-testid="super-sidebar"]')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -189,7 +189,6 @@ RSpec.describe "Compare", :js, feature_category: :groups_and_projects do
|
|||
context "when super sidebar is enabled" do
|
||||
before do
|
||||
user.update!(use_new_navigation: true)
|
||||
stub_feature_flags(super_sidebar_nav: true)
|
||||
end
|
||||
|
||||
it_behaves_like "compare view of branches"
|
||||
|
|
|
|||
|
|
@ -99,7 +99,6 @@ RSpec.describe 'New project', :js, feature_category: :groups_and_projects do
|
|||
context 'when the new navigation is enabled' do
|
||||
before do
|
||||
user.update!(use_new_navigation: true)
|
||||
stub_feature_flags(super_sidebar_nav: true)
|
||||
end
|
||||
|
||||
include_examples '"New project" page'
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import KubernetesOverview from '~/environments/components/kubernetes_overview.vu
|
|||
import KubernetesAgentInfo from '~/environments/components/kubernetes_agent_info.vue';
|
||||
import KubernetesPods from '~/environments/components/kubernetes_pods.vue';
|
||||
import KubernetesTabs from '~/environments/components/kubernetes_tabs.vue';
|
||||
import KubernetesStatusBar from '~/environments/components/kubernetes_status_bar.vue';
|
||||
import { agent, kubernetesNamespace } from './graphql/mock_data';
|
||||
import { mockKasTunnelUrl } from './mock_data';
|
||||
|
||||
|
|
@ -32,6 +33,7 @@ describe('~/environments/components/kubernetes_overview.vue', () => {
|
|||
const findAgentInfo = () => wrapper.findComponent(KubernetesAgentInfo);
|
||||
const findKubernetesPods = () => wrapper.findComponent(KubernetesPods);
|
||||
const findKubernetesTabs = () => wrapper.findComponent(KubernetesTabs);
|
||||
const findKubernetesStatusBar = () => wrapper.findComponent(KubernetesStatusBar);
|
||||
const findAlert = () => wrapper.findComponent(GlAlert);
|
||||
|
||||
const createWrapper = () => {
|
||||
|
|
@ -105,6 +107,49 @@ describe('~/environments/components/kubernetes_overview.vue', () => {
|
|||
configuration,
|
||||
});
|
||||
});
|
||||
|
||||
it('renders kubernetes status bar', () => {
|
||||
expect(findKubernetesStatusBar().exists()).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Kubernetes health status', () => {
|
||||
beforeEach(() => {
|
||||
createWrapper();
|
||||
toggleCollapse();
|
||||
});
|
||||
|
||||
it("doesn't set `clusterHealthStatus` when pods are still loading", async () => {
|
||||
findKubernetesPods().vm.$emit('loading', true);
|
||||
await nextTick();
|
||||
|
||||
expect(findKubernetesStatusBar().props('clusterHealthStatus')).toBe('');
|
||||
});
|
||||
|
||||
it("doesn't set `clusterHealthStatus` when workload types are still loading", async () => {
|
||||
findKubernetesTabs().vm.$emit('loading', true);
|
||||
await nextTick();
|
||||
|
||||
expect(findKubernetesStatusBar().props('clusterHealthStatus')).toBe('');
|
||||
});
|
||||
|
||||
it('sets `clusterHealthStatus` as error when pods emitted a failure', async () => {
|
||||
findKubernetesPods().vm.$emit('failed');
|
||||
await nextTick();
|
||||
|
||||
expect(findKubernetesStatusBar().props('clusterHealthStatus')).toBe('error');
|
||||
});
|
||||
|
||||
it('sets `clusterHealthStatus` as error when workload types emitted a failure', async () => {
|
||||
findKubernetesTabs().vm.$emit('failed');
|
||||
await nextTick();
|
||||
|
||||
expect(findKubernetesStatusBar().props('clusterHealthStatus')).toBe('error');
|
||||
});
|
||||
|
||||
it('sets `clusterHealthStatus` as success when data is loaded and no failures where emitted', () => {
|
||||
expect(findKubernetesStatusBar().props('clusterHealthStatus')).toBe('success');
|
||||
});
|
||||
});
|
||||
|
||||
describe('on cluster error', () => {
|
||||
|
|
|
|||
|
|
@ -50,6 +50,14 @@ describe('~/environments/components/kubernetes_pods.vue', () => {
|
|||
expect(findLoadingIcon().exists()).toBe(true);
|
||||
});
|
||||
|
||||
it('emits loading state', async () => {
|
||||
createWrapper();
|
||||
expect(wrapper.emitted('loading')[0]).toEqual([true]);
|
||||
|
||||
await waitForPromises();
|
||||
expect(wrapper.emitted('loading')[1]).toEqual([false]);
|
||||
});
|
||||
|
||||
it('hides the loading icon when the list of pods loaded', async () => {
|
||||
createWrapper();
|
||||
await waitForPromises();
|
||||
|
|
@ -84,6 +92,13 @@ describe('~/environments/components/kubernetes_pods.vue', () => {
|
|||
});
|
||||
},
|
||||
);
|
||||
|
||||
it('emits a failed event when there are failed pods', async () => {
|
||||
createWrapper();
|
||||
await waitForPromises();
|
||||
|
||||
expect(wrapper.emitted('failed')).toHaveLength(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when gets an error from the cluster_client API', () => {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,42 @@
|
|||
import { shallowMount } from '@vue/test-utils';
|
||||
import { GlLoadingIcon, GlBadge } from '@gitlab/ui';
|
||||
import KubernetesStatusBar from '~/environments/components/kubernetes_status_bar.vue';
|
||||
import {
|
||||
CLUSTER_STATUS_HEALTHY_TEXT,
|
||||
CLUSTER_STATUS_UNHEALTHY_TEXT,
|
||||
} from '~/environments/constants';
|
||||
|
||||
describe('~/environments/components/kubernetes_status_bar.vue', () => {
|
||||
let wrapper;
|
||||
|
||||
const findLoadingIcon = () => wrapper.findComponent(GlLoadingIcon);
|
||||
const findHealthBadge = () => wrapper.findComponent(GlBadge);
|
||||
|
||||
const createWrapper = ({ clusterHealthStatus = '' } = {}) => {
|
||||
wrapper = shallowMount(KubernetesStatusBar, {
|
||||
propsData: { clusterHealthStatus },
|
||||
});
|
||||
};
|
||||
|
||||
describe('health badge', () => {
|
||||
it('shows loading icon when cluster health is not present', () => {
|
||||
createWrapper();
|
||||
|
||||
expect(findLoadingIcon().exists()).toBe(true);
|
||||
});
|
||||
|
||||
it.each([
|
||||
['success', 'success', CLUSTER_STATUS_HEALTHY_TEXT],
|
||||
['error', 'danger', CLUSTER_STATUS_UNHEALTHY_TEXT],
|
||||
])(
|
||||
'when clusterHealthStatus is %s shows health badge with variant %s and text %s',
|
||||
(status, variant, text) => {
|
||||
createWrapper({ clusterHealthStatus: status });
|
||||
|
||||
expect(findLoadingIcon().exists()).toBe(false);
|
||||
expect(findHealthBadge().props('variant')).toBe(variant);
|
||||
expect(findHealthBadge().text()).toBe(text);
|
||||
},
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
@ -59,6 +59,14 @@ describe('~/environments/components/kubernetes_summary.vue', () => {
|
|||
expect(findLoadingIcon().exists()).toBe(true);
|
||||
});
|
||||
|
||||
it('emits loading state', async () => {
|
||||
createWrapper();
|
||||
expect(wrapper.emitted('loading')[0]).toEqual([true]);
|
||||
|
||||
await waitForPromises();
|
||||
expect(wrapper.emitted('loading')[1]).toEqual([false]);
|
||||
});
|
||||
|
||||
describe('when workloads data is loaded', () => {
|
||||
beforeEach(async () => {
|
||||
await createWrapper();
|
||||
|
|
@ -94,6 +102,10 @@ describe('~/environments/components/kubernetes_summary.vue', () => {
|
|||
);
|
||||
});
|
||||
|
||||
it('emits a failed event when there are failed workload types', () => {
|
||||
expect(wrapper.emitted('failed')).toHaveLength(1);
|
||||
});
|
||||
|
||||
it('emits an error message when gets an error from the cluster_client API', async () => {
|
||||
const error = new Error('Error from the cluster_client API');
|
||||
const createErroredApolloProvider = () => {
|
||||
|
|
|
|||
|
|
@ -165,4 +165,23 @@ describe('~/environments/components/kubernetes_tabs.vue', () => {
|
|||
expect(wrapper.emitted('cluster-error')).toEqual([[error]]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('summary tab', () => {
|
||||
beforeEach(() => {
|
||||
createWrapper();
|
||||
});
|
||||
|
||||
it('emits loading event when gets it from the component', () => {
|
||||
findKubernetesSummary().vm.$emit('loading', true);
|
||||
expect(wrapper.emitted('loading')[0]).toEqual([true]);
|
||||
|
||||
findKubernetesSummary().vm.$emit('loading', false);
|
||||
expect(wrapper.emitted('loading')[1]).toEqual([false]);
|
||||
});
|
||||
|
||||
it('emits a failed event when gets it from the component', () => {
|
||||
findKubernetesSummary().vm.$emit('failed');
|
||||
expect(wrapper.emitted('failed')).toHaveLength(1);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -40,11 +40,8 @@ RSpec.describe 'Startup CSS fixtures', type: :controller do
|
|||
expect(response).to be_successful
|
||||
end
|
||||
|
||||
# This Feature Flag is off by default
|
||||
# This ensures that the correct css is generated for super sidebar
|
||||
# When the feature flag is off, the general startup will capture it
|
||||
it "startup_css/project-#{type}-super-sidebar.html" do
|
||||
stub_feature_flags(super_sidebar_nav: true)
|
||||
user.update!(use_new_navigation: true)
|
||||
|
||||
get :show, params: {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,147 @@
|
|||
import Vue from 'vue';
|
||||
import VueApollo from 'vue-apollo';
|
||||
import { GlBadge, GlLoadingIcon, GlModal, GlSprintf, GlToggle } from '@gitlab/ui';
|
||||
|
||||
import { createAlert } from '~/alert';
|
||||
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
|
||||
import waitForPromises from 'helpers/wait_for_promises';
|
||||
import createMockApollo from 'helpers/mock_apollo_helper';
|
||||
|
||||
import catalogResourcesCreate from '~/pages/projects/shared/permissions/graphql/mutations/catalog_resources_create.mutation.graphql';
|
||||
import getCiCatalogSettingsQuery from '~/pages/projects/shared/permissions/graphql/queries/get_ci_catalog_settings.query.graphql';
|
||||
import CiCatalogSettings, {
|
||||
i18n,
|
||||
} from '~/pages/projects/shared/permissions/components/ci_catalog_settings.vue';
|
||||
|
||||
import { mockCiCatalogSettingsResponse } from './mock_data';
|
||||
|
||||
Vue.use(VueApollo);
|
||||
jest.mock('~/alert');
|
||||
|
||||
describe('CiCatalogSettings', () => {
|
||||
let wrapper;
|
||||
let ciCatalogSettingsResponse;
|
||||
let catalogResourcesCreateResponse;
|
||||
|
||||
const fullPath = 'gitlab-org/gitlab';
|
||||
|
||||
const createComponent = ({ ciCatalogSettingsHandler = ciCatalogSettingsResponse } = {}) => {
|
||||
const handlers = [
|
||||
[getCiCatalogSettingsQuery, ciCatalogSettingsHandler],
|
||||
[catalogResourcesCreate, catalogResourcesCreateResponse],
|
||||
];
|
||||
const mockApollo = createMockApollo(handlers);
|
||||
|
||||
wrapper = shallowMountExtended(CiCatalogSettings, {
|
||||
propsData: {
|
||||
fullPath,
|
||||
},
|
||||
stubs: {
|
||||
GlSprintf,
|
||||
},
|
||||
apolloProvider: mockApollo,
|
||||
});
|
||||
|
||||
return waitForPromises();
|
||||
};
|
||||
|
||||
const findLoadingIcon = () => wrapper.findComponent(GlLoadingIcon);
|
||||
const findBadge = () => wrapper.findComponent(GlBadge);
|
||||
const findModal = () => wrapper.findComponent(GlModal);
|
||||
const findToggle = () => wrapper.findComponent(GlToggle);
|
||||
|
||||
const findCiCatalogSettings = () => wrapper.findByTestId('ci-catalog-settings');
|
||||
|
||||
beforeEach(() => {
|
||||
ciCatalogSettingsResponse = jest.fn().mockResolvedValue(mockCiCatalogSettingsResponse);
|
||||
catalogResourcesCreateResponse = jest.fn();
|
||||
});
|
||||
|
||||
describe('when initial queries are loading', () => {
|
||||
beforeEach(() => {
|
||||
createComponent();
|
||||
});
|
||||
|
||||
it('shows a loading icon and no CI catalog settings', () => {
|
||||
expect(findLoadingIcon().exists()).toBe(true);
|
||||
expect(findCiCatalogSettings().exists()).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when queries have loaded', () => {
|
||||
beforeEach(async () => {
|
||||
await createComponent();
|
||||
});
|
||||
|
||||
it('does not show a loading icon', () => {
|
||||
expect(findLoadingIcon().exists()).toBe(false);
|
||||
});
|
||||
|
||||
it('renders the CI Catalog settings', () => {
|
||||
expect(findCiCatalogSettings().exists()).toBe(true);
|
||||
});
|
||||
|
||||
it('renders the experiment badge', () => {
|
||||
expect(findBadge().exists()).toBe(true);
|
||||
});
|
||||
|
||||
it('renders the toggle', () => {
|
||||
expect(findToggle().exists()).toBe(true);
|
||||
});
|
||||
|
||||
it('renders the modal', () => {
|
||||
expect(findModal().exists()).toBe(true);
|
||||
expect(findModal().attributes('title')).toBe(i18n.modal.title);
|
||||
});
|
||||
|
||||
describe('when queries have loaded', () => {
|
||||
beforeEach(() => {
|
||||
catalogResourcesCreateResponse.mockResolvedValue(mockCiCatalogSettingsResponse);
|
||||
});
|
||||
|
||||
it('shows the modal when the toggle is clicked', async () => {
|
||||
expect(findModal().props('visible')).toBe(false);
|
||||
|
||||
await findToggle().vm.$emit('change', true);
|
||||
|
||||
expect(findModal().props('visible')).toBe(true);
|
||||
expect(findModal().props('actionPrimary').text).toBe(i18n.modal.actionPrimary.text);
|
||||
});
|
||||
|
||||
it('hides the modal when cancel is clicked', () => {
|
||||
findToggle().vm.$emit('change', true);
|
||||
findModal().vm.$emit('canceled');
|
||||
|
||||
expect(findModal().props('visible')).toBe(false);
|
||||
expect(catalogResourcesCreateResponse).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('calls the mutation with the correct input from the modal click', async () => {
|
||||
expect(catalogResourcesCreateResponse).toHaveBeenCalledTimes(0);
|
||||
|
||||
findToggle().vm.$emit('change', true);
|
||||
findModal().vm.$emit('primary');
|
||||
await waitForPromises();
|
||||
|
||||
expect(catalogResourcesCreateResponse).toHaveBeenCalledTimes(1);
|
||||
expect(catalogResourcesCreateResponse).toHaveBeenCalledWith({
|
||||
input: {
|
||||
projectPath: fullPath,
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('when the query is unsuccessful', () => {
|
||||
const failedHandler = jest.fn().mockRejectedValue(new Error('GraphQL error'));
|
||||
|
||||
it('throws an error', async () => {
|
||||
await createComponent({ ciCatalogSettingsHandler: failedHandler });
|
||||
|
||||
await waitForPromises();
|
||||
|
||||
expect(createAlert).toHaveBeenCalledWith({ message: i18n.catalogResourceQueryError });
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
export const mockCiCatalogSettingsResponse = {
|
||||
data: {
|
||||
catalogResourcesCreate: {
|
||||
errors: [],
|
||||
},
|
||||
},
|
||||
};
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
import { GlSprintf, GlToggle } from '@gitlab/ui';
|
||||
import { shallowMount, mount } from '@vue/test-utils';
|
||||
import ProjectFeatureSetting from '~/pages/projects/shared/permissions/components/project_feature_setting.vue';
|
||||
import CiCatalogSettings from '~/pages/projects/shared/permissions/components/ci_catalog_settings.vue';
|
||||
import settingsPanel from '~/pages/projects/shared/permissions/components/settings_panel.vue';
|
||||
import {
|
||||
featureAccessLevel,
|
||||
|
|
@ -34,6 +35,7 @@ const defaultProps = {
|
|||
warnAboutPotentiallyUnwantedCharacters: true,
|
||||
},
|
||||
isGitlabCom: true,
|
||||
canAddCatalogResource: false,
|
||||
canDisableEmails: true,
|
||||
canChangeVisibilityLevel: true,
|
||||
allowedVisibilityOptions: [0, 10, 20],
|
||||
|
|
@ -118,6 +120,7 @@ describe('Settings Panel', () => {
|
|||
const findPagesSettings = () => wrapper.findComponent({ ref: 'pages-settings' });
|
||||
const findPagesAccessLevels = () =>
|
||||
wrapper.find('[name="project[project_feature_attributes][pages_access_level]"]');
|
||||
const findCiCatalogSettings = () => wrapper.findComponent(CiCatalogSettings);
|
||||
const findEmailSettings = () => wrapper.findComponent({ ref: 'email-settings' });
|
||||
const findShowDefaultAwardEmojis = () =>
|
||||
wrapper.find('input[name="project[project_setting_attributes][show_default_award_emojis]"]');
|
||||
|
|
@ -647,6 +650,19 @@ describe('Settings Panel', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('CI Catalog Settings', () => {
|
||||
it('should show the CI Catalog settings if user has permission', () => {
|
||||
wrapper = mountComponent({ canAddCatalogResource: true });
|
||||
|
||||
expect(findCiCatalogSettings().exists()).toBe(true);
|
||||
});
|
||||
it('should not show the CI Catalog settings if user does not have permission', () => {
|
||||
wrapper = mountComponent();
|
||||
|
||||
expect(findCiCatalogSettings().exists()).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Email notifications', () => {
|
||||
it('should show the disable email notifications input if emails an be disabled', () => {
|
||||
wrapper = mountComponent({ canDisableEmails: true });
|
||||
|
|
|
|||
|
|
@ -59,6 +59,7 @@ describe('Pipeline details header', () => {
|
|||
const findTimeAgo = () => wrapper.findComponent(TimeAgo);
|
||||
const findAllBadges = () => wrapper.findAllComponents(GlBadge);
|
||||
const findPipelineName = () => wrapper.findByTestId('pipeline-name');
|
||||
const findCommitTitle = () => wrapper.findByTestId('pipeline-commit-title');
|
||||
const findTotalJobs = () => wrapper.findByTestId('total-jobs');
|
||||
const findComputeCredits = () => wrapper.findByTestId('compute-credits');
|
||||
const findCommitLink = () => wrapper.findByTestId('commit-link');
|
||||
|
|
@ -178,6 +179,19 @@ describe('Pipeline details header', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('without pipeline name', () => {
|
||||
it('displays commit title', async () => {
|
||||
createComponent(defaultHandlers, { ...defaultProps, name: '' });
|
||||
|
||||
await waitForPromises();
|
||||
|
||||
const expectedTitle = pipelineHeaderSuccess.data.project.pipeline.commit.title;
|
||||
|
||||
expect(findPipelineName().exists()).toBe(false);
|
||||
expect(findCommitTitle().text()).toBe(expectedTitle);
|
||||
});
|
||||
});
|
||||
|
||||
describe('finished pipeline', () => {
|
||||
beforeEach(async () => {
|
||||
createComponent();
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ describe('Timeago component', () => {
|
|||
|
||||
const defaultProps = { duration: 0, finished_at: '' };
|
||||
|
||||
const createComponent = (props = defaultProps, stuck = false, extraProps) => {
|
||||
const createComponent = (props = defaultProps, extraProps) => {
|
||||
wrapper = extendedWrapper(
|
||||
shallowMount(TimeAgo, {
|
||||
propsData: {
|
||||
|
|
@ -16,9 +16,6 @@ describe('Timeago component', () => {
|
|||
details: {
|
||||
...props,
|
||||
},
|
||||
flags: {
|
||||
stuck,
|
||||
},
|
||||
},
|
||||
...extraProps,
|
||||
},
|
||||
|
|
@ -33,10 +30,6 @@ describe('Timeago component', () => {
|
|||
|
||||
const duration = () => wrapper.find('.duration');
|
||||
const finishedAt = () => wrapper.find('.finished-at');
|
||||
const findInProgress = () => wrapper.findByTestId('pipeline-in-progress');
|
||||
const findSkipped = () => wrapper.findByTestId('pipeline-skipped');
|
||||
const findHourGlassIcon = () => wrapper.findByTestId('hourglass-icon');
|
||||
const findWarningIcon = () => wrapper.findByTestId('warning-icon');
|
||||
const findCalendarIcon = () => wrapper.findByTestId('calendar-icon');
|
||||
|
||||
describe('with duration', () => {
|
||||
|
|
@ -79,9 +72,12 @@ describe('Timeago component', () => {
|
|||
});
|
||||
|
||||
it('should hide calendar icon if correct prop is passed', () => {
|
||||
createComponent({ duration: 0, finished_at: '2017-04-26T12:40:23.277Z' }, false, {
|
||||
displayCalendarIcon: false,
|
||||
});
|
||||
createComponent(
|
||||
{ duration: 0, finished_at: '2017-04-26T12:40:23.277Z' },
|
||||
{
|
||||
displayCalendarIcon: false,
|
||||
},
|
||||
);
|
||||
|
||||
expect(findCalendarIcon().exists()).toBe(false);
|
||||
});
|
||||
|
|
@ -97,45 +93,4 @@ describe('Timeago component', () => {
|
|||
expect(findCalendarIcon().exists()).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('in progress', () => {
|
||||
it.each`
|
||||
durationTime | finishedAtTime | shouldShow
|
||||
${10} | ${'2017-04-26T12:40:23.277Z'} | ${false}
|
||||
${10} | ${''} | ${false}
|
||||
${0} | ${'2017-04-26T12:40:23.277Z'} | ${false}
|
||||
${0} | ${''} | ${true}
|
||||
`(
|
||||
'progress state shown: $shouldShow when pipeline duration is $durationTime and finished_at is $finishedAtTime',
|
||||
({ durationTime, finishedAtTime, shouldShow }) => {
|
||||
createComponent({
|
||||
duration: durationTime,
|
||||
finished_at: finishedAtTime,
|
||||
});
|
||||
|
||||
expect(findInProgress().exists()).toBe(shouldShow);
|
||||
expect(findSkipped().exists()).toBe(false);
|
||||
},
|
||||
);
|
||||
|
||||
it('should show warning icon beside in progress if pipeline is stuck', () => {
|
||||
const stuck = true;
|
||||
|
||||
createComponent(defaultProps, stuck);
|
||||
|
||||
expect(findWarningIcon().exists()).toBe(true);
|
||||
expect(findHourGlassIcon().exists()).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('skipped', () => {
|
||||
it('should show skipped if pipeline was skipped', () => {
|
||||
createComponent({
|
||||
status: { label: 'skipped' },
|
||||
});
|
||||
|
||||
expect(findSkipped().exists()).toBe(true);
|
||||
expect(findInProgress().exists()).toBe(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import {
|
|||
RUNNING,
|
||||
DEPLOYING,
|
||||
REDEPLOYING,
|
||||
WILL_DEPLOY,
|
||||
} from '~/vue_merge_request_widget/components/deployment/constants';
|
||||
import DeploymentActionButton from '~/vue_merge_request_widget/components/deployment/deployment_action_button.vue';
|
||||
import { actionButtonMocks } from './deployment_mock_data';
|
||||
|
|
@ -118,4 +119,20 @@ describe('Deployment action button', () => {
|
|||
expect(wrapper.findComponent(GlButton).props('disabled')).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when the deployment status is will_deploy', () => {
|
||||
beforeEach(() => {
|
||||
factory({
|
||||
propsData: {
|
||||
...baseProps,
|
||||
actionInProgress: actionButtonMocks[REDEPLOYING].actionName,
|
||||
computedDeploymentStatus: WILL_DEPLOY,
|
||||
},
|
||||
});
|
||||
});
|
||||
it('is disabled and shows the loading icon', () => {
|
||||
expect(wrapper.findComponent(GlLoadingIcon).exists()).toBe(true);
|
||||
expect(wrapper.findComponent(GlButton).props('disabled')).toBe(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import {
|
|||
FAILED,
|
||||
DEPLOYING,
|
||||
REDEPLOYING,
|
||||
SUCCESS,
|
||||
STOPPING,
|
||||
} from '~/vue_merge_request_widget/components/deployment/constants';
|
||||
import eventHub from '~/vue_merge_request_widget/event_hub';
|
||||
|
|
@ -35,7 +36,8 @@ describe('DeploymentAction component', () => {
|
|||
|
||||
const findStopButton = () => wrapper.find('.js-stop-env');
|
||||
const findDeployButton = () => wrapper.find('.js-manual-deploy-action');
|
||||
const findRedeployButton = () => wrapper.find('.js-manual-redeploy-action');
|
||||
const findManualRedeployButton = () => wrapper.find('.js-manual-redeploy-action');
|
||||
const findRedeployButton = () => wrapper.find('.js-redeploy-action');
|
||||
|
||||
beforeEach(() => {
|
||||
executeActionSpy = jest.spyOn(MRWidgetService, 'executeInlineAction');
|
||||
|
|
@ -79,17 +81,17 @@ describe('DeploymentAction component', () => {
|
|||
|
||||
describe('when there is no retry_path in details', () => {
|
||||
it('the manual redeploy button does not appear', () => {
|
||||
expect(findRedeployButton().exists()).toBe(false);
|
||||
expect(findManualRedeployButton().exists()).toBe(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('when conditions are met', () => {
|
||||
describe.each`
|
||||
configConst | computedDeploymentStatus | displayConditionChanges | finderFn | endpoint
|
||||
${STOPPING} | ${CREATED} | ${{}} | ${findStopButton} | ${deploymentMockData.stop_url}
|
||||
${DEPLOYING} | ${MANUAL_DEPLOY} | ${playDetails} | ${findDeployButton} | ${playDetails.playable_build.play_path}
|
||||
${REDEPLOYING} | ${FAILED} | ${retryDetails} | ${findRedeployButton} | ${retryDetails.playable_build.retry_path}
|
||||
configConst | computedDeploymentStatus | displayConditionChanges | finderFn | endpoint
|
||||
${STOPPING} | ${CREATED} | ${{}} | ${findStopButton} | ${deploymentMockData.stop_url}
|
||||
${DEPLOYING} | ${MANUAL_DEPLOY} | ${playDetails} | ${findDeployButton} | ${playDetails.playable_build.play_path}
|
||||
${REDEPLOYING} | ${FAILED} | ${retryDetails} | ${findManualRedeployButton} | ${retryDetails.playable_build.retry_path}
|
||||
`(
|
||||
'$configConst action',
|
||||
({ configConst, computedDeploymentStatus, displayConditionChanges, finderFn, endpoint }) => {
|
||||
|
|
@ -231,4 +233,141 @@ describe('DeploymentAction component', () => {
|
|||
},
|
||||
);
|
||||
});
|
||||
|
||||
describe('with the reviewAppsRedeployMrWidget feature flag turned on', () => {
|
||||
beforeEach(() => {
|
||||
factory({
|
||||
propsData: {
|
||||
computedDeploymentStatus: SUCCESS,
|
||||
deployment: {
|
||||
...deploymentMockData,
|
||||
details: undefined,
|
||||
retry_url: retryDetails.playable_build.retry_path,
|
||||
environment_available: false,
|
||||
},
|
||||
},
|
||||
provide: {
|
||||
glFeatures: {
|
||||
reviewAppsRedeployMrWidget: true,
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('should display the redeploy button', () => {
|
||||
expect(findRedeployButton().exists()).toBe(true);
|
||||
});
|
||||
|
||||
describe('when the redeploy button is clicked', () => {
|
||||
describe('should show a confirm dialog but not call executeInlineAction when declined', () => {
|
||||
beforeEach(() => {
|
||||
executeActionSpy.mockResolvedValueOnce();
|
||||
confirmAction.mockResolvedValueOnce(false);
|
||||
findRedeployButton().trigger('click');
|
||||
});
|
||||
|
||||
it('should show the confirm dialog', () => {
|
||||
expect(confirmAction).toHaveBeenCalled();
|
||||
expect(confirmAction).toHaveBeenCalledWith(
|
||||
actionButtonMocks[REDEPLOYING].confirmMessage,
|
||||
{
|
||||
primaryBtnVariant: actionButtonMocks[REDEPLOYING].buttonVariant,
|
||||
primaryBtnText: actionButtonMocks[REDEPLOYING].buttonText,
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
it('should not execute the action', () => {
|
||||
expect(MRWidgetService.executeInlineAction).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('should show a confirm dialog and call executeInlineAction when accepted', () => {
|
||||
beforeEach(() => {
|
||||
executeActionSpy.mockResolvedValueOnce();
|
||||
confirmAction.mockResolvedValueOnce(true);
|
||||
findRedeployButton().trigger('click');
|
||||
});
|
||||
|
||||
it('should show the confirm dialog', () => {
|
||||
expect(confirmAction).toHaveBeenCalled();
|
||||
expect(confirmAction).toHaveBeenCalledWith(
|
||||
actionButtonMocks[REDEPLOYING].confirmMessage,
|
||||
{
|
||||
primaryBtnVariant: actionButtonMocks[REDEPLOYING].buttonVariant,
|
||||
primaryBtnText: actionButtonMocks[REDEPLOYING].buttonText,
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
it('should not throw an error', () => {
|
||||
expect(createAlert).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
describe('response includes redirect_url', () => {
|
||||
const url = '/root/example';
|
||||
beforeEach(async () => {
|
||||
executeActionSpy.mockResolvedValueOnce({
|
||||
data: { redirect_url: url },
|
||||
});
|
||||
|
||||
await waitForPromises();
|
||||
|
||||
confirmAction.mockResolvedValueOnce(true);
|
||||
findRedeployButton().trigger('click');
|
||||
});
|
||||
|
||||
it('does not call visit url', () => {
|
||||
expect(visitUrl).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('it should call the executeAction method', () => {
|
||||
beforeEach(async () => {
|
||||
jest.spyOn(wrapper.vm, 'executeAction').mockImplementation();
|
||||
jest.spyOn(eventHub, '$emit');
|
||||
|
||||
await waitForPromises();
|
||||
|
||||
confirmAction.mockResolvedValueOnce(true);
|
||||
findRedeployButton().trigger('click');
|
||||
});
|
||||
|
||||
it('calls with the expected arguments', () => {
|
||||
expect(wrapper.vm.executeAction).toHaveBeenCalled();
|
||||
expect(wrapper.vm.executeAction).toHaveBeenCalledWith(
|
||||
retryDetails.playable_build.retry_path,
|
||||
actionButtonMocks[REDEPLOYING],
|
||||
);
|
||||
});
|
||||
|
||||
it('emits the FetchDeployments event', () => {
|
||||
expect(eventHub.$emit).toHaveBeenCalledWith('FetchDeployments');
|
||||
});
|
||||
});
|
||||
|
||||
describe('when executeInlineAction errors', () => {
|
||||
beforeEach(async () => {
|
||||
executeActionSpy.mockRejectedValueOnce();
|
||||
jest.spyOn(eventHub, '$emit');
|
||||
|
||||
await waitForPromises();
|
||||
|
||||
confirmAction.mockResolvedValueOnce(true);
|
||||
findRedeployButton().trigger('click');
|
||||
});
|
||||
|
||||
it('should call createAlert with error message', () => {
|
||||
expect(createAlert).toHaveBeenCalledWith({
|
||||
message: actionButtonMocks[REDEPLOYING].errorMessage,
|
||||
});
|
||||
});
|
||||
|
||||
it('emits the FetchDeployments event', () => {
|
||||
expect(eventHub.$emit).toHaveBeenCalledWith('FetchDeployments');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -43,6 +43,7 @@ const deploymentMockData = {
|
|||
external_url_formatted: 'gitlab',
|
||||
deployed_at: '2017-03-22T22:44:42.258Z',
|
||||
deployed_at_formatted: 'Mar 22, 2017 10:44pm',
|
||||
environment_available: true,
|
||||
details: {},
|
||||
status: SUCCESS,
|
||||
changes: [
|
||||
|
|
|
|||
|
|
@ -427,6 +427,7 @@ export const mockStore = {
|
|||
external_url: 'https://fake.com',
|
||||
external_url_formatted: 'https://fake.com',
|
||||
status: SUCCESS,
|
||||
environment_available: true,
|
||||
},
|
||||
{
|
||||
id: 1,
|
||||
|
|
@ -434,6 +435,7 @@ export const mockStore = {
|
|||
external_url: 'https://fake.com',
|
||||
external_url_formatted: 'https://fake.com',
|
||||
status: SUCCESS,
|
||||
environment_available: true,
|
||||
},
|
||||
],
|
||||
postMergeDeployments: [
|
||||
|
|
|
|||
|
|
@ -626,6 +626,7 @@ describe('MrWidgetOptions', () => {
|
|||
deployed_at_formatted: 'Mar 22, 2017 10:44pm',
|
||||
changes,
|
||||
status: SUCCESS,
|
||||
environment_available: true,
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,22 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Resolvers::AuditEvents::AuditEventDefinitionsResolver, feature_category: :audit_events do
|
||||
using RSpec::Parameterized::TableSyntax
|
||||
|
||||
include GraphqlHelpers
|
||||
|
||||
let_it_be(:current_user) { create(:user) }
|
||||
|
||||
describe '#resolve' do
|
||||
let(:args) { {} }
|
||||
|
||||
subject(:audit_event_definitions) { resolve(described_class, args: args, ctx: { current_user: current_user }) }
|
||||
|
||||
it 'returns an array of audit event definitions' do
|
||||
expect(audit_event_definitions).to be_an(Array)
|
||||
expect(audit_event_definitions).to match_array(Gitlab::Audit::Type::Definition.definitions.values)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe GitlabSchema.types['AuditEventDefinition'], feature_category: :audit_events do
|
||||
let(:fields) do
|
||||
%i[
|
||||
name description introduced_by_issue introduced_by_mr
|
||||
feature_category milestone saved_to_database streamed
|
||||
]
|
||||
end
|
||||
|
||||
specify { expect(described_class.graphql_name).to eq('AuditEventDefinition') }
|
||||
specify { expect(described_class).to have_graphql_fields(fields) }
|
||||
end
|
||||
|
|
@ -7,6 +7,18 @@ RSpec.describe Ci::Catalog::ResourcesHelper, feature_category: :pipeline_composi
|
|||
|
||||
let_it_be(:project) { build(:project) }
|
||||
|
||||
describe '#can_add_catalog_resource?' do
|
||||
subject { helper.can_add_catalog_resource?(project) }
|
||||
|
||||
before do
|
||||
stub_licensed_features(ci_namespace_catalog: false)
|
||||
end
|
||||
|
||||
it 'user cannot add a catalog resource in CE regardless of permissions' do
|
||||
expect(subject).to be false
|
||||
end
|
||||
end
|
||||
|
||||
describe '#can_view_namespace_catalog?' do
|
||||
subject { helper.can_view_namespace_catalog?(project) }
|
||||
|
||||
|
|
|
|||
|
|
@ -138,56 +138,35 @@ RSpec.describe NavHelper, feature_category: :navigation do
|
|||
describe '#show_super_sidebar?' do
|
||||
shared_examples 'show_super_sidebar is supposed to' do
|
||||
before do
|
||||
stub_feature_flags(super_sidebar_nav: new_nav_ff)
|
||||
user.update!(use_new_navigation: user_preference)
|
||||
end
|
||||
|
||||
context 'with feature flag off' do
|
||||
let(:new_nav_ff) { false }
|
||||
context 'when user has not interacted with the new nav toggle yet' do
|
||||
let(:user_preference) { nil }
|
||||
|
||||
context 'when user has new nav disabled' do
|
||||
let(:user_preference) { false }
|
||||
specify { expect(subject).to eq false }
|
||||
|
||||
specify { expect(subject).to eq false }
|
||||
end
|
||||
|
||||
context 'when user has new nav enabled' do
|
||||
let(:user_preference) { true }
|
||||
|
||||
specify { expect(subject).to eq false }
|
||||
end
|
||||
end
|
||||
|
||||
context 'with feature flag on' do
|
||||
let(:new_nav_ff) { true }
|
||||
|
||||
context 'when user has not interacted with the new nav toggle yet' do
|
||||
let(:user_preference) { nil }
|
||||
|
||||
specify { expect(subject).to eq false }
|
||||
|
||||
context 'when the user was enrolled into the new nav via a special feature flag' do
|
||||
before do
|
||||
# this ff is disabled in globally to keep tests of the old nav working
|
||||
stub_feature_flags(super_sidebar_nav_enrolled: true)
|
||||
end
|
||||
|
||||
specify { expect(subject).to eq true }
|
||||
context 'when the user was enrolled into the new nav via a special feature flag' do
|
||||
before do
|
||||
# this ff is disabled in globally to keep tests of the old nav working
|
||||
stub_feature_flags(super_sidebar_nav_enrolled: true)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when user has new nav disabled' do
|
||||
let(:user_preference) { false }
|
||||
|
||||
specify { expect(subject).to eq false }
|
||||
end
|
||||
|
||||
context 'when user has new nav enabled' do
|
||||
let(:user_preference) { true }
|
||||
|
||||
specify { expect(subject).to eq true }
|
||||
end
|
||||
end
|
||||
|
||||
context 'when user has new nav disabled' do
|
||||
let(:user_preference) { false }
|
||||
|
||||
specify { expect(subject).to eq false }
|
||||
end
|
||||
|
||||
context 'when user has new nav enabled' do
|
||||
let(:user_preference) { true }
|
||||
|
||||
specify { expect(subject).to eq true }
|
||||
end
|
||||
end
|
||||
|
||||
context 'when nil is provided' do
|
||||
|
|
|
|||
|
|
@ -1052,6 +1052,12 @@ RSpec.describe ProjectsHelper, feature_category: :source_code_management do
|
|||
it 'includes membersPagePath' do
|
||||
expect(subject).to include(membersPagePath: project_project_members_path(project))
|
||||
end
|
||||
|
||||
it 'includes canAddCatalogResource' do
|
||||
allow(helper).to receive(:can?) { false }
|
||||
|
||||
expect(subject).to include(canAddCatalogResource: false)
|
||||
end
|
||||
end
|
||||
|
||||
describe '#project_classes' do
|
||||
|
|
|
|||
|
|
@ -0,0 +1,24 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe 'getting a list of audit event definitions', feature_category: :audit_events do
|
||||
include GraphqlHelpers
|
||||
|
||||
let_it_be(:current_user) { create(:user) }
|
||||
|
||||
let(:path) { %i[audit_event_definitions nodes] }
|
||||
let(:audit_event_definition_keys) do
|
||||
Gitlab::Audit::Type::Definition.definitions.keys
|
||||
end
|
||||
|
||||
let(:query) { graphql_query_for(:audit_event_definitions, {}, 'nodes { name }') }
|
||||
|
||||
it 'returns the audit event definitions' do
|
||||
post_graphql(query, current_user: current_user)
|
||||
|
||||
returned_names = graphql_data_at(*path).map { |v| v['name'].to_sym }
|
||||
|
||||
expect(returned_names).to all be_in(audit_event_definition_keys)
|
||||
end
|
||||
end
|
||||
|
|
@ -53,7 +53,11 @@ Capybara.register_server :puma_via_workhorse do |app, port, host, **options|
|
|||
# In cases of multiple installations of chromedriver, prioritize the version installed by SeleniumManager
|
||||
# selenium-manager doesn't work with Linux arm64 yet:
|
||||
# https://github.com/SeleniumHQ/selenium/issues/11357
|
||||
if RUBY_PLATFORM =~ /x86_64-linux|darwin/
|
||||
if RUBY_PLATFORM.include?('x86_64-linux') ||
|
||||
# Rosetta is required on macOS because the selenium-manager
|
||||
# binaries (https://github.com/SeleniumHQ/selenium/tree/trunk/common/manager/macos)
|
||||
# are only compiled for macOS x86.
|
||||
(RUBY_PLATFORM.include?('darwin') && system('/usr/bin/pgrep -q oahd'))
|
||||
chrome_options = Selenium::WebDriver::Chrome::Options.chrome
|
||||
chromedriver_path = File.dirname(Selenium::WebDriver::SeleniumManager.driver_path(chrome_options))
|
||||
ENV['PATH'] = "#{chromedriver_path}:#{ENV['PATH']}" # rubocop:disable RSpec/EnvAssignment
|
||||
|
|
|
|||
|
|
@ -149,7 +149,7 @@ module LoginHelpers
|
|||
mock_auth_hash(provider, uid, email, response_object: response_object)
|
||||
end
|
||||
|
||||
def configure_mock_auth(provider, uid, email, response_object: nil, additional_info: {})
|
||||
def configure_mock_auth(provider, uid, email, response_object: nil, additional_info: {}, name: 'mockuser')
|
||||
# The mock_auth configuration allows you to set per-provider (or default)
|
||||
# authentication hashes to return during integration testing.
|
||||
|
||||
|
|
@ -157,7 +157,7 @@ module LoginHelpers
|
|||
provider: provider,
|
||||
uid: uid,
|
||||
info: {
|
||||
name: 'mockuser',
|
||||
name: name,
|
||||
email: email,
|
||||
image: 'mock_user_thumbnail_url'
|
||||
},
|
||||
|
|
@ -180,8 +180,10 @@ module LoginHelpers
|
|||
}).merge(additional_info) { |_, old_hash, new_hash| old_hash.merge(new_hash) }
|
||||
end
|
||||
|
||||
def mock_auth_hash(provider, uid, email, additional_info: {}, response_object: nil)
|
||||
configure_mock_auth(provider, uid, email, additional_info: additional_info, response_object: response_object)
|
||||
def mock_auth_hash(provider, uid, email, additional_info: {}, response_object: nil, name: 'mockuser')
|
||||
configure_mock_auth(
|
||||
provider, uid, email, additional_info: additional_info, response_object: response_object, name: name
|
||||
)
|
||||
|
||||
original_env_config_omniauth_auth = Rails.application.env_config['omniauth.auth']
|
||||
Rails.application.env_config['omniauth.auth'] = OmniAuth.config.mock_auth[provider.to_sym]
|
||||
|
|
|
|||
|
|
@ -40,7 +40,8 @@ RSpec.shared_context 'with FOSS query type fields' do
|
|||
:usage_trends_measurements,
|
||||
:user,
|
||||
:users,
|
||||
:work_item
|
||||
:work_item,
|
||||
:audit_event_definitions
|
||||
]
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
RSpec.configure do |config|
|
||||
%i[saas saas_registration saas_sso_registration saas_subscription_registration].each do |metadata|
|
||||
%i[saas saas_registration].each do |metadata|
|
||||
config.before(:context, metadata) do
|
||||
# Ensure Gitlab.com? returns true during context.
|
||||
# This is needed for let_it_be which is shared across examples,
|
||||
|
|
|
|||
Loading…
Reference in New Issue