Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
1aa447601c
commit
3bba41a8c5
|
|
@ -86,7 +86,6 @@ Style/ExplicitBlockArgument:
|
|||
- 'spec/support/database/gitlab_schemas_validate_connection.rb'
|
||||
- 'spec/support/helpers/feature_flag_helpers.rb'
|
||||
- 'spec/support/helpers/features/runners_helpers.rb'
|
||||
- 'spec/support/helpers/features/top_nav_spec_helpers.rb'
|
||||
- 'spec/support/helpers/modal_helpers.rb'
|
||||
- 'spec/support/helpers/next_found_instance_of.rb'
|
||||
- 'spec/support/helpers/usage_data_helpers.rb'
|
||||
|
|
|
|||
|
|
@ -555,8 +555,6 @@ Style/GuardClause:
|
|||
- 'lib/tasks/config_lint.rake'
|
||||
- 'lib/tasks/gettext.rake'
|
||||
- 'qa/qa/ee/page/file/show.rb'
|
||||
- 'qa/qa/mobile/page/main/menu.rb'
|
||||
- 'qa/qa/mobile/page/sub_menus/common.rb'
|
||||
- 'qa/qa/page/component/snippet.rb'
|
||||
- 'qa/qa/page/mattermost/login.rb'
|
||||
- 'qa/qa/page/page_concern.rb'
|
||||
|
|
|
|||
|
|
@ -75,6 +75,6 @@ export default {
|
|||
/* TODO: Use max-height prop when gitlab-ui got updated.
|
||||
See https://gitlab.com/gitlab-org/gitlab-ui/-/issues/2374 */
|
||||
::v-deep .gl-new-dropdown-inner {
|
||||
max-height: 310px;
|
||||
max-height: 310px !important;
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -43,7 +43,7 @@ export default {
|
|||
return {
|
||||
isVisible: false,
|
||||
error: '',
|
||||
hasFailedState: false,
|
||||
failedState: {},
|
||||
podsLoading: false,
|
||||
workloadTypesLoading: false,
|
||||
};
|
||||
|
|
@ -78,6 +78,9 @@ export default {
|
|||
|
||||
return this.hasFailedState ? 'error' : 'success';
|
||||
},
|
||||
hasFailedState() {
|
||||
return Object.values(this.failedState).some((item) => item);
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
toggleCollapse() {
|
||||
|
|
@ -86,6 +89,12 @@ export default {
|
|||
onClusterError(message) {
|
||||
this.error = message;
|
||||
},
|
||||
onUpdateFailedState(event) {
|
||||
this.failedState = {
|
||||
...this.failedState,
|
||||
...event,
|
||||
};
|
||||
},
|
||||
},
|
||||
i18n: {
|
||||
collapse: __('Collapse'),
|
||||
|
|
@ -126,14 +135,14 @@ export default {
|
|||
class="gl-mb-5"
|
||||
@cluster-error="onClusterError"
|
||||
@loading="podsLoading = $event"
|
||||
@failed="hasFailedState = true" />
|
||||
@update-failed-state="onUpdateFailedState" />
|
||||
<kubernetes-tabs
|
||||
:configuration="k8sAccessConfiguration"
|
||||
:namespace="namespace"
|
||||
class="gl-mb-5"
|
||||
@cluster-error="onClusterError"
|
||||
@loading="workloadTypesLoading = $event"
|
||||
@failed="hasFailedState = true"
|
||||
@update-failed-state="onUpdateFailedState"
|
||||
/></template>
|
||||
</gl-collapse>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -82,9 +82,10 @@ export default {
|
|||
methods: {
|
||||
countPodsByPhase(phase) {
|
||||
const filteredPods = this.k8sPods.filter((item) => item.status.phase === phase);
|
||||
if (phase === PHASE_FAILED && filteredPods.length) {
|
||||
this.$emit('failed');
|
||||
}
|
||||
|
||||
const hasFailedState = Boolean(phase === PHASE_FAILED && filteredPods.length);
|
||||
this.$emit('update-failed-state', { pods: hasFailedState });
|
||||
|
||||
return filteredPods.length;
|
||||
},
|
||||
},
|
||||
|
|
|
|||
|
|
@ -153,7 +153,7 @@ export default {
|
|||
},
|
||||
},
|
||||
i18n: {
|
||||
healthLabel: s__('Environment|Environment health'),
|
||||
healthLabel: s__('Environment|Environment status'),
|
||||
syncStatusLabel: s__('Environment|Sync status'),
|
||||
},
|
||||
badgeContainerClasses: 'gl-display-flex gl-align-items-center gl-flex-shrink-0 gl-mr-3 gl-mb-2',
|
||||
|
|
|
|||
|
|
@ -140,9 +140,7 @@ export default {
|
|||
return workloadType.items?.failed?.length > 0;
|
||||
});
|
||||
|
||||
if (failed) {
|
||||
this.$emit('failed');
|
||||
}
|
||||
this.$emit('update-failed-state', { summary: failed });
|
||||
},
|
||||
},
|
||||
i18n: {
|
||||
|
|
|
|||
|
|
@ -140,7 +140,7 @@ export default {
|
|||
:namespace="namespace"
|
||||
:configuration="configuration"
|
||||
@loading="$emit('loading', $event)"
|
||||
@failed="$emit('failed')"
|
||||
@update-failed-state="$emit('update-failed-state', $event)"
|
||||
@cluster-error="$emit('cluster-error', $event)"
|
||||
/>
|
||||
|
||||
|
|
|
|||
|
|
@ -4,7 +4,8 @@
|
|||
"AlertManagementPrometheusIntegration"
|
||||
],
|
||||
"AmazonS3ConfigurationInterface": [
|
||||
"AmazonS3ConfigurationType"
|
||||
"AmazonS3ConfigurationType",
|
||||
"InstanceAmazonS3ConfigurationType"
|
||||
],
|
||||
"BaseHeaderInterface": [
|
||||
"AuditEventStreamingHeader",
|
||||
|
|
|
|||
|
|
@ -30,7 +30,6 @@ import { initUserTracking, initDefaultTrackers } from './tracking';
|
|||
import GlFieldErrors from './gl_field_errors';
|
||||
import initUserPopovers from './user_popovers';
|
||||
import initBroadcastNotifications from './broadcast_notification';
|
||||
import { initTopNav } from './nav';
|
||||
import { initCopyCodeButton } from './behaviors/copy_code';
|
||||
import initGitlabVersionCheck from './gitlab_version_check';
|
||||
|
||||
|
|
@ -82,9 +81,6 @@ initRails();
|
|||
function deferredInitialisation() {
|
||||
const $body = $('body');
|
||||
|
||||
if (!gon.use_new_navigation) {
|
||||
initTopNav();
|
||||
}
|
||||
initBreadcrumbs();
|
||||
initPrefetchLinks('.js-prefetch-document');
|
||||
initLogoAnimation();
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import { GlTab, GlTabs, GlBadge } from '@gitlab/ui';
|
|||
import MetadataItem from '~/vue_shared/components/registry/metadata_item.vue';
|
||||
import TitleArea from '~/vue_shared/components/registry/title_area.vue';
|
||||
import ModelVersionList from '~/ml/model_registry/components/model_version_list.vue';
|
||||
import ModelVersionDetail from '~/ml/model_registry/components/model_version_detail.vue';
|
||||
import * as i18n from '../translations';
|
||||
|
||||
export default {
|
||||
|
|
@ -14,6 +15,7 @@ export default {
|
|||
GlTab,
|
||||
GlBadge,
|
||||
MetadataItem,
|
||||
ModelVersionDetail,
|
||||
},
|
||||
props: {
|
||||
model: {
|
||||
|
|
@ -28,6 +30,9 @@ export default {
|
|||
candidateCount() {
|
||||
return this.model.candidateCount || 0;
|
||||
},
|
||||
latestVersionTitle() {
|
||||
return `${i18n.LATEST_VERSION_LABEL}: ${this.model.latestVersion.version}`;
|
||||
},
|
||||
},
|
||||
i18n,
|
||||
};
|
||||
|
|
@ -50,9 +55,9 @@ export default {
|
|||
|
||||
<gl-tabs class="gl-mt-4">
|
||||
<gl-tab :title="$options.i18n.MODEL_DETAILS_TAB_LABEL">
|
||||
<h3 class="gl-font-lg">{{ $options.i18n.LATEST_VERSION_LABEL }}</h3>
|
||||
<template v-if="model.latestVersion">
|
||||
{{ model.latestVersion.version }}
|
||||
<h3 class="gl-font-lg">{{ latestVersionTitle }}</h3>
|
||||
<model-version-detail :model-version="model.latestVersion" />
|
||||
</template>
|
||||
<div v-else class="gl-text-secondary">{{ $options.i18n.NO_VERSIONS_LABEL }}</div>
|
||||
</gl-tab>
|
||||
|
|
|
|||
|
|
@ -1,16 +1,30 @@
|
|||
<script>
|
||||
import TitleArea from '~/vue_shared/components/registry/title_area.vue';
|
||||
import ModelVersionDetail from '../components/model_version_detail.vue';
|
||||
|
||||
export default {
|
||||
name: 'ShowMlModelVersionApp',
|
||||
components: {},
|
||||
components: {
|
||||
ModelVersionDetail,
|
||||
TitleArea,
|
||||
},
|
||||
props: {
|
||||
modelVersion: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
title() {
|
||||
return `${this.modelVersion.model.name} / ${this.modelVersion.version}`;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>{{ modelVersion.model.name }} - {{ modelVersion.version }}</div>
|
||||
<div>
|
||||
<title-area :title="title" />
|
||||
<model-version-detail :model-version="modelVersion" />
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,44 @@
|
|||
<script>
|
||||
import { convertToGraphQLId } from '~/graphql_shared/utils';
|
||||
import { TYPENAME_PACKAGES_PACKAGE } from '~/graphql_shared/constants';
|
||||
|
||||
export default {
|
||||
name: 'ModelVersionDetail',
|
||||
components: {
|
||||
PackageFiles: () =>
|
||||
import('~/packages_and_registries/package_registry/components/details/package_files.vue'),
|
||||
},
|
||||
props: {
|
||||
modelVersion: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
packageId() {
|
||||
return convertToGraphQLId(TYPENAME_PACKAGES_PACKAGE, this.modelVersion.packageId);
|
||||
},
|
||||
projectPath() {
|
||||
return this.modelVersion.projectPath;
|
||||
},
|
||||
packageType() {
|
||||
return 'ml_model';
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<p>
|
||||
{{ modelVersion.description }}
|
||||
</p>
|
||||
<template v-if="modelVersion.packageId">
|
||||
<package-files
|
||||
:package-id="packageId"
|
||||
:project-path="projectPath"
|
||||
:package-type="packageType"
|
||||
/>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
|
|
@ -1,95 +0,0 @@
|
|||
<script>
|
||||
import { FREQUENT_ITEMS_PROJECTS, FREQUENT_ITEMS_GROUPS } from '~/frequent_items/constants';
|
||||
import { BV_DROPDOWN_SHOW, BV_DROPDOWN_HIDE } from '~/lib/utils/constants';
|
||||
import KeepAliveSlots from '~/vue_shared/components/keep_alive_slots.vue';
|
||||
import { resetMenuItemsActive } from '../utils';
|
||||
import ResponsiveHeader from './responsive_header.vue';
|
||||
import ResponsiveHome from './responsive_home.vue';
|
||||
import TopNavContainerView from './top_nav_container_view.vue';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
KeepAliveSlots,
|
||||
ResponsiveHeader,
|
||||
ResponsiveHome,
|
||||
TopNavContainerView,
|
||||
},
|
||||
props: {
|
||||
navData: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
activeView: 'home',
|
||||
hasMobileOverlay: false,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
nav() {
|
||||
return resetMenuItemsActive(this.navData);
|
||||
},
|
||||
},
|
||||
created() {
|
||||
this.$root.$on(BV_DROPDOWN_SHOW, this.showMobileOverlay);
|
||||
this.$root.$on(BV_DROPDOWN_HIDE, this.hideMobileOverlay);
|
||||
},
|
||||
beforeDestroy() {
|
||||
this.$root.$off(BV_DROPDOWN_SHOW, this.showMobileOverlay);
|
||||
this.$root.$off(BV_DROPDOWN_HIDE, this.hideMobileOverlay);
|
||||
},
|
||||
methods: {
|
||||
onMenuItemClick({ view }) {
|
||||
if (view) {
|
||||
this.activeView = view;
|
||||
}
|
||||
},
|
||||
showMobileOverlay() {
|
||||
this.hasMobileOverlay = true;
|
||||
},
|
||||
hideMobileOverlay() {
|
||||
this.hasMobileOverlay = false;
|
||||
},
|
||||
},
|
||||
FREQUENT_ITEMS_PROJECTS,
|
||||
FREQUENT_ITEMS_GROUPS,
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<div
|
||||
class="mobile-overlay"
|
||||
:class="{ 'mobile-nav-open': hasMobileOverlay }"
|
||||
data-testid="mobile-overlay"
|
||||
></div>
|
||||
<keep-alive-slots :slot-key="activeView">
|
||||
<template #home>
|
||||
<responsive-home :nav-data="nav" @menu-item-click="onMenuItemClick" />
|
||||
</template>
|
||||
<template #projects>
|
||||
<responsive-header @menu-item-click="onMenuItemClick">
|
||||
{{ __('Projects') }}
|
||||
</responsive-header>
|
||||
<top-nav-container-view
|
||||
:frequent-items-dropdown-type="$options.FREQUENT_ITEMS_PROJECTS.namespace"
|
||||
:frequent-items-vuex-module="$options.FREQUENT_ITEMS_PROJECTS.vuexModule"
|
||||
container-class="gl-px-3"
|
||||
v-bind="nav.views.projects"
|
||||
/>
|
||||
</template>
|
||||
<template #groups>
|
||||
<responsive-header @menu-item-click="onMenuItemClick">
|
||||
{{ __('Groups') }}
|
||||
</responsive-header>
|
||||
<top-nav-container-view
|
||||
:frequent-items-dropdown-type="$options.FREQUENT_ITEMS_GROUPS.namespace"
|
||||
:frequent-items-vuex-module="$options.FREQUENT_ITEMS_GROUPS.vuexModule"
|
||||
container-class="gl-px-3"
|
||||
v-bind="nav.views.groups"
|
||||
/>
|
||||
</template>
|
||||
</keep-alive-slots>
|
||||
</div>
|
||||
</template>
|
||||
|
|
@ -1,37 +0,0 @@
|
|||
<script>
|
||||
import { GlTooltipDirective } from '@gitlab/ui';
|
||||
import TopNavMenuItem from './top_nav_menu_item.vue';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
TopNavMenuItem,
|
||||
},
|
||||
directives: {
|
||||
GlTooltip: GlTooltipDirective,
|
||||
},
|
||||
computed: {
|
||||
menuItem() {
|
||||
return {
|
||||
id: 'home',
|
||||
view: 'home',
|
||||
icon: 'chevron-lg-left',
|
||||
};
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<header class="gl-py-4 gl-display-flex gl-align-items-center">
|
||||
<top-nav-menu-item
|
||||
v-gl-tooltip="{ title: s__('TopNav|Go back') }"
|
||||
class="gl-p-3!"
|
||||
:menu-item="menuItem"
|
||||
icon-only
|
||||
@click="$emit('menu-item-click', menuItem)"
|
||||
/>
|
||||
<span class="gl-font-size-h2 gl-font-weight-bold gl-ml-2">
|
||||
<slot></slot>
|
||||
</span>
|
||||
</header>
|
||||
</template>
|
||||
|
|
@ -1,63 +0,0 @@
|
|||
<script>
|
||||
import { GlTooltipDirective } from '@gitlab/ui';
|
||||
import TopNavMenuItem from './top_nav_menu_item.vue';
|
||||
import TopNavMenuSections from './top_nav_menu_sections.vue';
|
||||
import TopNavNewDropdown from './top_nav_new_dropdown.vue';
|
||||
|
||||
const NEW_VIEW = 'new';
|
||||
const SEARCH_VIEW = 'search';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
TopNavMenuItem,
|
||||
TopNavMenuSections,
|
||||
TopNavNewDropdown,
|
||||
},
|
||||
directives: {
|
||||
GlTooltip: GlTooltipDirective,
|
||||
},
|
||||
props: {
|
||||
navData: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
menuSections() {
|
||||
return [
|
||||
{ id: 'primary', menuItems: this.navData.primary },
|
||||
{ id: 'secondary', menuItems: this.navData.secondary },
|
||||
].filter((x) => x.menuItems?.length);
|
||||
},
|
||||
newDropdownViewModel() {
|
||||
return this.navData.views[NEW_VIEW];
|
||||
},
|
||||
searchMenuItem() {
|
||||
return this.navData.views[SEARCH_VIEW];
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<header class="gl-display-flex gl-align-items-center gl-py-4 gl-pl-4">
|
||||
<h1 class="gl-m-0 gl-font-size-h2 gl-reset-color gl-mr-auto">{{ __('Menu') }}</h1>
|
||||
<top-nav-menu-item
|
||||
v-if="searchMenuItem"
|
||||
v-gl-tooltip="{ title: searchMenuItem.title }"
|
||||
class="gl-ml-3"
|
||||
:menu-item="searchMenuItem"
|
||||
icon-only
|
||||
/>
|
||||
<top-nav-new-dropdown
|
||||
v-if="newDropdownViewModel"
|
||||
v-gl-tooltip="{ title: newDropdownViewModel.title }"
|
||||
:view-model="newDropdownViewModel"
|
||||
class="gl-ml-3"
|
||||
data-testid="mobile_new_dropdown"
|
||||
/>
|
||||
</header>
|
||||
<top-nav-menu-sections class="gl-h-full" :sections="menuSections" v-on="$listeners" />
|
||||
</div>
|
||||
</template>
|
||||
|
|
@ -1,61 +0,0 @@
|
|||
<script>
|
||||
import { GlNav, GlIcon, GlNavItemDropdown, GlDropdownForm, GlTooltipDirective } from '@gitlab/ui';
|
||||
import Tracking from '~/tracking';
|
||||
import TopNavDropdownMenu from './top_nav_dropdown_menu.vue';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
GlIcon,
|
||||
GlNav,
|
||||
GlNavItemDropdown,
|
||||
GlDropdownForm,
|
||||
TopNavDropdownMenu,
|
||||
},
|
||||
directives: {
|
||||
GlTooltip: GlTooltipDirective,
|
||||
},
|
||||
props: {
|
||||
navData: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
trackToggleEvent() {
|
||||
Tracking.event(undefined, 'click_nav', {
|
||||
label: 'hamburger_menu',
|
||||
property: 'navigation_top',
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<gl-nav class="navbar-sub-nav">
|
||||
<gl-nav-item-dropdown
|
||||
v-gl-tooltip.bottom="navData.menuTooltip"
|
||||
data-testid="navbar_dropdown"
|
||||
data-qa-title="Menu"
|
||||
menu-class="gl-mt-3! gl-max-w-none! gl-max-h-none! gl-sm-w-auto! js-top-nav-dropdown-menu"
|
||||
toggle-class="top-nav-toggle js-top-nav-dropdown-toggle gl-px-3!"
|
||||
no-flip
|
||||
no-caret
|
||||
@toggle="trackToggleEvent"
|
||||
>
|
||||
<template #button-content>
|
||||
<gl-icon name="hamburger" />
|
||||
<span v-if="navData.menuTitle" class="gl-ml-3">
|
||||
{{ navData.menuTitle }}
|
||||
</span>
|
||||
</template>
|
||||
<gl-dropdown-form>
|
||||
<top-nav-dropdown-menu
|
||||
:primary="navData.primary"
|
||||
:secondary="navData.secondary"
|
||||
:views="navData.views"
|
||||
/>
|
||||
</gl-dropdown-form>
|
||||
</gl-nav-item-dropdown>
|
||||
</gl-nav>
|
||||
</template>
|
||||
|
|
@ -1,81 +0,0 @@
|
|||
<script>
|
||||
import FrequentItemsApp from '~/frequent_items/components/app.vue';
|
||||
import eventHub from '~/frequent_items/event_hub';
|
||||
import VuexModuleProvider from '~/vue_shared/components/vuex_module_provider.vue';
|
||||
import TopNavMenuSections from './top_nav_menu_sections.vue';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
FrequentItemsApp,
|
||||
TopNavMenuSections,
|
||||
VuexModuleProvider,
|
||||
},
|
||||
inheritAttrs: false,
|
||||
props: {
|
||||
frequentItemsVuexModule: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
frequentItemsDropdownType: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
currentItem: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
containerClass: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: '',
|
||||
},
|
||||
linksPrimary: {
|
||||
type: Array,
|
||||
required: false,
|
||||
default: () => [],
|
||||
},
|
||||
linksSecondary: {
|
||||
type: Array,
|
||||
required: false,
|
||||
default: () => [],
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
menuSections() {
|
||||
return [
|
||||
{ id: 'primary', menuItems: this.linksPrimary },
|
||||
{ id: 'secondary', menuItems: this.linksSecondary },
|
||||
].filter((x) => x.menuItems?.length);
|
||||
},
|
||||
currentItemTimestamped() {
|
||||
return {
|
||||
...this.currentItem,
|
||||
lastAccessedOn: Date.now(),
|
||||
};
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
// For historic reasons, the frequent-items-app component requires this too start up.
|
||||
this.$nextTick(() => {
|
||||
eventHub.$emit(`${this.frequentItemsDropdownType}-dropdownOpen`);
|
||||
});
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="top-nav-container-view gl-display-flex gl-flex-direction-column">
|
||||
<div
|
||||
class="frequent-items-dropdown-container gl-w-auto"
|
||||
:class="containerClass"
|
||||
data-testid="frequent-items-container"
|
||||
>
|
||||
<div class="frequent-items-dropdown-content gl-w-full! gl-pt-0!">
|
||||
<vuex-module-provider :vuex-module="frequentItemsVuexModule">
|
||||
<frequent-items-app :current-item="currentItemTimestamped" v-bind="$attrs" />
|
||||
</vuex-module-provider>
|
||||
</div>
|
||||
</div>
|
||||
<top-nav-menu-sections class="gl-mt-auto" :sections="menuSections" with-top-border />
|
||||
</div>
|
||||
</template>
|
||||
|
|
@ -1,107 +0,0 @@
|
|||
<script>
|
||||
import { cloneDeep } from 'lodash';
|
||||
import { FREQUENT_ITEMS_PROJECTS, FREQUENT_ITEMS_GROUPS } from '~/frequent_items/constants';
|
||||
import KeepAliveSlots from '~/vue_shared/components/keep_alive_slots.vue';
|
||||
import TopNavContainerView from './top_nav_container_view.vue';
|
||||
import TopNavMenuSections from './top_nav_menu_sections.vue';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
KeepAliveSlots,
|
||||
TopNavContainerView,
|
||||
TopNavMenuSections,
|
||||
},
|
||||
props: {
|
||||
primary: {
|
||||
type: Array,
|
||||
required: false,
|
||||
default: () => [],
|
||||
},
|
||||
secondary: {
|
||||
type: Array,
|
||||
required: false,
|
||||
default: () => [],
|
||||
},
|
||||
views: {
|
||||
type: Object,
|
||||
required: false,
|
||||
default: () => ({}),
|
||||
},
|
||||
},
|
||||
data() {
|
||||
// It's expected that primary & secondary never change, so these are treated as "init" props.
|
||||
// We need to clone so that we can mutate the data without mutating the props
|
||||
const menuSections = [
|
||||
{ id: 'primary', menuItems: cloneDeep(this.primary) },
|
||||
{ id: 'secondary', menuItems: cloneDeep(this.secondary) },
|
||||
].filter((x) => x.menuItems?.length);
|
||||
|
||||
return {
|
||||
menuSections,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
allMenuItems() {
|
||||
return this.menuSections.flatMap((x) => x.menuItems);
|
||||
},
|
||||
activeView() {
|
||||
const active = this.allMenuItems.find((x) => x.active);
|
||||
|
||||
return active?.view;
|
||||
},
|
||||
menuClass() {
|
||||
if (!this.activeView) {
|
||||
return 'gl-w-full';
|
||||
}
|
||||
|
||||
return '';
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
onMenuItemClick({ id }) {
|
||||
this.allMenuItems.forEach((menuItem) => {
|
||||
this.$set(menuItem, 'active', id === menuItem.id);
|
||||
});
|
||||
},
|
||||
},
|
||||
FREQUENT_ITEMS_PROJECTS,
|
||||
FREQUENT_ITEMS_GROUPS,
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="gl-display-flex gl-align-items-stretch">
|
||||
<div
|
||||
class="gl-w-grid-size-30 gl-flex-shrink-0 gl-bg-gray-10 gl-p-3"
|
||||
:class="menuClass"
|
||||
data-testid="menu-sidebar"
|
||||
>
|
||||
<top-nav-menu-sections
|
||||
:sections="menuSections"
|
||||
:is-primary-section="true"
|
||||
@menu-item-click="onMenuItemClick"
|
||||
/>
|
||||
</div>
|
||||
<keep-alive-slots
|
||||
v-show="activeView"
|
||||
:slot-key="activeView"
|
||||
class="gl-w-grid-size-40 gl-overflow-hidden gl-p-3"
|
||||
data-testid="menu-subview"
|
||||
>
|
||||
<template #projects>
|
||||
<top-nav-container-view
|
||||
:frequent-items-dropdown-type="$options.FREQUENT_ITEMS_PROJECTS.namespace"
|
||||
:frequent-items-vuex-module="$options.FREQUENT_ITEMS_PROJECTS.vuexModule"
|
||||
v-bind="views.projects"
|
||||
/>
|
||||
</template>
|
||||
<template #groups>
|
||||
<top-nav-container-view
|
||||
:frequent-items-dropdown-type="$options.FREQUENT_ITEMS_GROUPS.namespace"
|
||||
:frequent-items-vuex-module="$options.FREQUENT_ITEMS_GROUPS.vuexModule"
|
||||
v-bind="views.groups"
|
||||
/>
|
||||
</template>
|
||||
</keep-alive-slots>
|
||||
</div>
|
||||
</template>
|
||||
|
|
@ -1,52 +0,0 @@
|
|||
<script>
|
||||
import { GlButton, GlIcon } from '@gitlab/ui';
|
||||
import { kebabCase, mapKeys } from 'lodash';
|
||||
|
||||
const getDataKey = (key) => `data-${kebabCase(key)}`;
|
||||
|
||||
const ACTIVE_CLASS = 'gl-shadow-none! gl-font-weight-bold! active';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
GlButton,
|
||||
GlIcon,
|
||||
},
|
||||
props: {
|
||||
menuItem: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
iconOnly: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
dataAttrs() {
|
||||
return mapKeys(this.menuItem.data || {}, (value, key) => getDataKey(key));
|
||||
},
|
||||
},
|
||||
ACTIVE_CLASS,
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<gl-button
|
||||
category="tertiary"
|
||||
:href="menuItem.href"
|
||||
class="top-nav-menu-item gl-display-block gl-pr-3!"
|
||||
:class="[menuItem.css_class, { [$options.ACTIVE_CLASS]: menuItem.active }]"
|
||||
:aria-label="menuItem.title"
|
||||
v-bind="dataAttrs"
|
||||
v-on="$listeners"
|
||||
>
|
||||
<span class="gl-display-flex">
|
||||
<gl-icon v-if="menuItem.icon" :name="menuItem.icon" :class="{ 'gl-mr-3!': !iconOnly }" />
|
||||
<template v-if="!iconOnly">
|
||||
{{ menuItem.title }}
|
||||
<gl-icon v-if="menuItem.view" name="chevron-right" class="gl-ml-auto" />
|
||||
</template>
|
||||
</span>
|
||||
</gl-button>
|
||||
</template>
|
||||
|
|
@ -1,82 +0,0 @@
|
|||
<script>
|
||||
import TopNavMenuItem from './top_nav_menu_item.vue';
|
||||
|
||||
const BORDER_CLASSES = 'gl-pt-3 gl-border-1 gl-border-t-solid';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
TopNavMenuItem,
|
||||
},
|
||||
props: {
|
||||
sections: {
|
||||
type: Array,
|
||||
required: true,
|
||||
},
|
||||
withTopBorder: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false,
|
||||
},
|
||||
isPrimarySection: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
onClick(menuItem) {
|
||||
// If we're a link, let's just do the default behavior so the view won't change
|
||||
if (menuItem.href) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.$emit('menu-item-click', menuItem);
|
||||
},
|
||||
getMenuSectionClasses(index) {
|
||||
// This is a method instead of a computed so we don't have to incur the cost of
|
||||
// creating a whole new array/object.
|
||||
const hasBorder = this.withTopBorder || index > 0;
|
||||
return {
|
||||
[BORDER_CLASSES]: hasBorder,
|
||||
'gl-border-gray-100': hasBorder && this.isPrimarySection,
|
||||
'gl-border-gray-50': hasBorder && !this.isPrimarySection,
|
||||
'gl-mt-3': index > 0,
|
||||
};
|
||||
},
|
||||
},
|
||||
// Expose for unit tests
|
||||
BORDER_CLASSES,
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="gl-display-flex gl-align-items-stretch gl-flex-direction-column">
|
||||
<div
|
||||
v-for="({ id, menuItems }, sectionIndex) in sections"
|
||||
:key="id"
|
||||
:class="getMenuSectionClasses(sectionIndex)"
|
||||
data-testid="menu-section"
|
||||
>
|
||||
<template v-for="(menuItem, menuItemIndex) in menuItems">
|
||||
<strong
|
||||
v-if="menuItem.type == 'header'"
|
||||
:key="menuItem.title"
|
||||
class="gl-px-4 gl-py-2 gl-text-gray-900 gl-display-block"
|
||||
:class="{ 'gl-pt-3!': menuItemIndex > 0 }"
|
||||
data-testid="menu-header"
|
||||
>
|
||||
{{ menuItem.title }}
|
||||
</strong>
|
||||
<top-nav-menu-item
|
||||
v-else
|
||||
:key="menuItem.id"
|
||||
:menu-item="menuItem"
|
||||
data-testid="menu-item"
|
||||
class="gl-w-full"
|
||||
:class="{ 'gl-mt-1': menuItemIndex > 0 }"
|
||||
@click="onClick(menuItem)"
|
||||
/>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
|
@ -1,73 +0,0 @@
|
|||
<script>
|
||||
import { GlDropdown, GlDropdownDivider, GlDropdownItem, GlDropdownSectionHeader } from '@gitlab/ui';
|
||||
import InviteMembersTrigger from '~/invite_members/components/invite_members_trigger.vue';
|
||||
import { TOP_NAV_INVITE_MEMBERS_COMPONENT } from '~/invite_members/constants';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
GlDropdown,
|
||||
GlDropdownDivider,
|
||||
GlDropdownItem,
|
||||
GlDropdownSectionHeader,
|
||||
InviteMembersTrigger,
|
||||
},
|
||||
props: {
|
||||
viewModel: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
sections() {
|
||||
return this.viewModel.menu_sections || [];
|
||||
},
|
||||
showHeaders() {
|
||||
return this.sections.length > 1;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
isInvitedMembers(menuItem) {
|
||||
return menuItem.component === TOP_NAV_INVITE_MEMBERS_COMPONENT;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<gl-dropdown
|
||||
toggle-class="top-nav-menu-item"
|
||||
icon="plus"
|
||||
:text="viewModel.title"
|
||||
category="tertiary"
|
||||
text-sr-only
|
||||
no-caret
|
||||
right
|
||||
>
|
||||
<template v-for="({ title, menu_items }, index) in sections">
|
||||
<gl-dropdown-divider v-if="index > 0" :key="`${index}_divider`" data-testid="divider" />
|
||||
<gl-dropdown-section-header v-if="showHeaders" :key="`${index}_header`" data-testid="header">
|
||||
{{ title }}
|
||||
</gl-dropdown-section-header>
|
||||
<template v-for="menuItem in menu_items">
|
||||
<invite-members-trigger
|
||||
v-if="isInvitedMembers(menuItem)"
|
||||
:key="`${index}_item_${menuItem.id}`"
|
||||
:trigger-element="`dropdown-${menuItem.data.trigger_element}`"
|
||||
:display-text="menuItem.title"
|
||||
:icon="menuItem.icon"
|
||||
:trigger-source="menuItem.data.trigger_source"
|
||||
/>
|
||||
<gl-dropdown-item
|
||||
v-else
|
||||
:key="`${index}_item_${menuItem.id}`"
|
||||
link-class="top-nav-menu-item"
|
||||
:href="menuItem.href"
|
||||
data-testid="item"
|
||||
:data-qa-selector="`${menuItem.title.toLowerCase().replace(' ', '_')}_mobile_button`"
|
||||
>
|
||||
{{ menuItem.title }}
|
||||
</gl-dropdown-item>
|
||||
</template>
|
||||
</template>
|
||||
</gl-dropdown>
|
||||
</template>
|
||||
|
|
@ -1,31 +0,0 @@
|
|||
// TODO: With the combined_menu feature flag removed, there's likely a better
|
||||
// way to slice up the async import (i.e., include trigger in main bundle, but
|
||||
// async import subviews. Don't do this at the cost of UX).
|
||||
// See https://gitlab.com/gitlab-org/gitlab/-/issues/336042
|
||||
const importModule = () => import(/* webpackChunkName: 'top_nav' */ './mount');
|
||||
|
||||
const tryMountTopNav = async () => {
|
||||
const el = document.getElementById('js-top-nav');
|
||||
|
||||
if (!el) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { mountTopNav } = await importModule();
|
||||
|
||||
mountTopNav(el);
|
||||
};
|
||||
|
||||
const tryMountTopNavResponsive = async () => {
|
||||
const el = document.getElementById('js-top-nav-responsive');
|
||||
|
||||
if (!el) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { mountTopNavResponsive } = await importModule();
|
||||
|
||||
mountTopNavResponsive(el);
|
||||
};
|
||||
|
||||
export const initTopNav = async () => Promise.all([tryMountTopNav(), tryMountTopNavResponsive()]);
|
||||
|
|
@ -1,30 +0,0 @@
|
|||
import Vue from 'vue';
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import Vuex from 'vuex';
|
||||
import ResponsiveApp from './components/responsive_app.vue';
|
||||
import App from './components/top_nav_app.vue';
|
||||
import { createStore } from './stores';
|
||||
|
||||
Vue.use(Vuex);
|
||||
|
||||
const mount = (el, Component) => {
|
||||
const viewModel = JSON.parse(el.dataset.viewModel);
|
||||
const store = createStore();
|
||||
|
||||
return new Vue({
|
||||
el,
|
||||
name: 'TopNavRoot',
|
||||
store,
|
||||
render(h) {
|
||||
return h(Component, {
|
||||
props: {
|
||||
navData: viewModel,
|
||||
},
|
||||
});
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export const mountTopNav = (el) => mount(el, App);
|
||||
|
||||
export const mountTopNavResponsive = (el) => mount(el, ResponsiveApp);
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
// eslint-disable-next-line no-restricted-imports
|
||||
import Vuex from 'vuex';
|
||||
import { createStoreOptions } from '~/frequent_items/store';
|
||||
|
||||
export const createStore = () => new Vuex.Store(createStoreOptions());
|
||||
|
|
@ -1 +0,0 @@
|
|||
export * from './reset_menu_items_active';
|
||||
|
|
@ -1,14 +0,0 @@
|
|||
const resetActiveInArray = (arr) => arr?.map((menuItem) => ({ ...menuItem, active: false }));
|
||||
|
||||
/**
|
||||
* This method sets `active: false` for the menu items within the given nav data.
|
||||
*
|
||||
* @returns navData with the menu items updated with `active: false`
|
||||
*/
|
||||
export const resetMenuItemsActive = ({ primary, secondary, ...navData }) => {
|
||||
return {
|
||||
...navData,
|
||||
primary: resetActiveInArray(primary),
|
||||
secondary: resetActiveInArray(secondary),
|
||||
};
|
||||
};
|
||||
|
|
@ -251,10 +251,26 @@ async function fetchOperations(operationsUrl, serviceName) {
|
|||
}
|
||||
}
|
||||
|
||||
async function fetchMetrics(metricsUrl) {
|
||||
async function fetchMetrics(metricsUrl, { filters = {}, limit } = {}) {
|
||||
try {
|
||||
const params = new URLSearchParams();
|
||||
|
||||
if (Array.isArray(filters.search)) {
|
||||
const searchPrefix = filters.search
|
||||
.map((f) => f.value)
|
||||
.join(' ')
|
||||
.trim();
|
||||
|
||||
if (searchPrefix) {
|
||||
params.append('starts_with', searchPrefix);
|
||||
if (limit) {
|
||||
params.append('limit', limit);
|
||||
}
|
||||
}
|
||||
}
|
||||
const { data } = await axios.get(metricsUrl, {
|
||||
withCredentials: true,
|
||||
params,
|
||||
});
|
||||
if (!Array.isArray(data.metrics)) {
|
||||
throw new Error('metrics are missing/invalid in the response'); // eslint-disable-line @gitlab/require-i18n-strings
|
||||
|
|
@ -299,6 +315,6 @@ export function buildClient(config) {
|
|||
fetchTrace: (traceId) => fetchTrace(tracingUrl, traceId),
|
||||
fetchServices: () => fetchServices(servicesUrl),
|
||||
fetchOperations: (serviceName) => fetchOperations(operationsUrl, serviceName),
|
||||
fetchMetrics: () => fetchMetrics(metricsUrl),
|
||||
fetchMetrics: (options) => fetchMetrics(metricsUrl, options),
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { initSimpleApp } from '~/helpers/init_simple_app_helper';
|
||||
import { ShowMlModelVersion } from '~/ml/model_registry/apps';
|
||||
|
||||
initSimpleApp('#js-mount-show-ml-model-version', ShowMlModelVersion);
|
||||
initSimpleApp('#js-mount-show-ml-model-version', ShowMlModelVersion, { withApolloProvider: true });
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@ import { nextTick } from 'vue';
|
|||
import { GlForm, GlButton } from '@gitlab/ui';
|
||||
import { VARIANT_DANGER, VARIANT_INFO, createAlert } from '~/alert';
|
||||
import axios from '~/lib/utils/axios_utils';
|
||||
import { readFileAsDataURL } from '~/lib/utils/file_utility';
|
||||
import SetStatusForm from '~/set_status_modal/set_status_form.vue';
|
||||
import SettingsBlock from '~/packages_and_registries/shared/components/settings_block.vue';
|
||||
import { isUserBusy, computedClearStatusAfterValue } from '~/set_status_modal/utils';
|
||||
|
|
@ -106,20 +105,12 @@ export default {
|
|||
this.updateProfileSettings = false;
|
||||
}
|
||||
},
|
||||
async syncHeaderAvatars() {
|
||||
const dataURL = await readFileAsDataURL(this.avatarBlob);
|
||||
|
||||
const elements = gon?.use_new_navigation
|
||||
? ['[data-testid="user-dropdown"] .gl-avatar']
|
||||
: ['.header-user-avatar', '.js-sidebar-user-avatar'];
|
||||
|
||||
elements.forEach((selector) => {
|
||||
const node = document.querySelector(selector);
|
||||
if (!node) return;
|
||||
|
||||
node.setAttribute('src', dataURL);
|
||||
node.setAttribute('srcset', dataURL);
|
||||
});
|
||||
syncHeaderAvatars() {
|
||||
document.dispatchEvent(
|
||||
new CustomEvent('userAvatar:update', {
|
||||
detail: { url: URL.createObjectURL(this.avatarBlob) },
|
||||
}),
|
||||
);
|
||||
},
|
||||
onBlobChange(blob) {
|
||||
this.avatarBlob = blob;
|
||||
|
|
|
|||
|
|
@ -89,12 +89,9 @@ export default class Profile {
|
|||
}
|
||||
|
||||
updateHeaderAvatar() {
|
||||
if (gon?.use_new_navigation) {
|
||||
$('[data-testid="user-dropdown"] .gl-avatar').attr('src', this.avatarGlCrop.dataURL);
|
||||
} else {
|
||||
$('.header-user-avatar').attr('src', this.avatarGlCrop.dataURL);
|
||||
$('.js-sidebar-user-avatar').attr('src', this.avatarGlCrop.dataURL);
|
||||
}
|
||||
const url = URL.createObjectURL(this.avatarGlCrop.getBlob());
|
||||
|
||||
document.dispatchEvent(new CustomEvent('userAvatar:update', { detail: { url } }));
|
||||
}
|
||||
|
||||
setRepoRadio() {
|
||||
|
|
|
|||
|
|
@ -36,7 +36,10 @@ export default {
|
|||
<span>
|
||||
<gl-button
|
||||
v-gl-modal="$options.uploadBlobModalId"
|
||||
variant="link"
|
||||
icon="upload"
|
||||
class="stat-link gl-px-0!"
|
||||
button-text-classes="gl-ml-2"
|
||||
data-testid="upload-file-button"
|
||||
>{{ __('Upload File') }}</gl-button
|
||||
>
|
||||
|
|
|
|||
|
|
@ -269,6 +269,7 @@ export default {
|
|||
:invalid-feedback="form.fields['commit_message'].feedback"
|
||||
>
|
||||
<gl-form-textarea
|
||||
id="commit_message"
|
||||
ref="message"
|
||||
v-model="form.fields['commit_message'].value"
|
||||
v-validation:[form.showValidation]
|
||||
|
|
@ -289,6 +290,7 @@ export default {
|
|||
:invalid-feedback="form.fields['branch_name'].feedback"
|
||||
>
|
||||
<gl-form-input
|
||||
id="branch_name"
|
||||
v-model="form.fields['branch_name'].value"
|
||||
v-validation:[form.showValidation]
|
||||
:state="form.fields['branch_name'].state"
|
||||
|
|
|
|||
|
|
@ -59,9 +59,13 @@ export default {
|
|||
data() {
|
||||
return {
|
||||
setStatusModalReady: false,
|
||||
updatedAvatarUrl: null,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
avatarUrl() {
|
||||
return this.updatedAvatarUrl || this.data.avatar_url;
|
||||
},
|
||||
toggleText() {
|
||||
return sprintf(__('%{user} user’s menu'), { user: this.data.name });
|
||||
},
|
||||
|
|
@ -190,7 +194,16 @@ export default {
|
|||
};
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
document.addEventListener('userAvatar:update', this.updateAvatar);
|
||||
},
|
||||
unmounted() {
|
||||
document.removeEventListener('userAvatar:update', this.updateAvatar);
|
||||
},
|
||||
methods: {
|
||||
updateAvatar(event) {
|
||||
this.updatedAvatarUrl = event.detail?.url;
|
||||
},
|
||||
onShow() {
|
||||
this.initBuyCIMinsCallout();
|
||||
},
|
||||
|
|
@ -240,7 +253,7 @@ export default {
|
|||
<gl-avatar
|
||||
:size="24"
|
||||
:entity-name="data.name"
|
||||
:src="data.avatar_url"
|
||||
:src="avatarUrl"
|
||||
aria-hidden="true"
|
||||
data-testid="user-avatar-content"
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -26,6 +26,22 @@ const sortItemsByFrequencyAndLastAccess = (items) =>
|
|||
return 0;
|
||||
});
|
||||
|
||||
/**
|
||||
* Returns the most frequently visited items.
|
||||
*
|
||||
* @param {Array} items - A list of items retrieved from the local storage
|
||||
* @param {Number} maxCount - The maximum number of items to be returned
|
||||
* @returns {Array}
|
||||
*/
|
||||
export const getTopFrequentItems = (items, maxCount) => {
|
||||
if (!Array.isArray(items)) return [];
|
||||
|
||||
const frequentItems = items.filter((item) => item.frequency >= FREQUENT_ITEMS.ELIGIBLE_FREQUENCY);
|
||||
sortItemsByFrequencyAndLastAccess(frequentItems);
|
||||
|
||||
return frequentItems.slice(0, maxCount);
|
||||
};
|
||||
|
||||
/**
|
||||
* This tracks projects' and groups' visits in order to suggest a list of frequently visited
|
||||
* entities to the user. The suggestion logic is implemented server-side and computed items can be
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
export const COMPONENTS = {
|
||||
conflict: () => import('./conflicts.vue'),
|
||||
unresolved_discussions: () => import('./unresolved_discussions.vue'),
|
||||
discussions_not_resolved: () => import('./unresolved_discussions.vue'),
|
||||
need_rebase: () => import('./rebase.vue'),
|
||||
default: () => import('./message.vue'),
|
||||
};
|
||||
|
|
|
|||
|
|
@ -134,7 +134,7 @@
|
|||
|
||||
.stat-text,
|
||||
.stat-link {
|
||||
padding: $gl-btn-vert-padding 0;
|
||||
padding: $gl-btn-vert-padding;
|
||||
background-color: transparent;
|
||||
font-size: $gl-font-size;
|
||||
line-height: $gl-btn-line-height;
|
||||
|
|
@ -149,7 +149,6 @@
|
|||
&:hover,
|
||||
&:focus {
|
||||
text-decoration: underline;
|
||||
border-bottom: 0;
|
||||
}
|
||||
|
||||
.project-stat-value {
|
||||
|
|
@ -159,13 +158,6 @@
|
|||
.icon {
|
||||
color: var(--gray-500, $gl-text-color-secondary);
|
||||
}
|
||||
|
||||
.add-license-link {
|
||||
&,
|
||||
.icon {
|
||||
color: var(--blue-600, $blue-600);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.btn {
|
||||
|
|
@ -186,3 +178,60 @@
|
|||
color: var(--gl-text-color, $gl-text-color);
|
||||
}
|
||||
}
|
||||
|
||||
// FF :project_overview_reorg enabled
|
||||
.project-page-indicator:not(.hidden) + .project-page-layout {
|
||||
--project-overview-sidebar-width: 290px;
|
||||
|
||||
@include media-breakpoint-up(lg) {
|
||||
display: grid;
|
||||
grid-template-columns: auto var(--project-overview-sidebar-width);
|
||||
gap: 2rem;
|
||||
|
||||
.project-page-layout-content,
|
||||
.project-page-layout-sidebar {
|
||||
min-width: 1px;
|
||||
}
|
||||
|
||||
.project-page-layout-sidebar {
|
||||
order: 2;
|
||||
overflow-x: clip;
|
||||
margin-right: -$gl-padding-8;
|
||||
}
|
||||
|
||||
.project-page-sidebar {
|
||||
position: sticky;
|
||||
top: calc(#{$calc-application-header-height} + #{$gl-spacing-scale-4});
|
||||
width: calc(100% + 100px);
|
||||
height: calc(
|
||||
#{$calc-application-viewport-height} - #{$gl-spacing-scale-4}
|
||||
);
|
||||
padding-inline: $gl-padding-4;
|
||||
overflow-y: scroll;
|
||||
overflow-x: hidden;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
|
||||
.project-page-sidebar-block {
|
||||
width: calc(var(--project-overview-sidebar-width) - 1px);
|
||||
|
||||
&:first-of-type {
|
||||
padding-top: $gl-spacing-scale-1;
|
||||
}
|
||||
}
|
||||
|
||||
.nav {
|
||||
> li {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.btn {
|
||||
justify-content: flex-start;
|
||||
|
||||
&:not(.btn-dashed) {
|
||||
box-shadow: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -235,8 +235,7 @@
|
|||
}
|
||||
|
||||
.repository-languages-bar {
|
||||
height: 8px;
|
||||
margin-bottom: $gl-padding;
|
||||
height: 0.5rem;
|
||||
background-color: var(--white, $white);
|
||||
border-radius: $border-radius-default;
|
||||
|
||||
|
|
|
|||
|
|
@ -29,8 +29,13 @@ module Projects
|
|||
def latest_version_view_model
|
||||
return unless model.latest_version
|
||||
|
||||
model_version = model.latest_version
|
||||
|
||||
{
|
||||
version: model.latest_version.version
|
||||
version: model_version.version,
|
||||
description: model_version.description,
|
||||
project_path: project_path(model_version.project),
|
||||
package_id: model_version.package_id
|
||||
}
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -18,6 +18,9 @@ module Projects
|
|||
id: model_version.id,
|
||||
version: model_version.version,
|
||||
path: model_version.path,
|
||||
description: model_version.description,
|
||||
project_path: project_path(model_version.project),
|
||||
package_id: model_version.package_id,
|
||||
model: {
|
||||
name: model.name,
|
||||
path: model.path
|
||||
|
|
|
|||
|
|
@ -11,12 +11,22 @@ module StatAnchorsHelper
|
|||
|
||||
private
|
||||
|
||||
def new_button_attribute(anchor)
|
||||
anchor.class_modifier || 'btn-link gl-text-blue-500!'
|
||||
end
|
||||
|
||||
def button_attribute(anchor)
|
||||
anchor.class_modifier || 'btn-dashed'
|
||||
end
|
||||
|
||||
def extra_classes(anchor)
|
||||
if anchor.is_link
|
||||
if Feature.enabled?(:project_overview_reorg)
|
||||
if anchor.is_link
|
||||
'stat-link gl-px-0! gl-pb-2!'
|
||||
else
|
||||
"stat-link gl-px-0! gl-pb-2! #{new_button_attribute(anchor)}"
|
||||
end
|
||||
elsif anchor.is_link
|
||||
'stat-link'
|
||||
else
|
||||
"gl-button btn #{button_attribute(anchor)}"
|
||||
|
|
|
|||
|
|
@ -86,7 +86,7 @@ class CommitStatus < Ci::ApplicationRecord
|
|||
scope :for_project_paths, -> (paths) do
|
||||
# Pluck is used to split this query. Splitting the query is required for database decomposition for `ci_*` tables.
|
||||
# https://docs.gitlab.com/ee/development/database/transaction_guidelines.html#database-decomposition-and-sharding
|
||||
project_ids = Project.where_full_path_in(Array(paths)).pluck(:id)
|
||||
project_ids = Project.where_full_path_in(Array(paths), use_includes: false).pluck(:id)
|
||||
|
||||
for_project(project_ids)
|
||||
end
|
||||
|
|
|
|||
|
|
@ -51,8 +51,15 @@ class ProjectMember < Member
|
|||
end
|
||||
|
||||
def permissible_access_level_roles_for_project_access_token(current_user, project)
|
||||
permissible_access_level_roles(current_user, project).filter do |_, value|
|
||||
value <= project.project_authorizations.find_by(user: current_user).access_level
|
||||
if Ability.allowed?(current_user, :manage_owners, project)
|
||||
Gitlab::Access.options_with_owner
|
||||
else
|
||||
max_access_level = project.team.max_member_access(current_user.id)
|
||||
return {} unless max_access_level.present?
|
||||
|
||||
ProjectMember.access_level_roles.filter do |_, value|
|
||||
value <= max_access_level
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -21,8 +21,16 @@ class ProjectPresenter < Gitlab::View::Presenter::Delegated
|
|||
AnchorData = Struct.new(:is_link, :label, :link, :class_modifier, :icon, :itemprop, :data)
|
||||
MAX_TOPICS_TO_SHOW = 3
|
||||
|
||||
def statistic_icon(icon_name = 'plus-square-o')
|
||||
sprite_icon(icon_name, css_class: 'icon gl-mr-2 gl-text-gray-500')
|
||||
def statistic_default_class_list
|
||||
Feature.enabled?(:project_overview_reorg) ? 'icon gl-mr-3 gl-text-gray-500' : 'icon gl-mr-2 gl-text-gray-500'
|
||||
end
|
||||
|
||||
def statistic_default_icon
|
||||
Feature.enabled?(:project_overview_reorg) ? 'plus' : 'plus-square-o'
|
||||
end
|
||||
|
||||
def statistic_icon(icon_name = statistic_default_icon, class_list = statistic_default_class_list)
|
||||
sprite_icon(icon_name, css_class: class_list)
|
||||
end
|
||||
|
||||
def statistics_anchors(show_auto_devops_callout:)
|
||||
|
|
@ -288,13 +296,19 @@ class ProjectPresenter < Gitlab::View::Presenter::Delegated
|
|||
if can_current_user_push_to_default_branch?
|
||||
new_file_path = empty_repo? ? ide_edit_path(project, default_branch_or_main) : project_new_blob_path(project, default_branch_or_main)
|
||||
|
||||
AnchorData.new(false, statistic_icon + _('New file'), new_file_path, 'btn-dashed')
|
||||
if Feature.enabled?(:project_overview_reorg)
|
||||
AnchorData.new(false, statistic_icon('plus', 'gl-text-blue-500! gl-mr-3') + _('New file'), new_file_path)
|
||||
else
|
||||
AnchorData.new(false, statistic_icon + _('New file'), new_file_path, 'btn-dashed')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def readme_anchor_data
|
||||
if can_current_user_push_to_default_branch? && readme_path.nil?
|
||||
AnchorData.new(false, statistic_icon + _('Add README'), empty_repo? ? add_readme_ide_path : add_readme_path)
|
||||
icon = Feature.enabled?(:project_overview_reorg) ? statistic_icon('plus', 'gl-text-blue-500! gl-mr-3') : statistic_icon
|
||||
label = icon + _('Add README')
|
||||
AnchorData.new(false, label, empty_repo? ? add_readme_ide_path : add_readme_path)
|
||||
elsif readme_path
|
||||
AnchorData.new(
|
||||
false,
|
||||
|
|
@ -308,9 +322,11 @@ class ProjectPresenter < Gitlab::View::Presenter::Delegated
|
|||
|
||||
def changelog_anchor_data
|
||||
if can_current_user_push_to_default_branch? && repository.changelog.blank?
|
||||
icon = Feature.enabled?(:project_overview_reorg) ? statistic_icon('plus', 'gl-mr-3') : statistic_icon
|
||||
label = icon + _('Add CHANGELOG')
|
||||
AnchorData.new(
|
||||
false,
|
||||
statistic_icon + _('Add CHANGELOG'),
|
||||
label,
|
||||
empty_repo? ? add_changelog_ide_path : add_changelog_path
|
||||
)
|
||||
elsif repository.changelog.present?
|
||||
|
|
@ -336,9 +352,11 @@ class ProjectPresenter < Gitlab::View::Presenter::Delegated
|
|||
'license'
|
||||
)
|
||||
elsif can_current_user_push_to_default_branch?
|
||||
icon = Feature.enabled?(:project_overview_reorg) ? statistic_icon('plus', 'gl-text-blue-500! gl-mr-3') : statistic_icon
|
||||
label = icon + _('Add LICENSE')
|
||||
AnchorData.new(
|
||||
false,
|
||||
content_tag(:span, statistic_icon + _('Add LICENSE'), class: 'add-license-link d-flex'),
|
||||
content_tag(:span, label, class: 'add-license-link d-flex'),
|
||||
empty_repo? ? add_license_ide_path : add_license_path
|
||||
)
|
||||
end
|
||||
|
|
@ -346,9 +364,11 @@ class ProjectPresenter < Gitlab::View::Presenter::Delegated
|
|||
|
||||
def contribution_guide_anchor_data
|
||||
if can_current_user_push_to_default_branch? && repository.contribution_guide.blank?
|
||||
icon = Feature.enabled?(:project_overview_reorg) ? statistic_icon('plus', 'gl-text-blue-500! gl-mr-3') : statistic_icon
|
||||
label = icon + _('Add CONTRIBUTING')
|
||||
AnchorData.new(
|
||||
false,
|
||||
statistic_icon + _('Add CONTRIBUTING'),
|
||||
label,
|
||||
empty_repo? ? add_contribution_guide_ide_path : add_contribution_guide_path
|
||||
)
|
||||
elsif repository.contribution_guide.present?
|
||||
|
|
@ -387,7 +407,11 @@ class ProjectPresenter < Gitlab::View::Presenter::Delegated
|
|||
def kubernetes_cluster_anchor_data
|
||||
if can_instantiate_cluster?
|
||||
if clusters.empty?
|
||||
AnchorData.new(false, statistic_icon + _('Add Kubernetes cluster'), project_clusters_path(project))
|
||||
if Feature.enabled?(:project_overview_reorg)
|
||||
AnchorData.new(false, content_tag(:span, statistic_icon('plus', 'gl-mr-3') + _('Add Kubernetes cluster'), class: 'btn-link'), project_clusters_path(project))
|
||||
else
|
||||
AnchorData.new(false, content_tag(:span, statistic_icon + _('Add Kubernetes cluster')), project_clusters_path(project))
|
||||
end
|
||||
else
|
||||
cluster_link = clusters.count == 1 ? project_cluster_path(project, clusters.first) : project_clusters_path(project)
|
||||
|
||||
|
|
@ -402,7 +426,7 @@ class ProjectPresenter < Gitlab::View::Presenter::Delegated
|
|||
if cicd_missing?
|
||||
AnchorData.new(false, statistic_icon + _('Set up CI/CD'), project_ci_pipeline_editor_path(project))
|
||||
elsif repository.gitlab_ci_yml.present?
|
||||
AnchorData.new(false, statistic_icon('doc-text') + _('CI/CD configuration'), project_ci_pipeline_editor_path(project), 'btn-default')
|
||||
AnchorData.new(false, statistic_icon('rocket') + _('CI/CD configuration'), project_ci_pipeline_editor_path(project), 'btn-default')
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -412,7 +436,9 @@ class ProjectPresenter < Gitlab::View::Presenter::Delegated
|
|||
if project.wiki.has_home_page?
|
||||
AnchorData.new(false, statistic_icon('book') + _('Wiki'), project_wiki_path, 'btn-default', nil, nil)
|
||||
elsif can_create_wiki?
|
||||
AnchorData.new(false, statistic_icon + _('Add Wiki'), project_create_wiki_path, nil, nil, nil)
|
||||
icon = Feature.enabled?(:project_overview_reorg) ? statistic_icon('plus', 'gl-mr-3') : statistic_icon
|
||||
label = icon + _('Add Wiki')
|
||||
AnchorData.new(false, label, project_create_wiki_path, nil, nil, nil)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -457,8 +483,11 @@ class ProjectPresenter < Gitlab::View::Presenter::Delegated
|
|||
def integrations_anchor_data
|
||||
return unless can?(current_user, :admin_project, project)
|
||||
|
||||
label = statistic_icon('settings') + _('Configure Integrations')
|
||||
AnchorData.new(false, label, project_settings_integrations_path(project), nil, nil, nil)
|
||||
if Feature.enabled?(:project_overview_reorg)
|
||||
AnchorData.new(false, content_tag(:span, statistic_icon('plus', 'gl-blue-500! gl-mr-3') + _('Configure Integrations'), class: 'btn-link'), project_settings_integrations_path(project), nil, nil, nil)
|
||||
else
|
||||
AnchorData.new(false, content_tag(:span, statistic_icon('settings') + _('Configure Integrations')), project_settings_integrations_path(project), nil, nil, nil)
|
||||
end
|
||||
end
|
||||
|
||||
def cicd_missing?
|
||||
|
|
|
|||
|
|
@ -59,7 +59,7 @@ module Clusters
|
|||
|
||||
return unless project_entries
|
||||
|
||||
allowed_projects.where_full_path_in(project_entries.keys).map do |project|
|
||||
allowed_projects.where_full_path_in(project_entries.keys, use_includes: false).map do |project|
|
||||
{ project_id: project.id, config: user_access_as }
|
||||
end
|
||||
end
|
||||
|
|
@ -70,7 +70,7 @@ module Clusters
|
|||
|
||||
return unless group_entries
|
||||
|
||||
allowed_groups.where_full_path_in(group_entries.keys).map do |group|
|
||||
allowed_groups.where_full_path_in(group_entries.keys, use_includes: false).map do |group|
|
||||
{ group_id: group.id, config: user_access_as }
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -33,6 +33,8 @@ module Users
|
|||
attr_reader :execution_tracker
|
||||
|
||||
def migrate_records
|
||||
migrate_user_achievements
|
||||
|
||||
return if hard_delete
|
||||
|
||||
migrate_issues
|
||||
|
|
@ -101,6 +103,11 @@ module Users
|
|||
batched_migrate(Release, :author_id)
|
||||
end
|
||||
|
||||
def migrate_user_achievements
|
||||
batched_migrate(Achievements::UserAchievement, :awarded_by_user_id)
|
||||
batched_migrate(Achievements::UserAchievement, :revoked_by_user_id)
|
||||
end
|
||||
|
||||
# rubocop:disable CodeReuse/ActiveRecord
|
||||
def batched_migrate(base_scope, column, batch_size: 50)
|
||||
loop do
|
||||
|
|
|
|||
|
|
@ -22,7 +22,13 @@
|
|||
href: resume_admin_background_migration_path(migration, database: params[:database]),
|
||||
button_options: { class: 'has-tooltip', title: _('Resume'), 'aria-label' => _('Resume') })
|
||||
- elsif migration.failed?
|
||||
= render Pajamas::ButtonComponent.new(icon: 'retry',
|
||||
method: :post,
|
||||
href: retry_admin_background_migration_path(migration, database: params[:database]),
|
||||
button_options: { class: 'has-tooltip', title: _('Retry'), 'aria-label' => _('Retry') })
|
||||
= render Pajamas::ButtonComponent.new(category: :tertiary,
|
||||
size: :small,
|
||||
icon: 'ellipsis_v',
|
||||
button_options: { class: 'js-label-options-dropdown gl-ml-3', 'aria_label': _('Label actions dropdown'), title: _('Label actions dropdown'), data: { toggle: 'dropdown' } })
|
||||
.dropdown-menu.dropdown-menu-right
|
||||
%ul
|
||||
%li
|
||||
= link_button_to _('Retry'), retry_admin_background_migration_path(migration, database: params[:database]), method: :post, icon: 'retry', category: :tertiary, title: _('Retry')
|
||||
%li
|
||||
= clipboard_button text: migration.finalize_command, variant: :default, size: :medium, title: _('Copy command to finalize manually'), category: :tertiary, button_text: _('Copy command to finalize manually')
|
||||
|
|
|
|||
|
|
@ -1,2 +1,2 @@
|
|||
<%= sanitize_name(@updated_by.name) %> requested a new review on <%= merge_request_reference_link(@merge_request) %>.
|
||||
<%= sanitize_name(@updated_by.name) %> requested a new review on <%= url_for(project_merge_request_url(@merge_request.target_project, @merge_request)) %>
|
||||
<%= render_if_exists 'notify/diff_summary' -%>
|
||||
|
|
|
|||
|
|
@ -8,23 +8,27 @@
|
|||
- add_page_startup_api_call project_blob_path(@project, tree_join(@ref, readme_path), viewer: "rich", format: "json")
|
||||
|
||||
#tree-holder.tree-holder.clearfix.js-per-page{ data: { blame_per_page: Gitlab::Git::BlamePagination::PAGINATION_PER_PAGE } }
|
||||
- if Feature.enabled?(:project_overview_reorg)
|
||||
.nav-block.gl-display-flex.gl-flex-direction-column.gl-sm-flex-direction-row.gl-align-items-stretch
|
||||
= render 'projects/tree/tree_header', tree: @tree
|
||||
|
||||
.info-well.gl-display-none.gl-sm-display-flex.project-last-commit.gl-flex-direction-column.gl-mt-5
|
||||
#js-last-commit.gl-m-auto{ data: {ref_type: @ref_type.to_s} }
|
||||
= gl_loading_icon(size: 'md')
|
||||
- if project.licensed_feature_available?(:code_owners)
|
||||
#js-code-owners{ data: { branch: @ref, can_view_branch_rules: can_view_branch_rules?, branch_rules_path: branch_rules_path } }
|
||||
|
||||
.nav-block.gl-display-flex.gl-flex-direction-column.gl-sm-flex-direction-row.gl-align-items-stretch
|
||||
= render 'projects/tree/tree_header', tree: @tree
|
||||
- if Feature.disabled?(:project_overview_reorg)
|
||||
.nav-block.gl-display-flex.gl-flex-direction-column.gl-sm-flex-direction-row.gl-align-items-stretch
|
||||
= render 'projects/tree/tree_header', tree: @tree
|
||||
|
||||
- if project.forked?
|
||||
#js-fork-info{ data: vue_fork_divergence_data(project, ref) }
|
||||
|
||||
- if is_project_overview && has_project_shortcut_buttons
|
||||
- if Feature.disabled?(:project_overview_reorg) && is_project_overview && has_project_shortcut_buttons
|
||||
.project-buttons.gl-mb-5.js-show-on-project-root{ data: { testid: 'project-buttons' } }
|
||||
= render 'stat_anchor_list', anchors: @project.statistics_buttons(show_auto_devops_callout: show_auto_devops_callout), project_buttons: true
|
||||
|
||||
#js-tree-list{ data: vue_file_list_data(project, ref) }
|
||||
- if can_edit_tree?
|
||||
= render 'projects/blob/new_dir'
|
||||
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
- empty_repo = @project.empty_repo?
|
||||
- show_auto_devops_callout = show_auto_devops_callout?(@project)
|
||||
- emails_disabled = @project.emails_disabled?
|
||||
- ff_reorg_disabled = Feature.disabled?(:project_overview_reorg)
|
||||
|
||||
.project-home-panel.js-show-on-project-root.gl-mt-4.gl-mb-5{ class: [("empty-project" if empty_repo)] }
|
||||
.gl-display-flex.gl-justify-content-space-between.gl-flex-wrap.gl-flex-direction-column.gl-md-flex-direction-row.gl-mb-3.gl-gap-5
|
||||
%header.project-home-panel.js-show-on-project-root.gl-mt-5{ class: [("empty-project" if empty_repo)] }
|
||||
.gl-display-flex.gl-justify-content-space-between.gl-flex-wrap.gl-flex-direction-column.gl-md-flex-direction-row.gl-gap-5
|
||||
.home-panel-title-row.gl-display-flex.gl-align-items-center
|
||||
%div{ class: 'avatar-container rect-avatar s64 home-panel-avatar gl-flex-shrink-0 gl-w-11 gl-h-11 gl-mr-3! float-none' }
|
||||
= project_icon(@project, alt: @project.name, class: 'avatar avatar-tile s64', width: 64, height: 64, itemprop: 'image')
|
||||
|
|
@ -35,25 +35,29 @@
|
|||
= render 'projects/buttons/star'
|
||||
= render 'projects/buttons/fork'
|
||||
|
||||
- if can?(current_user, :read_code, @project)
|
||||
%nav.project-stats
|
||||
- if @project.empty_repo?
|
||||
= render 'stat_anchor_list', anchors: @project.empty_repo_statistics_anchors
|
||||
- else
|
||||
= render 'stat_anchor_list', anchors: @project.statistics_anchors(show_auto_devops_callout: show_auto_devops_callout)
|
||||
.gl-my-3
|
||||
= render "shared/projects/topics", project: @project
|
||||
.home-panel-home-desc.mt-1
|
||||
- if @project.description.present?
|
||||
.home-panel-description.text-break
|
||||
.home-panel-description-markdown.read-more-container{ itemprop: 'description' }
|
||||
= markdown_field(@project, :description)
|
||||
= render Pajamas::ButtonComponent.new(category: :tertiary, variant: :link, button_options: { class: 'js-read-more-trigger gl-lg-display-none' }) do
|
||||
= _("Read more")
|
||||
- if ff_reorg_disabled
|
||||
- if can?(current_user, :read_code, @project)
|
||||
- show_auto_devops_callout = show_auto_devops_callout?(@project)
|
||||
|
||||
%nav.project-stats.gl-mt-3
|
||||
- if @project.empty_repo?
|
||||
= render 'stat_anchor_list', anchors: @project.empty_repo_statistics_anchors
|
||||
- else
|
||||
= render 'stat_anchor_list', anchors: @project.statistics_anchors(show_auto_devops_callout: show_auto_devops_callout)
|
||||
.gl-my-3
|
||||
= render "shared/projects/topics", project: @project
|
||||
|
||||
.home-panel-home-desc.mt-1
|
||||
- if @project.description.present?
|
||||
.home-panel-description.text-break
|
||||
.home-panel-description-markdown.read-more-container{ itemprop: 'description' }
|
||||
= markdown_field(@project, :description)
|
||||
= render Pajamas::ButtonComponent.new(category: :tertiary, variant: :link, button_options: { class: 'js-read-more-trigger gl-lg-display-none' }) do
|
||||
= _("Read more")
|
||||
|
||||
= render_if_exists "projects/home_mirror"
|
||||
|
||||
- if @project.badges.present?
|
||||
- if ff_reorg_disabled && @project.badges.present?
|
||||
.project-badges.mb-2{ data: { testid: 'project-badges-content' } }
|
||||
- @project.badges.each do |badge|
|
||||
- badge_link_url = badge.rendered_link_url(@project)
|
||||
|
|
|
|||
|
|
@ -1,9 +1,20 @@
|
|||
%h4.gl-mt-0.gl-mb-3{ data: { testid: 'invite-member-section',
|
||||
track_label: 'invite_members_empty_project',
|
||||
track_action: 'render' } }
|
||||
= s_('InviteMember|Invite your team')
|
||||
%p= s_('InviteMember|Add members to this project and start collaborating with your team.')
|
||||
.js-invite-members-trigger{ data: { variant: 'confirm',
|
||||
classes: 'gl-mb-8 gl-w-full gl-sm-w-auto',
|
||||
display_text: s_('InviteMember|Invite members'),
|
||||
trigger_source: 'project_empty_page' } }
|
||||
- if Feature.enabled?(:project_overview_reorg)
|
||||
%p.gl-font-weight-bold.gl-text-gray-900.gl-mt-0.gl-mt-n1.gl-mb-3{ data: { testid: 'invite-member-section',
|
||||
track_label: 'invite_members_empty_project',
|
||||
track_action: 'render' } }
|
||||
= s_('InviteMember|Invite your team')
|
||||
%p.gl-mb-3= s_('InviteMember|Add members to this project and start collaborating with your team.')
|
||||
.js-invite-members-trigger{ data: { variant: 'confirm',
|
||||
classes: 'gl-mb-3 gl-w-full gl-sm-w-auto',
|
||||
display_text: s_('InviteMember|Invite members'),
|
||||
trigger_source: 'project_empty_page' } }
|
||||
- else
|
||||
%h4.gl-mt-0.gl-mb-3{ data: { testid: 'invite-member-section',
|
||||
track_label: 'invite_members_empty_project',
|
||||
track_action: 'render' } }
|
||||
= s_('InviteMember|Invite your team')
|
||||
%p= s_('InviteMember|Add members to this project and start collaborating with your team.')
|
||||
.js-invite-members-trigger{ data: { variant: 'confirm',
|
||||
classes: 'gl-mb-8 gl-w-full gl-sm-w-auto',
|
||||
display_text: s_('InviteMember|Invite members'),
|
||||
trigger_source: 'project_empty_page' } }
|
||||
|
|
|
|||
|
|
@ -0,0 +1,61 @@
|
|||
- has_project_shortcut_buttons = !current_user || current_user.project_shortcut_buttons
|
||||
- show_auto_devops_callout = show_auto_devops_callout?(@project)
|
||||
|
||||
%aside.project-page-sidebar
|
||||
- if @project.description.present? || @project.badges.present?
|
||||
.project-page-sidebar-block.home-panel-home-desc.gl-py-4.gl-border-b.gl-border-gray-50
|
||||
-# Project description
|
||||
- if @project.description.present?
|
||||
.gl-display-flex.gl-justify-content-space-between.gl-mt-1.gl-pr-2
|
||||
%p.gl-font-weight-bold.gl-text-gray-900.gl-m-0= s_('ProjectPage|Project information')
|
||||
= render Pajamas::ButtonComponent.new(href: edit_project_path(@project),
|
||||
category: :tertiary,
|
||||
icon: 'settings',
|
||||
size: :small,
|
||||
button_options: { class: 'has-tooltip', title: s_('ProjectPage|Project settings'), 'aria-label' => s_('ProjectPage|Project settings') })
|
||||
.home-panel-description.text-break
|
||||
.home-panel-description-markdown{ itemprop: 'description' }
|
||||
= markdown_field(@project, :description)
|
||||
|
||||
-# Topics
|
||||
- if @project.topics.present?
|
||||
.gl-mb-5
|
||||
= render "shared/projects/topics", project: @project
|
||||
|
||||
-# Programming languages
|
||||
- if can?(current_user, :read_code, @project) && @project.repository_languages.present?
|
||||
.gl-mb-2{ class: ('gl-mb-4!' if @project.badges.present?) }
|
||||
= repository_languages_bar(@project.repository_languages)
|
||||
|
||||
-# Badges
|
||||
- if @project.badges.present?
|
||||
.project-badges.gl-mb-2{ data: { testid: 'project-badges-content' } }
|
||||
- @project.badges.each do |badge|
|
||||
- badge_link_url = badge.rendered_link_url(@project)
|
||||
%a.gl-mr-3{ href: badge_link_url,
|
||||
target: '_blank',
|
||||
rel: 'noopener noreferrer',
|
||||
data: { testid: 'badge-image-link', qa_link_url: badge_link_url } }>
|
||||
%img.project-badge{ src: badge.rendered_image_url(@project),
|
||||
'aria-hidden': true,
|
||||
alt: 'Project badge' }>
|
||||
|
||||
-# Invite members
|
||||
- if @project.empty_repo?
|
||||
.project-page-sidebar-block.gl-py-4.gl-border-b.gl-border-gray-50
|
||||
= render "invite_members_empty_project" if can_admin_project_member?(@project)
|
||||
|
||||
-# Buttons
|
||||
- if can?(current_user, :read_code, @project) && !@project.empty_repo?
|
||||
.project-page-sidebar-block.gl-py-4.gl-border-b.gl-border-gray-50
|
||||
%nav.project-stats
|
||||
= render 'stat_anchor_list', anchors: @project.statistics_anchors(show_auto_devops_callout: show_auto_devops_callout)
|
||||
|
||||
-# Buttons
|
||||
- if has_project_shortcut_buttons
|
||||
.project-page-sidebar-block.gl-py-4
|
||||
.project-buttons.gl-mb-2.js-show-on-project-root{ data: { testid: 'project-buttons' } }
|
||||
- if @project.empty_repo?
|
||||
= render 'stat_anchor_list', anchors: @project.empty_repo_statistics_buttons, project_buttons: true
|
||||
- else
|
||||
= render 'stat_anchor_list', anchors: @project.statistics_buttons(show_auto_devops_callout: show_auto_devops_callout), project_buttons: true
|
||||
|
|
@ -2,8 +2,9 @@
|
|||
- project_buttons = local_assigns.fetch(:project_buttons, false)
|
||||
|
||||
- return unless anchors.any?
|
||||
%ul.nav{ class: (project_buttons ? 'gl-gap-3' : 'gl-gap-5') }
|
||||
|
||||
%ul.nav.gl-gap-2
|
||||
- anchors.each do |anchor|
|
||||
%li.nav-item
|
||||
= link_to_if(anchor.link, anchor.label, anchor.link, stat_anchor_attrs(anchor)) do
|
||||
.stat-text.d-flex.align-items-center{ class: ('btn gl-button btn-default disabled' if project_buttons) }= anchor.label
|
||||
.stat-text.d-flex.align-items-center{ class: ('btn gl-button btn-default gl-px-0! disabled' if project_buttons) }= anchor.label
|
||||
|
|
|
|||
|
|
@ -9,68 +9,142 @@
|
|||
= render "home_panel"
|
||||
= render "archived_notice", project: @project
|
||||
|
||||
= render "invite_members_empty_project" if can_admin_project_member?(@project)
|
||||
- if Feature.enabled?(:project_overview_reorg)
|
||||
- add_page_specific_style 'page_bundles/project'
|
||||
|
||||
%h4.gl-mt-0.gl-mb-3
|
||||
= _('The repository for this project is empty')
|
||||
.project-page-indicator.js-show-on-project-root
|
||||
|
||||
- if @project.can_current_user_push_code?
|
||||
%p
|
||||
= _('You can get started by cloning the repository or start adding files to it with one of the following options.')
|
||||
.project-page-layout
|
||||
.project-page-layout-content.gl-mt-5
|
||||
.project-buttons.gl-mb-5{ data: { testid: 'quick-actions-container' } }
|
||||
.project-clone-holder.d-block.d-md-none
|
||||
= render "shared/mobile_clone_panel"
|
||||
|
||||
.project-buttons{ data: { testid: 'quick-actions-container' } }
|
||||
.project-code-holder.d-block.d-md-none.gl-mt-3.gl-mr-3
|
||||
= render "shared/mobile_clone_panel"
|
||||
.project-clone-holder.gl-display-none.gl-md-display-flex.gl-justify-content-end.gl-w-full.gl-mt-2
|
||||
= render "projects/buttons/code", ref: @ref
|
||||
|
||||
.project-code-holder.d-none.d-md-inline-block.gl-mb-3.gl-mr-3.float-left
|
||||
= render "projects/buttons/code", ref: @ref
|
||||
= render 'stat_anchor_list', anchors: @project.empty_repo_statistics_buttons, project_buttons: true
|
||||
= render Pajamas::CardComponent.new(card_options: { class: 'gl-mb-5' }, body_options: { class: 'gl-new-card-body gl-bg-gray-10 gl-p-5' }) do |c|
|
||||
- c.with_body do
|
||||
%h4.gl-font-lg.gl-mt-0.gl-mb-2= _('The repository for this project is empty')
|
||||
- if @project.can_current_user_push_code?
|
||||
%p.gl-m-0.gl-text-secondary= _('You can get started by cloning the repository or start adding files to it with one of the following options.')
|
||||
|
||||
- if can?(current_user, :push_code, @project)
|
||||
.empty-wrapper.gl-mt-4
|
||||
%h3#repo-command-line-instructions.page-title-empty
|
||||
= _('Command line instructions')
|
||||
- if can?(current_user, :push_code, @project)
|
||||
= render Pajamas::CardComponent.new(header_options: { class: 'gl-py-4' }) do |c|
|
||||
- c.with_header do
|
||||
%h5.gl-font-lg.gl-m-0= _('Command line instructions')
|
||||
- c.with_body do
|
||||
%p
|
||||
= _('You can also upload existing files from your computer using the instructions below.')
|
||||
.git-empty.js-git-empty
|
||||
%h5= _('Git global setup')
|
||||
%pre.gl-bg-gray-10
|
||||
:preserve
|
||||
git config --global user.name "#{h git_user_name}"
|
||||
git config --global user.email "#{h git_user_email}"
|
||||
|
||||
%h5= _('Create a new repository')
|
||||
%pre.gl-bg-gray-10
|
||||
:preserve
|
||||
git clone #{ content_tag(:span, default_url_to_repo, class: 'js-clone')}
|
||||
cd #{h @project.path}
|
||||
git switch --create #{h escaped_default_branch_name}
|
||||
touch README.md
|
||||
git add README.md
|
||||
git commit -m "add README"
|
||||
- if @project.can_current_user_push_to_default_branch?
|
||||
%span><
|
||||
git push --set-upstream origin #{h escaped_default_branch_name }
|
||||
|
||||
%h5= _('Push an existing folder')
|
||||
%pre.gl-bg-gray-10
|
||||
:preserve
|
||||
cd existing_folder
|
||||
git init --initial-branch=#{h escaped_default_branch_name}
|
||||
git remote add origin #{ content_tag(:span, default_url_to_repo, class: 'js-clone')}
|
||||
git add .
|
||||
git commit -m "Initial commit"
|
||||
- if @project.can_current_user_push_to_default_branch?
|
||||
%span><
|
||||
git push --set-upstream origin #{h escaped_default_branch_name }
|
||||
|
||||
%h5= _('Push an existing Git repository')
|
||||
%pre.gl-bg-gray-10
|
||||
:preserve
|
||||
cd existing_repo
|
||||
git remote rename origin old-origin
|
||||
git remote add origin #{ content_tag(:span, default_url_to_repo, class: 'js-clone')}
|
||||
- if @project.can_current_user_push_to_default_branch?
|
||||
%span><
|
||||
git push --set-upstream origin --all
|
||||
git push --set-upstream origin --tags
|
||||
|
||||
.project-page-layout-sidebar.js-show-on-project-root.gl-mt-5
|
||||
= render "sidebar"
|
||||
|
||||
- else
|
||||
= render "invite_members_empty_project" if can_admin_project_member?(@project)
|
||||
|
||||
%h4.gl-mt-0.gl-mb-3
|
||||
= _('The repository for this project is empty')
|
||||
|
||||
- if @project.can_current_user_push_code?
|
||||
%p
|
||||
= _('You can also upload existing files from your computer using the instructions below.')
|
||||
.git-empty.js-git-empty
|
||||
%h5= _('Git global setup')
|
||||
%pre.bg-light
|
||||
:preserve
|
||||
git config --global user.name "#{h git_user_name}"
|
||||
git config --global user.email "#{h git_user_email}"
|
||||
= _('You can get started by cloning the repository or start adding files to it with one of the following options.')
|
||||
|
||||
%h5= _('Create a new repository')
|
||||
%pre.bg-light
|
||||
:preserve
|
||||
git clone #{ content_tag(:span, default_url_to_repo, class: 'js-clone')}
|
||||
cd #{h @project.path}
|
||||
git switch --create #{h escaped_default_branch_name}
|
||||
touch README.md
|
||||
git add README.md
|
||||
git commit -m "add README"
|
||||
- if @project.can_current_user_push_to_default_branch?
|
||||
%span><
|
||||
git push --set-upstream origin #{h escaped_default_branch_name }
|
||||
.project-buttons{ data: { testid: 'quick-actions-container' } }
|
||||
.project-clone-holder.d-block.d-md-none.gl-mt-3.gl-mr-3
|
||||
= render "shared/mobile_clone_panel"
|
||||
|
||||
%h5= _('Push an existing folder')
|
||||
%pre.bg-light
|
||||
:preserve
|
||||
cd existing_folder
|
||||
git init --initial-branch=#{h escaped_default_branch_name}
|
||||
git remote add origin #{ content_tag(:span, default_url_to_repo, class: 'js-clone')}
|
||||
git add .
|
||||
git commit -m "Initial commit"
|
||||
- if @project.can_current_user_push_to_default_branch?
|
||||
%span><
|
||||
git push --set-upstream origin #{h escaped_default_branch_name }
|
||||
.project-clone-holder.d-none.d-md-inline-block.gl-mb-3.gl-mr-3.float-left
|
||||
= render "projects/buttons/code", ref: @ref
|
||||
= render 'stat_anchor_list', anchors: @project.empty_repo_statistics_buttons, project_buttons: true
|
||||
|
||||
%h5= _('Push an existing Git repository')
|
||||
%pre.bg-light
|
||||
:preserve
|
||||
cd existing_repo
|
||||
git remote rename origin old-origin
|
||||
git remote add origin #{ content_tag(:span, default_url_to_repo, class: 'js-clone')}
|
||||
- if @project.can_current_user_push_to_default_branch?
|
||||
%span><
|
||||
git push --set-upstream origin --all
|
||||
git push --set-upstream origin --tags
|
||||
- if can?(current_user, :push_code, @project)
|
||||
.empty-wrapper.gl-mt-4
|
||||
%h3#repo-command-line-instructions.page-title-empty
|
||||
= _('Command line instructions')
|
||||
%p
|
||||
= _('You can also upload existing files from your computer using the instructions below.')
|
||||
.git-empty.js-git-empty
|
||||
%h5= _('Git global setup')
|
||||
%pre.bg-light
|
||||
:preserve
|
||||
git config --global user.name "#{h git_user_name}"
|
||||
git config --global user.email "#{h git_user_email}"
|
||||
|
||||
%h5= _('Create a new repository')
|
||||
%pre.bg-light
|
||||
:preserve
|
||||
git clone #{ content_tag(:span, default_url_to_repo, class: 'js-clone')}
|
||||
cd #{h @project.path}
|
||||
git switch --create #{h escaped_default_branch_name}
|
||||
touch README.md
|
||||
git add README.md
|
||||
git commit -m "add README"
|
||||
- if @project.can_current_user_push_to_default_branch?
|
||||
%span><
|
||||
git push --set-upstream origin #{h escaped_default_branch_name }
|
||||
|
||||
%h5= _('Push an existing folder')
|
||||
%pre.bg-light
|
||||
:preserve
|
||||
cd existing_folder
|
||||
git init --initial-branch=#{h escaped_default_branch_name}
|
||||
git remote add origin #{ content_tag(:span, default_url_to_repo, class: 'js-clone')}
|
||||
git add .
|
||||
git commit -m "Initial commit"
|
||||
- if @project.can_current_user_push_to_default_branch?
|
||||
%span><
|
||||
git push --set-upstream origin #{h escaped_default_branch_name }
|
||||
|
||||
%h5= _('Push an existing Git repository')
|
||||
%pre.bg-light
|
||||
:preserve
|
||||
cd existing_repo
|
||||
git remote rename origin old-origin
|
||||
git remote add origin #{ content_tag(:span, default_url_to_repo, class: 'js-clone')}
|
||||
- if @project.can_current_user_push_to_default_branch?
|
||||
%span><
|
||||
git push --set-upstream origin --all
|
||||
git push --set-upstream origin --tags
|
||||
|
|
|
|||
|
|
@ -15,15 +15,35 @@
|
|||
|
||||
= render "home_panel"
|
||||
|
||||
- if can?(current_user, :read_code, @project) && @project.repository_languages.present?
|
||||
- add_page_startup_graphql_call('repository/path_last_commit', { projectPath: @project.full_path, ref: current_ref, path: current_route_path || "" })
|
||||
= repository_languages_bar(@project.repository_languages)
|
||||
- if Feature.enabled?(:project_overview_reorg)
|
||||
.project-page-indicator.js-show-on-project-root
|
||||
|
||||
= render "archived_notice", project: @project
|
||||
= render_if_exists "projects/marked_for_deletion_notice", project: @project
|
||||
= render_if_exists "projects/ancestor_group_marked_for_deletion_notice", project: @project
|
||||
.project-page-layout
|
||||
.project-page-layout-sidebar.js-show-on-project-root.gl-mt-5
|
||||
= render "sidebar"
|
||||
|
||||
- view_path = @project.default_view
|
||||
.project-page-layout-content.gl-mt-5
|
||||
- if can?(current_user, :read_code, @project) && @project.repository_languages.present?
|
||||
- add_page_startup_graphql_call('repository/path_last_commit', { projectPath: @project.full_path, ref: current_ref, path: current_route_path || "" })
|
||||
|
||||
%div{ class: project_child_container_class(view_path) }
|
||||
= render view_path, is_project_overview: true
|
||||
= render "archived_notice", project: @project
|
||||
= render_if_exists "projects/marked_for_deletion_notice", project: @project
|
||||
= render_if_exists "projects/ancestor_group_marked_for_deletion_notice", project: @project
|
||||
|
||||
- view_path = @project.default_view
|
||||
|
||||
%div{ class: project_child_container_class(view_path) }
|
||||
= render view_path, is_project_overview: true
|
||||
- else
|
||||
- if can?(current_user, :read_code, @project) && @project.repository_languages.present?
|
||||
- add_page_startup_graphql_call('repository/path_last_commit', { projectPath: @project.full_path, ref: current_ref, path: current_route_path || "" })
|
||||
= repository_languages_bar(@project.repository_languages)
|
||||
|
||||
= render "archived_notice", project: @project
|
||||
= render_if_exists "projects/marked_for_deletion_notice", project: @project
|
||||
= render_if_exists "projects/ancestor_group_marked_for_deletion_notice", project: @project
|
||||
|
||||
- view_path = @project.default_view
|
||||
|
||||
%div{ class: project_child_container_class(view_path) }
|
||||
= render view_path, is_project_overview: true
|
||||
|
|
|
|||
|
|
@ -2,8 +2,9 @@
|
|||
|
||||
- if project.topics.present?
|
||||
.gl-w-full.gl-display-inline-flex.gl-flex-wrap.gl-font-base.gl-font-weight-normal.gl-align-items-center.gl-mx-n2.gl-my-n2{ 'data-testid': 'project_topic_list' }
|
||||
%span.gl-p-2.gl-text-gray-500
|
||||
= _('Topics') + ':'
|
||||
- if Feature.disabled?(:project_overview_reorg)
|
||||
%span.gl-p-2.gl-text-gray-500
|
||||
= _('Topics') + ':'
|
||||
- project.topics_to_show.each do |topic|
|
||||
- explore_project_topic_path = topic_explore_projects_cleaned_path(topic_name: topic[:name])
|
||||
- if topic[:title].length > max_project_topic_length
|
||||
|
|
|
|||
|
|
@ -0,0 +1,8 @@
|
|||
---
|
||||
name: key_contacts_management
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/issues/432400
|
||||
rollout_issue_url: https://gitlab.com/gitlab-org/customers-gitlab-com/-/issues/5873
|
||||
milestone: '16.7'
|
||||
type: development
|
||||
group: group::subscription management
|
||||
default_enabled: false
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
---
|
||||
name: project_overview_reorg
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/137025
|
||||
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/432034
|
||||
milestone: '16.7'
|
||||
type: development
|
||||
group: group::ux paper cuts
|
||||
default_enabled: false
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class FixBrokenUserAchievementsAwarded < Gitlab::Database::Migration[2.2]
|
||||
disable_ddl_transaction!
|
||||
restrict_gitlab_migration gitlab_schema: :gitlab_main
|
||||
|
||||
milestone '16.7'
|
||||
|
||||
class User < MigrationRecord
|
||||
self.table_name = 'users'
|
||||
end
|
||||
|
||||
def up
|
||||
User.reset_column_information
|
||||
|
||||
ghost_id = User.where(user_type: 5).first&.id
|
||||
|
||||
return unless ghost_id
|
||||
|
||||
update_column_in_batches(:user_achievements, :awarded_by_user_id, ghost_id) do |table, query|
|
||||
query.where(table[:awarded_by_user_id].eq(nil))
|
||||
end
|
||||
end
|
||||
|
||||
def down
|
||||
# noop -- this is a data migration and can't be reversed
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class FixBrokenUserAchievementsRevoked < Gitlab::Database::Migration[2.2]
|
||||
disable_ddl_transaction!
|
||||
restrict_gitlab_migration gitlab_schema: :gitlab_main
|
||||
|
||||
milestone '16.7'
|
||||
|
||||
class User < MigrationRecord
|
||||
self.table_name = 'users'
|
||||
end
|
||||
|
||||
def up
|
||||
User.reset_column_information
|
||||
|
||||
ghost_id = User.where(user_type: 5).first&.id
|
||||
|
||||
return unless ghost_id
|
||||
|
||||
update_column_in_batches(:user_achievements, :revoked_by_user_id, ghost_id) do |table, query|
|
||||
query.where(table[:revoked_at].not_eq(nil)).where(table[:revoked_by_user_id].eq(nil))
|
||||
end
|
||||
end
|
||||
|
||||
def down
|
||||
# noop -- this is a data migration and can't be reversed
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1 @@
|
|||
2fd167741f25de79d9aa561e0b48f3b1c3c40bce45df762d82841ac0e52109aa
|
||||
|
|
@ -0,0 +1 @@
|
|||
8bea5995e63f29947b408a871615b3838d586af4baac3eca79aaa39c8334a379
|
||||
|
|
@ -55,6 +55,7 @@ Audit event types belong to the following product categories.
|
|||
| [`google_cloud_logging_configuration_created`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/122025) | Triggered when Google Cloud Logging configuration is created| **{check-circle}** Yes | **{check-circle}** Yes | GitLab [16.1](https://gitlab.com/gitlab-org/gitlab/-/issues/409422) |
|
||||
| [`google_cloud_logging_configuration_deleted`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/122025) | Triggered when Google Cloud Logging configuration is deleted| **{check-circle}** Yes | **{check-circle}** Yes | GitLab [16.1](https://gitlab.com/gitlab-org/gitlab/-/issues/409422) |
|
||||
| [`google_cloud_logging_configuration_updated`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/122025) | Triggered when Google Cloud Logging configuration is updated| **{check-circle}** Yes | **{check-circle}** Yes | GitLab [16.1](https://gitlab.com/gitlab-org/gitlab/-/issues/409422) |
|
||||
| [`instance_amazon_s3_configuration_created`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/137651) | Triggered when instance Amazon S3 configuration for audit events streaming is created| **{check-circle}** Yes | **{check-circle}** Yes | GitLab [16.7](https://gitlab.com/gitlab-org/gitlab/-/issues/423235) |
|
||||
| [`instance_google_cloud_logging_configuration_created`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/130663) | Triggered when Instance level Google Cloud Logging configuration is created| **{check-circle}** Yes | **{check-circle}** Yes | GitLab [16.4](https://gitlab.com/gitlab-org/gitlab/-/issues/423038) |
|
||||
| [`instance_google_cloud_logging_configuration_deleted`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/131752) | Triggered when instance level Google Cloud Logging configuration is deleted.| **{check-circle}** Yes | **{check-circle}** Yes | GitLab [16.5](https://gitlab.com/gitlab-org/gitlab/-/issues/423040) |
|
||||
| [`instance_google_cloud_logging_configuration_updated`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/131790) | Triggered when instance level Google Cloud Logging configuration is updated.| **{check-circle}** Yes | **{check-circle}** Yes | GitLab [16.5](https://gitlab.com/gitlab-org/gitlab/-/issues/423039) |
|
||||
|
|
|
|||
|
|
@ -682,6 +682,19 @@ In GitLab 13.4, a seed project is added when GitLab is first installed. This mak
|
|||
on a new Geo secondary site. There is an [issue to account for seed projects](https://gitlab.com/gitlab-org/omnibus-gitlab/-/issues/5618)
|
||||
when checking the database.
|
||||
|
||||
### Message: `FATAL: could not map anonymous shared memory: Cannot allocate memory`
|
||||
|
||||
If you see this message, it means that the secondary site's PostgreSQL tries to request memory that is higher than the available memory. There is an [issue](https://gitlab.com/gitlab-org/gitlab/-/issues/381585) that tracks this problem.
|
||||
|
||||
Example error message in Patroni logs (located at `/var/log/gitlab/patroni/current` for Linux package installations):
|
||||
|
||||
```plaintext
|
||||
2023-11-21_23:55:18.63727 FATAL: could not map anonymous shared memory: Cannot allocate memory
|
||||
2023-11-21_23:55:18.63729 HINT: This error usually means that PostgreSQL's request for a shared memory segment exceeded available memory, swap space, or huge pages. To reduce the request size (currently 17035526144 bytes), reduce PostgreSQL's shared memory usage, perhaps by reducing shared_buffers or max_connections.
|
||||
```
|
||||
|
||||
The workaround is to increase the memory available to the secondary site's PostgreSQL nodes to match the memory requirements of the primary site's PostgreSQL nodes.
|
||||
|
||||
## Synchronization errors
|
||||
|
||||
### Reverify all uploads (or any SSF data type which is verified)
|
||||
|
|
|
|||
|
|
@ -1472,6 +1472,29 @@ Input type: `AuditEventsAmazonS3ConfigurationUpdateInput`
|
|||
| <a id="mutationauditeventsamazons3configurationupdateclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. |
|
||||
| <a id="mutationauditeventsamazons3configurationupdateerrors"></a>`errors` | [`[String!]!`](#string) | Errors encountered during execution of the mutation. |
|
||||
|
||||
### `Mutation.auditEventsInstanceAmazonS3ConfigurationCreate`
|
||||
|
||||
Input type: `AuditEventsInstanceAmazonS3ConfigurationCreateInput`
|
||||
|
||||
#### Arguments
|
||||
|
||||
| Name | Type | Description |
|
||||
| ---- | ---- | ----------- |
|
||||
| <a id="mutationauditeventsinstanceamazons3configurationcreateaccesskeyxid"></a>`accessKeyXid` | [`String!`](#string) | Access key ID of the Amazon S3 account. |
|
||||
| <a id="mutationauditeventsinstanceamazons3configurationcreateawsregion"></a>`awsRegion` | [`String!`](#string) | AWS region where the bucket is created. |
|
||||
| <a id="mutationauditeventsinstanceamazons3configurationcreatebucketname"></a>`bucketName` | [`String!`](#string) | Name of the bucket where the audit events would be logged. |
|
||||
| <a id="mutationauditeventsinstanceamazons3configurationcreateclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. |
|
||||
| <a id="mutationauditeventsinstanceamazons3configurationcreatename"></a>`name` | [`String`](#string) | Destination name. |
|
||||
| <a id="mutationauditeventsinstanceamazons3configurationcreatesecretaccesskey"></a>`secretAccessKey` | [`String!`](#string) | Secret access key of the Amazon S3 account. |
|
||||
|
||||
#### Fields
|
||||
|
||||
| Name | Type | Description |
|
||||
| ---- | ---- | ----------- |
|
||||
| <a id="mutationauditeventsinstanceamazons3configurationcreateclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. |
|
||||
| <a id="mutationauditeventsinstanceamazons3configurationcreateerrors"></a>`errors` | [`[String!]!`](#string) | Errors encountered during execution of the mutation. |
|
||||
| <a id="mutationauditeventsinstanceamazons3configurationcreateinstanceamazons3configuration"></a>`instanceAmazonS3Configuration` | [`InstanceAmazonS3ConfigurationType`](#instanceamazons3configurationtype) | Created instance Amazon S3 configuration. |
|
||||
|
||||
### `Mutation.auditEventsStreamingDestinationEventsAdd`
|
||||
|
||||
Input type: `AuditEventsStreamingDestinationEventsAddInput`
|
||||
|
|
@ -20317,6 +20340,20 @@ CI/CD variables a project inherites from its parent group and ancestors.
|
|||
| <a id="inheritedcivariableraw"></a>`raw` | [`Boolean`](#boolean) | Indicates whether the variable is raw. |
|
||||
| <a id="inheritedcivariablevariabletype"></a>`variableType` | [`CiVariableType`](#civariabletype) | Type of the variable. |
|
||||
|
||||
### `InstanceAmazonS3ConfigurationType`
|
||||
|
||||
Stores instance level Amazon S3 configurations for audit event streaming.
|
||||
|
||||
#### Fields
|
||||
|
||||
| Name | Type | Description |
|
||||
| ---- | ---- | ----------- |
|
||||
| <a id="instanceamazons3configurationtypeaccesskeyxid"></a>`accessKeyXid` | [`String!`](#string) | Access key ID of the Amazon S3 account. |
|
||||
| <a id="instanceamazons3configurationtypeawsregion"></a>`awsRegion` | [`String!`](#string) | AWS region where the bucket is created. |
|
||||
| <a id="instanceamazons3configurationtypebucketname"></a>`bucketName` | [`String!`](#string) | Name of the bucket where the audit events would be logged. |
|
||||
| <a id="instanceamazons3configurationtypeid"></a>`id` | [`ID!`](#id) | ID of the configuration. |
|
||||
| <a id="instanceamazons3configurationtypename"></a>`name` | [`String!`](#string) | Name of the external destination to send audit events to. |
|
||||
|
||||
### `InstanceExternalAuditEventDestination`
|
||||
|
||||
Represents an external resource to send instance audit events to.
|
||||
|
|
@ -32377,6 +32414,7 @@ Implementations:
|
|||
Implementations:
|
||||
|
||||
- [`AmazonS3ConfigurationType`](#amazons3configurationtype)
|
||||
- [`InstanceAmazonS3ConfigurationType`](#instanceamazons3configurationtype)
|
||||
|
||||
##### Fields
|
||||
|
||||
|
|
|
|||
|
|
@ -189,9 +189,10 @@ a patch fix like `1.5.1`, then `~latest` returns the `1.5.1` release.
|
|||
[Issue #427286](https://gitlab.com/gitlab-org/gitlab/-/issues/427286) proposes to
|
||||
change this behavior.
|
||||
|
||||
## CI/CD Catalog **(FREE ALL EXPERIMENT)**
|
||||
## CI/CD Catalog **(FREE ALL BETA)**
|
||||
|
||||
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/407249) in GitLab 16.1.
|
||||
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/407249) in GitLab 16.1 as an [experiment](../../policy/experiment-beta-support.md#experiment).
|
||||
> - [Moved](https://gitlab.com/gitlab-org/gitlab/-/issues/432045) to [beta](../../policy/experiment-beta-support.md#beta) in GitLab 16.7.
|
||||
|
||||
The CI/CD Catalog is a list of projects with published CI/CD components you can use to extend your CI/CD workflow.
|
||||
|
||||
|
|
|
|||
|
|
@ -19,10 +19,8 @@ When selecting a single link to navigate, use `click_`.
|
|||
For example:
|
||||
|
||||
```ruby
|
||||
def click_ci_cd_pipelines
|
||||
within_sidebar do
|
||||
click_element(:link_pipelines)
|
||||
end
|
||||
def click_add_badge_button
|
||||
click_element :add_badge_button
|
||||
end
|
||||
```
|
||||
|
||||
|
|
@ -38,12 +36,8 @@ When interacting with multiple elements to go to a page, use `go_to_`.
|
|||
For example:
|
||||
|
||||
```ruby
|
||||
def go_to_operations_environments
|
||||
hover_operations do
|
||||
within_submenu do
|
||||
click_element(:operations_environments_link)
|
||||
end
|
||||
end
|
||||
def go_to_applications
|
||||
click_element('nav-item-link', submenu_item: 'Applications')
|
||||
end
|
||||
```
|
||||
|
||||
|
|
|
|||
|
|
@ -359,6 +359,7 @@ Below is a list of Mattermost version changes for GitLab 14.0 and later:
|
|||
|
||||
| GitLab version | Mattermost version | Notes |
|
||||
| :------------- | :----------------- | ---------------------------------------------------------------------------------------- |
|
||||
| 16.7 | 9.2 | |
|
||||
| 16.6 | 9.1 | |
|
||||
| 16.5 | 9.0 | |
|
||||
| 16.4 | 8.1 | |
|
||||
|
|
|
|||
|
|
@ -63,9 +63,9 @@ Use CI/CD environment variables to configure your project.
|
|||
|
||||
1. On the left sidebar, select **Settings > CI/CD**.
|
||||
1. Expand **Variables**.
|
||||
1. Set the variable `AZURE_CLIENT_ID` to your Azure client ID.
|
||||
1. Set the variable `AZURE_CLIENT_SECRET` to your Azure client secret.
|
||||
1. Set the variable `AZURE_TENANT_ID` to your service principal.
|
||||
1. Set the variable `ARM_CLIENT_ID` to your Azure client ID.
|
||||
1. Set the variable `ARM_CLIENT_SECRET` to your Azure client secret.
|
||||
1. Set the variable `ARM_TENANT_ID` to your service principal.
|
||||
1. Set the variable `TF_VAR_agent_token` to the agent token displayed in the previous task.
|
||||
1. Set the variable `TF_VAR_kas_address` to the agent server address displayed in the previous task.
|
||||
|
||||
|
|
|
|||
|
|
@ -36,9 +36,11 @@ You can upload a file from the GitLab UI.
|
|||
<!-- For why we duplicated the info, see https://gitlab.com/gitlab-org/gitlab/-/merge_requests/111072#note_1267429478 -->
|
||||
|
||||
1. On the left sidebar, select **Search or go to** and find your project.
|
||||
1. From the project dashboard or repository, next to the branch name, select the plus icon (**{plus}**).
|
||||
1. From the dropdown list, select **Upload file**.
|
||||
1. Complete the fields. To create a merge request with the uploaded file, ensure the **Start a new merge request with these changes** toggle is turned on.
|
||||
1. Go to the directory where you want to upload the file.
|
||||
1. Next to the directory name, select the plus icon (**{plus}**) > **Upload file**.
|
||||
1. Complete the fields.
|
||||
- To create a merge request with your changes, enter a branch name
|
||||
that's not your repository's [default branch](branches/default.md).
|
||||
1. Select **Upload file**.
|
||||
|
||||
## Commit changes to a repository
|
||||
|
|
|
|||
|
|
@ -6,70 +6,70 @@ info: To determine the technical writer assigned to the Stage/Group associated w
|
|||
|
||||
# Web Editor **(FREE ALL)**
|
||||
|
||||
You can use the Web Editor to make changes to a single file directly from the
|
||||
GitLab UI. To make changes to multiple files, see [Web IDE](../web_ide/index.md).
|
||||
You can use the Web Editor to make changes to a single file directly from the GitLab UI.
|
||||
To make changes to multiple files, see [Web IDE](../web_ide/index.md).
|
||||
|
||||
Your [primary email address](../../../user/profile/index.md#change-the-email-displayed-on-your-commits)
|
||||
is used by default for any change you commit through the Web Editor.
|
||||
Your [primary email address](../../profile/index.md#change-the-email-displayed-on-your-commits)
|
||||
is used by default for any change you commit with the Web Editor.
|
||||
When you create or edit a file in the Web Editor, you can use the same
|
||||
[keyboard shortcuts](../../shortcuts.md#web-ide) for the Web IDE.
|
||||
|
||||
## Create a file
|
||||
|
||||
To create a text file in the Web Editor:
|
||||
|
||||
1. On the left sidebar, select **Search or go to** and find your project.
|
||||
1. From the project dashboard or repository, next to the branch name,
|
||||
select the plus icon (**{plus}**).
|
||||
1. From the dropdown list, select **New file**.
|
||||
1. Go to the directory where you want to create the new file.
|
||||
1. Next to the directory name, select the plus icon (**{plus}**) > **New file**.
|
||||
1. Complete the fields.
|
||||
1. To create a merge request with the new file, ensure the **Start a new merge request with these changes** checkbox is selected, if you had chosen a **Target branch** other than the [default branch (such as `main`)](../../../user/project/repository/branches/default.md).
|
||||
- To create a merge request with your changes, enter a branch name
|
||||
that's not your repository's [default branch](branches/default.md).
|
||||
1. Select **Commit changes**.
|
||||
|
||||
### Create a file from a template
|
||||
### From a template
|
||||
|
||||
To create a text file from a template in the Web Editor:
|
||||
|
||||
1. On the left sidebar, select **Search or go to** and find your project.
|
||||
1. Select **Code > Repository**.
|
||||
1. Next to the project name, select the plus icon (**{plus}**) to display a
|
||||
dropdown list, then select **New file** from the list.
|
||||
1. For **Filename**, provide one of the filenames that GitLab provides a template for:
|
||||
1. Go to the directory where you want to create the new file.
|
||||
1. Next to the directory name, select the plus icon (**{plus}**) > **New file**.
|
||||
1. In **Filename**, enter a filename that GitLab provides a template for:
|
||||
- `.gitignore`
|
||||
- `.gitlab-ci.yml`
|
||||
- `LICENSE`
|
||||
- `Dockerfile`
|
||||
1. Select **Apply a template**, then select the template you want to apply.
|
||||
1. Make your changes to the file.
|
||||
1. Provide a **Commit message**.
|
||||
1. Enter a **Target branch** to merge into. To create a new merge request with
|
||||
your changes, enter a branch name that is not your repository's
|
||||
[default branch](../../../user/project/repository/branches/default.md),
|
||||
1. Select **Commit changes** to add the commit to your branch.
|
||||
1. From the **Apply a template** dropdown list, select a template.
|
||||
1. Complete the fields.
|
||||
- To create a merge request with your changes, enter a branch name
|
||||
that's not your repository's [default branch](branches/default.md).
|
||||
1. Select **Commit changes**.
|
||||
|
||||
## Edit a file
|
||||
|
||||
To edit a text file in the Web Editor:
|
||||
|
||||
1. On the left sidebar, select **Search or go to** and find your project.
|
||||
1. Go to your file.
|
||||
1. In the upper right, select **Edit > Edit single file**.
|
||||
|
||||
Here you can use the same [keyboard shortcuts](../../shortcuts.md#web-ide) for the Web IDE.
|
||||
1. Go to the file you want to edit.
|
||||
1. Select **Edit > Edit single file**.
|
||||
1. Complete the fields.
|
||||
- To create a merge request with your changes, enter a branch name
|
||||
that's not your repository's [default branch](branches/default.md).
|
||||
1. Select **Commit changes**.
|
||||
|
||||
### Preview Markdown
|
||||
|
||||
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/378966) in GitLab 15.6.
|
||||
|
||||
To preview Markdown content in the Web Editor:
|
||||
To preview a Markdown file in the Web Editor:
|
||||
|
||||
1. [Edit a file](#edit-a-file).
|
||||
1. Do one of the following:
|
||||
- Select the **Preview** tab.
|
||||
- From the context menu, select **Preview Markdown**.
|
||||
1. On the left sidebar, select **Search or go to** and find your project.
|
||||
1. Go to the file you want to preview.
|
||||
1. Select **Edit > Edit single file**.
|
||||
1. Select the **Preview** tab.
|
||||
|
||||
In the **Preview** tab, you can see a live Markdown preview alongside your content.
|
||||
You can see a live Markdown preview alongside your content.
|
||||
|
||||
To close the preview panel, do one of the following:
|
||||
|
||||
- Select the **Write** tab.
|
||||
- From the context menu, select **Hide Live Preview**.
|
||||
To close the preview panel, select the **Write** tab.
|
||||
|
||||
### Link to specific lines
|
||||
|
||||
|
|
@ -79,22 +79,21 @@ information to the filename segment of the URL. For example:
|
|||
- `MY_FILE.js#L3` highlights line 3 in `MY_FILE.js`.
|
||||
- `MY_FILE.js#L3-10` highlights lines 3 to 10 in `MY_FILE.js`.
|
||||
|
||||
To link to a single line, you can also:
|
||||
|
||||
1. [Edit a file](#edit-a-file).
|
||||
1. Select a line number.
|
||||
When you edit a file, you can also link to a single line by selecting a line number.
|
||||
|
||||
## Upload a file
|
||||
|
||||
To upload a binary file in the Web Editor:
|
||||
To upload a file in the Web Editor:
|
||||
|
||||
<!-- This list is duplicated at doc/gitlab-basics/add-file.md#from-the-ui -->
|
||||
<!-- This list is duplicated at doc/user/project/repository/index.md#add-a-file-from-the-ui -->
|
||||
<!-- For why we duplicated the info, see https://gitlab.com/gitlab-org/gitlab/-/merge_requests/111072#note_1267429478 -->
|
||||
|
||||
1. On the left sidebar, select **Search or go to** and find your project.
|
||||
1. From the project dashboard or repository, next to the branch name, select the plus icon (**{plus}**).
|
||||
1. From the dropdown list, select **Upload file**.
|
||||
1. Complete the fields. To create a merge request with the uploaded file, ensure the **Start a new merge request with these changes** toggle is turned on.
|
||||
1. Go to the directory where you want to upload the file.
|
||||
1. Next to the directory name, select the plus icon (**{plus}**) > **Upload file**.
|
||||
1. Complete the fields.
|
||||
- To create a merge request with your changes, enter a branch name
|
||||
that's not your repository's [default branch](branches/default.md).
|
||||
1. Select **Upload file**.
|
||||
|
||||
## Create a directory
|
||||
|
|
@ -102,9 +101,11 @@ To upload a binary file in the Web Editor:
|
|||
To create a directory in the Web Editor:
|
||||
|
||||
1. On the left sidebar, select **Search or go to** and find your project.
|
||||
1. From the project dashboard or repository, next to the branch name, select the plus icon (**{plus}**).
|
||||
1. From the dropdown list, select **New directory**.
|
||||
1. Complete the fields. To create a merge request with the new directory, ensure the **Start a new merge request with these changes** toggle is turned on.
|
||||
1. Go to the directory where you want to create the new directory.
|
||||
1. Next to the directory name, select the plus icon (**{plus}**) > **New directory**.
|
||||
1. Complete the fields.
|
||||
- To create a merge request with your changes, enter a branch name
|
||||
that's not your repository's [default branch](branches/default.md).
|
||||
1. Select **Create directory**.
|
||||
|
||||
## Create a branch
|
||||
|
|
@ -112,8 +113,7 @@ To create a directory in the Web Editor:
|
|||
To create a [branch](branches/index.md) in the Web Editor:
|
||||
|
||||
1. On the left sidebar, select **Search or go to** and find your project.
|
||||
1. From the project dashboard or repository, next to the branch name, select the plus icon (**{plus}**).
|
||||
1. From the dropdown list, select **New branch**.
|
||||
1. Next to the repository name, select the plus icon (**{plus}**) > **New branch**.
|
||||
1. Complete the fields.
|
||||
1. Select **Create branch**.
|
||||
|
||||
|
|
@ -123,7 +123,6 @@ You can create [tags](tags/index.md) to mark milestones such as
|
|||
production releases and release candidates. To create a tag in the Web Editor:
|
||||
|
||||
1. On the left sidebar, select **Search or go to** and find your project.
|
||||
1. From the project dashboard or repository, next to the branch name, select the plus icon (**{plus}**).
|
||||
1. From the dropdown list, select **New tag**.
|
||||
1. Next to the repository name, select the plus icon (**{plus}**) > **New tag**.
|
||||
1. Complete the fields.
|
||||
1. Select **Create tag**.
|
||||
|
|
|
|||
|
|
@ -122,7 +122,7 @@ module Gitlab
|
|||
|
||||
# rubocop: disable CodeReuse/ActiveRecord
|
||||
def project_id
|
||||
@project_id ||= Project.where_full_path_in([full_path]).pluck(:id)
|
||||
@project_id ||= Project.where_full_path_in([full_path], use_includes: false).pluck(:id)
|
||||
end
|
||||
# rubocop: enable CodeReuse/ActiveRecord
|
||||
end
|
||||
|
|
|
|||
|
|
@ -264,6 +264,13 @@ module Gitlab
|
|||
100 * migrated_tuple_count / total_tuple_count
|
||||
end
|
||||
|
||||
def finalize_command
|
||||
<<~SCRIPT.delete("\n").squeeze(' ').strip
|
||||
sudo gitlab-rake gitlab:background_migrations:finalize
|
||||
[#{job_class_name},#{table_name},#{column_name},'#{job_arguments.to_json.gsub(',', '\,')}']
|
||||
SCRIPT
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def validate_batched_jobs_status
|
||||
|
|
|
|||
|
|
@ -231,7 +231,7 @@ module Gitlab
|
|||
"\n\n" \
|
||||
"Finalize it manually by running the following command in a `bash` or `sh` shell:" \
|
||||
"\n\n" \
|
||||
"\tsudo gitlab-rake gitlab:background_migrations:finalize[#{job_class_name},#{table_name},#{column_name},'#{job_arguments.to_json.gsub(',', '\,')}']" \
|
||||
"\t#{migration.finalize_command}" \
|
||||
"\n\n" \
|
||||
"For more information, check the documentation" \
|
||||
"\n\n" \
|
||||
|
|
|
|||
|
|
@ -77,6 +77,7 @@ module Gitlab
|
|||
push_frontend_feature_flag(:security_auto_fix)
|
||||
push_frontend_feature_flag(:source_editor_toolbar)
|
||||
push_frontend_feature_flag(:vscode_web_ide, current_user)
|
||||
push_frontend_feature_flag(:key_contacts_management, current_user)
|
||||
# To be removed with https://gitlab.com/gitlab-org/gitlab/-/issues/399248
|
||||
push_frontend_feature_flag(:remove_monitor_metrics)
|
||||
push_frontend_feature_flag(:custom_emoji)
|
||||
|
|
|
|||
|
|
@ -20,8 +20,7 @@ module Gitlab
|
|||
# this logic cannot be placed in the NamespaceResolver due to N+1
|
||||
scope = scope.without_project_namespaces if scope == Namespace
|
||||
# `with_route` avoids an N+1 calculating full_path
|
||||
scope = scope.where_full_path_in(full_paths).with_route
|
||||
.allow_cross_joins_across_databases(url: "https://gitlab.com/gitlab-org/gitlab/-/issues/420046")
|
||||
scope = scope.where_full_path_in(full_paths)
|
||||
|
||||
scope.each do |model_instance|
|
||||
loader.call(model_instance.full_path.downcase, model_instance)
|
||||
|
|
|
|||
|
|
@ -13840,6 +13840,9 @@ msgstr ""
|
|||
msgid "Copy command"
|
||||
msgstr ""
|
||||
|
||||
msgid "Copy command to finalize manually"
|
||||
msgstr ""
|
||||
|
||||
msgid "Copy commands"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -18985,7 +18988,7 @@ msgstr ""
|
|||
msgid "Environment|Deployments"
|
||||
msgstr ""
|
||||
|
||||
msgid "Environment|Environment health"
|
||||
msgid "Environment|Environment status"
|
||||
msgstr ""
|
||||
|
||||
msgid "Environment|External IP"
|
||||
|
|
@ -30337,15 +30340,9 @@ msgstr ""
|
|||
msgid "Metrics|Delete metric?"
|
||||
msgstr ""
|
||||
|
||||
msgid "Metrics|Description"
|
||||
msgstr ""
|
||||
|
||||
msgid "Metrics|Edit metric"
|
||||
msgstr ""
|
||||
|
||||
msgid "Metrics|Failed to load metrics."
|
||||
msgstr ""
|
||||
|
||||
msgid "Metrics|For grouping similar metrics"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -30355,15 +30352,9 @@ msgstr ""
|
|||
msgid "Metrics|Legend label (optional)"
|
||||
msgstr ""
|
||||
|
||||
msgid "Metrics|Metrics"
|
||||
msgstr ""
|
||||
|
||||
msgid "Metrics|Must be a valid PromQL query."
|
||||
msgstr ""
|
||||
|
||||
msgid "Metrics|Name"
|
||||
msgstr ""
|
||||
|
||||
msgid "Metrics|New metric"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -30376,9 +30367,6 @@ msgstr ""
|
|||
msgid "Metrics|There was an error trying to validate your query"
|
||||
msgstr ""
|
||||
|
||||
msgid "Metrics|Type"
|
||||
msgstr ""
|
||||
|
||||
msgid "Metrics|Unit label"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -32891,15 +32879,30 @@ msgstr ""
|
|||
msgid "Objective"
|
||||
msgstr ""
|
||||
|
||||
msgid "ObservabilityMetrics|Description"
|
||||
msgstr ""
|
||||
|
||||
msgid "ObservabilityMetrics|Error: Failed to load metrics details. Try reloading the page."
|
||||
msgstr ""
|
||||
|
||||
msgid "ObservabilityMetrics|Failed to load metrics."
|
||||
msgstr ""
|
||||
|
||||
msgid "ObservabilityMetrics|Metric Details"
|
||||
msgstr ""
|
||||
|
||||
msgid "ObservabilityMetrics|Metrics"
|
||||
msgstr ""
|
||||
|
||||
msgid "ObservabilityMetrics|Name"
|
||||
msgstr ""
|
||||
|
||||
msgid "ObservabilityMetrics|Search metrics starting with..."
|
||||
msgstr ""
|
||||
|
||||
msgid "ObservabilityMetrics|Type"
|
||||
msgstr ""
|
||||
|
||||
msgid "Observability|Enable"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -37438,6 +37441,12 @@ msgstr ""
|
|||
msgid "ProjectPage|Project ID: %{project_id}"
|
||||
msgstr ""
|
||||
|
||||
msgid "ProjectPage|Project information"
|
||||
msgstr ""
|
||||
|
||||
msgid "ProjectPage|Project settings"
|
||||
msgstr ""
|
||||
|
||||
msgid "ProjectQualitySummary|An error occurred while trying to fetch project quality statistics"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -50719,9 +50728,6 @@ msgstr ""
|
|||
msgid "TopNav|Explore"
|
||||
msgstr ""
|
||||
|
||||
msgid "TopNav|Go back"
|
||||
msgstr ""
|
||||
|
||||
msgid "TopNav|Switch to"
|
||||
msgstr ""
|
||||
|
||||
|
|
|
|||
|
|
@ -60,7 +60,7 @@
|
|||
"@gitlab/favicon-overlay": "2.0.0",
|
||||
"@gitlab/fonts": "^1.3.0",
|
||||
"@gitlab/svgs": "3.71.0",
|
||||
"@gitlab/ui": "^70.0.1",
|
||||
"@gitlab/ui": "^71.1.1",
|
||||
"@gitlab/visual-review-tools": "1.7.3",
|
||||
"@gitlab/web-ide": "^0.0.1-dev-20231129035648",
|
||||
"@mattiasbuelens/web-streams-adapter": "^0.1.0",
|
||||
|
|
@ -264,9 +264,6 @@
|
|||
"gettext-extractor": "^3.7.0",
|
||||
"gettext-extractor-vue": "^5.1.0",
|
||||
"glob": "^7.1.6",
|
||||
"istanbul-lib-coverage": "^3.0.0",
|
||||
"istanbul-lib-report": "^3.0.0",
|
||||
"istanbul-reports": "^3.0.0",
|
||||
"jest": "^28.1.3",
|
||||
"jest-canvas-mock": "^2.4.0",
|
||||
"jest-diff": "^28.1.3",
|
||||
|
|
|
|||
|
|
@ -1,60 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module QA
|
||||
module Mobile
|
||||
module Page
|
||||
module Main
|
||||
module Menu
|
||||
extend QA::Page::PageConcern
|
||||
|
||||
def self.prepended(base)
|
||||
super
|
||||
|
||||
base.class_eval do
|
||||
view 'app/views/layouts/header/_default.html.haml' do
|
||||
element :mobile_navbar_button, required: true
|
||||
end
|
||||
|
||||
view 'app/assets/javascripts/nav/components/responsive_home.vue' do
|
||||
element :mobile_new_dropdown
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def open_mobile_menu
|
||||
if has_no_element?('user-avatar-content')
|
||||
Support::Retrier.retry_until do
|
||||
click_element(:mobile_navbar_button)
|
||||
has_element?('user-avatar-content')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def open_mobile_new_dropdown
|
||||
open_mobile_menu
|
||||
|
||||
Support::Retrier.retry_until do
|
||||
find('[data-qa-selector="mobile_new_dropdown"] > button').click
|
||||
has_css?('.dropdown-menu-right.show')
|
||||
end
|
||||
end
|
||||
|
||||
def has_personal_area?(wait: Capybara.default_max_wait_time)
|
||||
open_mobile_menu
|
||||
super
|
||||
end
|
||||
|
||||
def has_no_personal_area?(wait: Capybara.default_max_wait_time)
|
||||
open_mobile_menu
|
||||
super
|
||||
end
|
||||
|
||||
def within_user_menu
|
||||
open_mobile_menu
|
||||
super
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -1,31 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module QA
|
||||
module Mobile
|
||||
module Page
|
||||
module Project
|
||||
module Show
|
||||
extend QA::Page::PageConcern
|
||||
|
||||
def self.prepended(base)
|
||||
super
|
||||
|
||||
base.class_eval do
|
||||
prepend QA::Mobile::Page::Main::Menu
|
||||
|
||||
view 'app/assets/javascripts/nav/components/top_nav_new_dropdown.vue' do
|
||||
element :new_issue_mobile_button
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def go_to_new_issue
|
||||
open_mobile_new_dropdown
|
||||
|
||||
click_element(:new_issue_mobile_button)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -1,28 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module QA
|
||||
module Mobile
|
||||
module Page
|
||||
module SubMenus
|
||||
module Common
|
||||
def open_mobile_nav_sidebar
|
||||
unless has_css?('.sidebar-expanded-mobile')
|
||||
Support::Retrier.retry_until do
|
||||
click_element('toggle-mobile-nav-button')
|
||||
has_css?('.sidebar-expanded-mobile')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def within_sidebar
|
||||
wait_for_requests
|
||||
|
||||
open_mobile_nav_sidebar
|
||||
|
||||
super
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -23,16 +23,6 @@ module QA
|
|||
def go_to_applications
|
||||
click_element('nav-item-link', submenu_item: 'Applications')
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def within_sidebar(&block)
|
||||
page.within('.sidebar-top-level-items', &block)
|
||||
end
|
||||
|
||||
def within_submenu(element, &block)
|
||||
within_element(element, &block)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -6,7 +6,6 @@ module QA
|
|||
class Menu < Page::Base
|
||||
# We need to check phone_layout? instead of mobile_layout? here
|
||||
# since tablets have the regular top navigation bar
|
||||
prepend Mobile::Page::Main::Menu if Runtime::Env.phone_layout?
|
||||
include SubMenus::CreateNewMenu
|
||||
include SubMenus::SuperSidebar::GlobalSearchModal
|
||||
|
||||
|
|
@ -40,14 +39,6 @@ module QA
|
|||
element 'global-search-input'
|
||||
end
|
||||
|
||||
view 'app/assets/javascripts/nav/components/top_nav_app.vue' do
|
||||
element :navbar_dropdown
|
||||
end
|
||||
|
||||
view 'app/assets/javascripts/nav/components/top_nav_dropdown_menu.vue' do
|
||||
element 'menu-subview'
|
||||
end
|
||||
|
||||
view 'lib/gitlab/nav/top_nav_menu_item.rb' do
|
||||
element :menu_item_link
|
||||
end
|
||||
|
|
|
|||
|
|
@ -9,9 +9,6 @@ module QA
|
|||
include Page::Component::Breadcrumbs
|
||||
include Page::File::Shared::CommitMessage
|
||||
include Page::Component::Dropdown
|
||||
# We need to check phone_layout? instead of mobile_layout? here
|
||||
# since tablets have the regular top navigation bar
|
||||
prepend Mobile::Page::Project::Show if Runtime::Env.phone_layout?
|
||||
|
||||
view 'app/assets/javascripts/repository/components/preview/index.vue' do
|
||||
element 'blob-viewer-content'
|
||||
|
|
@ -36,6 +33,9 @@ module QA
|
|||
view 'app/views/projects/_home_panel.html.haml' do
|
||||
element 'project-name-content'
|
||||
element 'project-id-content'
|
||||
end
|
||||
|
||||
view 'app/views/projects/_sidebar.html.haml' do
|
||||
element 'project-badges-content'
|
||||
element 'badge-image-link'
|
||||
end
|
||||
|
|
|
|||
|
|
@ -8,20 +8,12 @@ module QA
|
|||
super
|
||||
|
||||
base.class_eval do
|
||||
prepend Mobile::Page::SubMenus::Common if QA::Runtime::Env.mobile_layout?
|
||||
|
||||
view 'app/assets/javascripts/super_sidebar/components/super_sidebar.vue' do
|
||||
element :navbar
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def within_sidebar(&block)
|
||||
wait_for_requests
|
||||
|
||||
within_element(:navbar, &block)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# Opens the new item menu and yields to the block
|
||||
|
|
|
|||
|
|
@ -57,6 +57,6 @@ else
|
|||
end
|
||||
|
||||
puts missing_issue_message % missing_issues unless missing_issues.empty?
|
||||
puts "See https://about.gitlab.com/handbook/engineering/quality/quality-engineering/debugging-qa-test-failures/#quarantining-tests"
|
||||
puts "See https://about.gitlab.com/handbook/engineering/infrastructure/test-platform/debugging-qa-test-failures/#quarantining-tests"
|
||||
exit 1
|
||||
end
|
||||
|
|
|
|||
|
|
@ -25,7 +25,10 @@ RSpec.describe Projects::Ml::ShowMlModelComponent, type: :component, feature_cat
|
|||
'path' => "/#{project.full_path}/-/ml/models/#{model1.id}",
|
||||
'description' => 'This is a placeholder for the short description',
|
||||
'latestVersion' => {
|
||||
'version' => model1.latest_version.version
|
||||
'version' => model1.latest_version.version,
|
||||
'description' => model1.latest_version.description,
|
||||
'projectPath' => "/#{project.full_path}",
|
||||
'packageId' => model1.latest_version.package_id
|
||||
},
|
||||
'versionCount' => 1
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ require "spec_helper"
|
|||
RSpec.describe Projects::Ml::ShowMlModelVersionComponent, type: :component, feature_category: :mlops do
|
||||
let_it_be(:project) { build_stubbed(:project) }
|
||||
let_it_be(:model) { build_stubbed(:ml_models, project: project) }
|
||||
let_it_be(:version) { build_stubbed(:ml_model_versions, model: model) }
|
||||
let_it_be(:version) { build_stubbed(:ml_model_versions, :with_package, model: model, description: 'abc') }
|
||||
|
||||
subject(:component) do
|
||||
described_class.new(model_version: version)
|
||||
|
|
@ -23,7 +23,10 @@ RSpec.describe Projects::Ml::ShowMlModelVersionComponent, type: :component, feat
|
|||
'modelVersion' => {
|
||||
'id' => version.id,
|
||||
'version' => version.version,
|
||||
'description' => 'abc',
|
||||
'projectPath' => "/#{project.full_path}",
|
||||
'path' => "/#{project.full_path}/-/ml/models/#{model.id}/versions/#{version.id}",
|
||||
'packageId' => version.package_id,
|
||||
'model' => {
|
||||
'name' => model.name,
|
||||
'path' => "/#{project.full_path}/-/ml/models/#{model.id}"
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ FactoryBot.define do
|
|||
|
||||
model { association :ml_models }
|
||||
project { model.project }
|
||||
description { 'Some description' }
|
||||
|
||||
trait :with_package do
|
||||
package do
|
||||
|
|
|
|||
|
|
@ -3,8 +3,6 @@
|
|||
require 'spec_helper'
|
||||
|
||||
RSpec.describe 'Frequently visited items', :js, feature_category: :shared do
|
||||
include Features::TopNavSpecHelpers
|
||||
|
||||
let_it_be(:user) { create(:user) }
|
||||
|
||||
before do
|
||||
|
|
|
|||
|
|
@ -3,8 +3,6 @@
|
|||
require 'spec_helper'
|
||||
|
||||
RSpec.describe 'New project', :js, feature_category: :groups_and_projects do
|
||||
include Features::TopNavSpecHelpers
|
||||
|
||||
before do
|
||||
stub_application_setting(import_sources: Gitlab::ImportSources.values)
|
||||
end
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ RSpec.describe 'Projects > Show > User sees Git instructions', feature_category:
|
|||
# validation failure on NotificationSetting.
|
||||
# See https://gitlab.com/gitlab-org/gitlab/-/issues/299822#note_492817174
|
||||
user.notification_settings.reset
|
||||
stub_feature_flags(project_overview_reorg: false)
|
||||
end
|
||||
|
||||
shared_examples_for 'redirects to the sign in page' do
|
||||
|
|
|
|||
|
|
@ -17,8 +17,8 @@ RSpec.describe 'Projects > Show > User sees setup shortcut buttons', feature_cat
|
|||
|
||||
describe 'as a normal user' do
|
||||
before do
|
||||
stub_feature_flags(project_overview_reorg: false)
|
||||
sign_in(user)
|
||||
|
||||
visit project_path(project)
|
||||
end
|
||||
|
||||
|
|
@ -40,6 +40,7 @@ RSpec.describe 'Projects > Show > User sees setup shortcut buttons', feature_cat
|
|||
|
||||
describe 'as a maintainer' do
|
||||
before do
|
||||
stub_feature_flags(project_overview_reorg: false)
|
||||
project.add_maintainer(user)
|
||||
sign_in(user)
|
||||
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ RSpec.describe 'Project', feature_category: :groups_and_projects do
|
|||
before do
|
||||
sign_in user
|
||||
visit new_project_path
|
||||
stub_feature_flags(project_overview_reorg: false)
|
||||
end
|
||||
|
||||
shared_examples 'creates from template' do |template, sub_template_tab = nil|
|
||||
|
|
@ -99,6 +100,7 @@ RSpec.describe 'Project', feature_category: :groups_and_projects do
|
|||
|
||||
before do
|
||||
sign_in(project.first_owner)
|
||||
stub_feature_flags(project_overview_reorg: false)
|
||||
end
|
||||
|
||||
it 'parses Markdown' do
|
||||
|
|
@ -164,6 +166,7 @@ RSpec.describe 'Project', feature_category: :groups_and_projects do
|
|||
before do
|
||||
sign_in(project.first_owner)
|
||||
visit path
|
||||
stub_feature_flags(project_overview_reorg: false)
|
||||
end
|
||||
|
||||
it 'shows project topics' do
|
||||
|
|
@ -195,6 +198,7 @@ RSpec.describe 'Project', feature_category: :groups_and_projects do
|
|||
before do
|
||||
sign_in(project.first_owner)
|
||||
visit path
|
||||
stub_feature_flags(project_overview_reorg: false)
|
||||
end
|
||||
|
||||
context 'desktop component' do
|
||||
|
|
@ -427,6 +431,10 @@ RSpec.describe 'Project', feature_category: :groups_and_projects do
|
|||
let(:project) { create(:project, :repository) }
|
||||
let(:user) { create(:user) }
|
||||
|
||||
before do
|
||||
stub_feature_flags(project_overview_reorg: false)
|
||||
end
|
||||
|
||||
it 'does not contain default branch information in its content', :js do
|
||||
default_branch = 'merge-commit-analyze-side-branch'
|
||||
|
||||
|
|
|
|||
|
|
@ -18,8 +18,10 @@ RSpec.describe 'User uploads avatar to profile', feature_category: :user_profile
|
|||
|
||||
wait_for_all_requests
|
||||
|
||||
data_uri = find('.avatar-image .gl-avatar')['src']
|
||||
within_testid('user-dropdown') { expect(find('.gl-avatar')['src']).to eq data_uri }
|
||||
within_testid('user-dropdown') do
|
||||
# We are setting a blob URL
|
||||
expect(find('.gl-avatar')['src']).to start_with 'blob:'
|
||||
end
|
||||
|
||||
visit profile_path
|
||||
|
||||
|
|
|
|||
|
|
@ -149,14 +149,14 @@ describe('~/environments/components/kubernetes_overview.vue', () => {
|
|||
});
|
||||
|
||||
it('sets `clusterHealthStatus` as error when pods emitted a failure', async () => {
|
||||
findKubernetesPods().vm.$emit('failed');
|
||||
findKubernetesPods().vm.$emit('update-failed-state', { pods: true });
|
||||
await nextTick();
|
||||
|
||||
expect(findKubernetesStatusBar().props('clusterHealthStatus')).toBe('error');
|
||||
});
|
||||
|
||||
it('sets `clusterHealthStatus` as error when workload types emitted a failure', async () => {
|
||||
findKubernetesTabs().vm.$emit('failed');
|
||||
findKubernetesTabs().vm.$emit('update-failed-state', { summary: true });
|
||||
await nextTick();
|
||||
|
||||
expect(findKubernetesStatusBar().props('clusterHealthStatus')).toBe('error');
|
||||
|
|
@ -165,6 +165,21 @@ describe('~/environments/components/kubernetes_overview.vue', () => {
|
|||
it('sets `clusterHealthStatus` as success when data is loaded and no failures where emitted', () => {
|
||||
expect(findKubernetesStatusBar().props('clusterHealthStatus')).toBe('success');
|
||||
});
|
||||
|
||||
it('sets `clusterHealthStatus` as success after state update if there are no failures', async () => {
|
||||
findKubernetesTabs().vm.$emit('update-failed-state', { summary: true });
|
||||
findKubernetesTabs().vm.$emit('update-failed-state', { pods: true });
|
||||
await nextTick();
|
||||
expect(findKubernetesStatusBar().props('clusterHealthStatus')).toBe('error');
|
||||
|
||||
findKubernetesTabs().vm.$emit('update-failed-state', { summary: false });
|
||||
await nextTick();
|
||||
expect(findKubernetesStatusBar().props('clusterHealthStatus')).toBe('error');
|
||||
|
||||
findKubernetesTabs().vm.$emit('update-failed-state', { pods: false });
|
||||
await nextTick();
|
||||
expect(findKubernetesStatusBar().props('clusterHealthStatus')).toBe('success');
|
||||
});
|
||||
});
|
||||
|
||||
describe('on cluster error', () => {
|
||||
|
|
|
|||
|
|
@ -90,11 +90,17 @@ describe('~/environments/components/kubernetes_pods.vue', () => {
|
|||
]);
|
||||
});
|
||||
|
||||
it('emits a failed event when there are failed pods', async () => {
|
||||
it('emits a update-failed-state event for each pod', async () => {
|
||||
createWrapper();
|
||||
await waitForPromises();
|
||||
|
||||
expect(wrapper.emitted('failed')).toHaveLength(1);
|
||||
expect(wrapper.emitted('update-failed-state')).toHaveLength(4);
|
||||
expect(wrapper.emitted('update-failed-state')).toEqual([
|
||||
[{ pods: false }],
|
||||
[{ pods: false }],
|
||||
[{ pods: false }],
|
||||
[{ pods: true }],
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -107,8 +107,8 @@ 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 a update-failed-state event when there are failed workload types', () => {
|
||||
expect(wrapper.emitted('update-failed-state')).toEqual([[{ summary: true }]]);
|
||||
});
|
||||
|
||||
it('emits an error message when gets an error from the cluster_client API', async () => {
|
||||
|
|
|
|||
|
|
@ -179,9 +179,10 @@ describe('~/environments/components/kubernetes_tabs.vue', () => {
|
|||
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);
|
||||
it('emits a state update event when gets it from the component', () => {
|
||||
const eventData = { summary: true };
|
||||
findKubernetesSummary().vm.$emit('update-failed-state', eventData);
|
||||
expect(wrapper.emitted('update-failed-state')).toEqual([[eventData]]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import { GlBadge, GlTab } from '@gitlab/ui';
|
|||
import { shallowMount } from '@vue/test-utils';
|
||||
import { ShowMlModel } from '~/ml/model_registry/apps';
|
||||
import ModelVersionList from '~/ml/model_registry/components/model_version_list.vue';
|
||||
import ModelVersionDetail from '~/ml/model_registry/components/model_version_detail.vue';
|
||||
import TitleArea from '~/vue_shared/components/registry/title_area.vue';
|
||||
import MetadataItem from '~/vue_shared/components/registry/metadata_item.vue';
|
||||
import { NO_VERSIONS_LABEL } from '~/ml/model_registry/translations';
|
||||
|
|
@ -16,6 +17,7 @@ const findDetailTab = () => wrapper.findAllComponents(GlTab).at(0);
|
|||
const findVersionsTab = () => wrapper.findAllComponents(GlTab).at(1);
|
||||
const findVersionsCountBadge = () => findVersionsTab().findComponent(GlBadge);
|
||||
const findModelVersionList = () => findVersionsTab().findComponent(ModelVersionList);
|
||||
const findModelVersionDetail = () => findDetailTab().findComponent(ModelVersionDetail);
|
||||
const findCandidateTab = () => wrapper.findAllComponents(GlTab).at(2);
|
||||
const findCandidatesCountBadge = () => findCandidateTab().findComponent(GlBadge);
|
||||
const findTitleArea = () => wrapper.findComponent(TitleArea);
|
||||
|
|
@ -47,7 +49,11 @@ describe('ShowMlModel', () => {
|
|||
|
||||
describe('when it has latest version', () => {
|
||||
it('displays the version', () => {
|
||||
expect(findDetailTab().text()).toContain(MODEL.latestVersion.version);
|
||||
expect(findModelVersionDetail().props('modelVersion')).toBe(MODEL.latestVersion);
|
||||
});
|
||||
|
||||
it('displays the title', () => {
|
||||
expect(findDetailTab().text()).toContain('Latest version: 1.2.3');
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -59,6 +65,10 @@ describe('ShowMlModel', () => {
|
|||
it('shows no version message', () => {
|
||||
expect(findDetailTab().text()).toContain(NO_VERSIONS_LABEL);
|
||||
});
|
||||
|
||||
it('does not render model version detail', () => {
|
||||
expect(findModelVersionDetail().exists()).toBe(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue