Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
7892ed2e23
commit
90c9981395
58 changed files with 819 additions and 424 deletions
|
@ -66,6 +66,7 @@ export default {
|
|||
ref="contentViewer"
|
||||
:content="content"
|
||||
:type="activeViewer.fileType"
|
||||
data-qa-selector="file_content"
|
||||
/>
|
||||
</template>
|
||||
</div>
|
||||
|
|
|
@ -1,13 +1,10 @@
|
|||
import { __ } from '~/locale';
|
||||
|
||||
class RecentSearchesServiceError {
|
||||
class RecentSearchesServiceError extends Error {
|
||||
constructor(message) {
|
||||
super(message || __('Recent Searches Service is unavailable'));
|
||||
this.name = 'RecentSearchesServiceError';
|
||||
this.message = message || __('Recent Searches Service is unavailable');
|
||||
}
|
||||
}
|
||||
|
||||
// Can't use `extends` for builtin prototypes and get true inheritance yet
|
||||
RecentSearchesServiceError.prototype = Error.prototype;
|
||||
|
||||
export default RecentSearchesServiceError;
|
||||
|
|
|
@ -6,6 +6,7 @@ import { addDelimiter } from './lib/utils/text_utility';
|
|||
import flash from './flash';
|
||||
import CreateMergeRequestDropdown from './create_merge_request_dropdown';
|
||||
import IssuablesHelper from './helpers/issuables_helper';
|
||||
import { joinPaths } from '~/lib/utils/url_utility';
|
||||
import { __ } from './locale';
|
||||
|
||||
export default class Issue {
|
||||
|
@ -14,6 +15,16 @@ export default class Issue {
|
|||
|
||||
if ($('.js-close-blocked-issue-warning').length) this.initIssueWarningBtnEventListener();
|
||||
|
||||
if ($('.js-alert-moved-from-service-desk-warning').length) {
|
||||
const trimmedPathname = window.location.pathname.slice(1);
|
||||
this.alertMovedFromServiceDeskDismissedKey = joinPaths(
|
||||
trimmedPathname,
|
||||
'alert-issue-moved-from-service-desk-dismissed',
|
||||
);
|
||||
|
||||
this.initIssueMovedFromServiceDeskDismissHandler();
|
||||
}
|
||||
|
||||
Issue.$btnNewBranch = $('#new-branch');
|
||||
Issue.createMrDropdownWrap = document.querySelector('.create-mr-dropdown-wrap');
|
||||
|
||||
|
@ -167,6 +178,21 @@ export default class Issue {
|
|||
});
|
||||
}
|
||||
|
||||
initIssueMovedFromServiceDeskDismissHandler() {
|
||||
const alertMovedFromServiceDeskWarning = $('.js-alert-moved-from-service-desk-warning');
|
||||
|
||||
if (!localStorage.getItem(this.alertMovedFromServiceDeskDismissedKey)) {
|
||||
alertMovedFromServiceDeskWarning.show();
|
||||
}
|
||||
|
||||
alertMovedFromServiceDeskWarning.on('click', '.js-close', e => {
|
||||
e.preventDefault();
|
||||
e.stopImmediatePropagation();
|
||||
alertMovedFromServiceDeskWarning.remove();
|
||||
localStorage.setItem(this.alertMovedFromServiceDeskDismissedKey, true);
|
||||
});
|
||||
}
|
||||
|
||||
static submitNoteForm(form) {
|
||||
const noteText = form.find('textarea.js-note-text').val();
|
||||
if (noteText && noteText.trim().length > 0) {
|
||||
|
|
|
@ -1,68 +0,0 @@
|
|||
<script>
|
||||
import { GlAlert, GlSprintf, GlLink } from '@gitlab/ui';
|
||||
import { mapState } from 'vuex';
|
||||
import { approximateDuration, calculateRemainingMilliseconds } from '~/lib/utils/datetime_utility';
|
||||
import {
|
||||
EXPIRATION_POLICY_ALERT_TITLE,
|
||||
EXPIRATION_POLICY_ALERT_PRIMARY_BUTTON,
|
||||
EXPIRATION_POLICY_ALERT_FULL_MESSAGE,
|
||||
EXPIRATION_POLICY_ALERT_SHORT_MESSAGE,
|
||||
} from '../constants';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
GlAlert,
|
||||
GlSprintf,
|
||||
GlLink,
|
||||
},
|
||||
|
||||
computed: {
|
||||
...mapState(['config', 'images', 'isLoading']),
|
||||
isEmpty() {
|
||||
return !this.images || this.images.length === 0;
|
||||
},
|
||||
showAlert() {
|
||||
return this.config.expirationPolicy?.enabled;
|
||||
},
|
||||
timeTillRun() {
|
||||
const difference = calculateRemainingMilliseconds(this.config.expirationPolicy?.next_run_at);
|
||||
return approximateDuration(difference / 1000);
|
||||
},
|
||||
alertConfiguration() {
|
||||
if (this.isEmpty || this.isLoading) {
|
||||
return {
|
||||
title: null,
|
||||
primaryButton: null,
|
||||
message: EXPIRATION_POLICY_ALERT_SHORT_MESSAGE,
|
||||
};
|
||||
}
|
||||
return {
|
||||
title: EXPIRATION_POLICY_ALERT_TITLE,
|
||||
primaryButton: EXPIRATION_POLICY_ALERT_PRIMARY_BUTTON,
|
||||
message: EXPIRATION_POLICY_ALERT_FULL_MESSAGE,
|
||||
};
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<gl-alert
|
||||
v-if="showAlert"
|
||||
:dismissible="false"
|
||||
:primary-button-text="alertConfiguration.primaryButton"
|
||||
:primary-button-link="config.settingsPath"
|
||||
:title="alertConfiguration.title"
|
||||
>
|
||||
<gl-sprintf :message="alertConfiguration.message">
|
||||
<template #days>
|
||||
<strong>{{ timeTillRun }}</strong>
|
||||
</template>
|
||||
<template #link="{content}">
|
||||
<gl-link :href="config.expirationPolicyHelpPagePath" target="_blank">
|
||||
{{ content }}
|
||||
</gl-link>
|
||||
</template>
|
||||
</gl-sprintf>
|
||||
</gl-alert>
|
||||
</template>
|
|
@ -0,0 +1,138 @@
|
|||
<script>
|
||||
import { GlSprintf, GlLink, GlIcon } from '@gitlab/ui';
|
||||
import { n__ } from '~/locale';
|
||||
import { approximateDuration, calculateRemainingMilliseconds } from '~/lib/utils/datetime_utility';
|
||||
|
||||
import {
|
||||
CONTAINER_REGISTRY_TITLE,
|
||||
LIST_INTRO_TEXT,
|
||||
EXPIRATION_POLICY_WILL_RUN_IN,
|
||||
EXPIRATION_POLICY_DISABLED_TEXT,
|
||||
EXPIRATION_POLICY_DISABLED_MESSAGE,
|
||||
} from '../constants';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
GlIcon,
|
||||
GlSprintf,
|
||||
GlLink,
|
||||
},
|
||||
props: {
|
||||
expirationPolicy: {
|
||||
type: Object,
|
||||
default: () => ({}),
|
||||
required: false,
|
||||
},
|
||||
imagesCount: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
required: false,
|
||||
},
|
||||
helpPagePath: {
|
||||
type: String,
|
||||
default: '',
|
||||
required: false,
|
||||
},
|
||||
expirationPolicyHelpPagePath: {
|
||||
type: String,
|
||||
default: '',
|
||||
required: false,
|
||||
},
|
||||
hideExpirationPolicyData: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
loader: {
|
||||
repeat: 10,
|
||||
width: 1000,
|
||||
height: 40,
|
||||
},
|
||||
i18n: {
|
||||
CONTAINER_REGISTRY_TITLE,
|
||||
LIST_INTRO_TEXT,
|
||||
EXPIRATION_POLICY_DISABLED_MESSAGE,
|
||||
},
|
||||
computed: {
|
||||
imagesCountText() {
|
||||
return n__(
|
||||
'ContainerRegistry|%{count} Image repository',
|
||||
'ContainerRegistry|%{count} Image repositories',
|
||||
this.imagesCount,
|
||||
);
|
||||
},
|
||||
timeTillRun() {
|
||||
const difference = calculateRemainingMilliseconds(this.expirationPolicy?.next_run_at);
|
||||
return approximateDuration(difference / 1000);
|
||||
},
|
||||
expirationPolicyEnabled() {
|
||||
return this.expirationPolicy?.enabled;
|
||||
},
|
||||
expirationPolicyText() {
|
||||
return this.expirationPolicyEnabled
|
||||
? EXPIRATION_POLICY_WILL_RUN_IN
|
||||
: EXPIRATION_POLICY_DISABLED_TEXT;
|
||||
},
|
||||
showExpirationPolicyTip() {
|
||||
return (
|
||||
!this.expirationPolicyEnabled && this.imagesCount > 0 && !this.hideExpirationPolicyData
|
||||
);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<div
|
||||
class="gl-display-flex gl-justify-content-space-between gl-align-items-center"
|
||||
data-testid="header"
|
||||
>
|
||||
<h4 data-testid="title">{{ $options.i18n.CONTAINER_REGISTRY_TITLE }}</h4>
|
||||
<div class="gl-display-none d-sm-block" data-testid="commands-slot">
|
||||
<slot name="commands"></slot>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
v-if="imagesCount"
|
||||
class="gl-display-flex gl-align-items-center gl-mt-1 gl-mb-3 gl-text-gray-700"
|
||||
data-testid="subheader"
|
||||
>
|
||||
<span class="gl-mr-3" data-testid="images-count">
|
||||
<gl-icon class="gl-mr-1" name="container-image" />
|
||||
<gl-sprintf :message="imagesCountText">
|
||||
<template #count>
|
||||
{{ imagesCount }}
|
||||
</template>
|
||||
</gl-sprintf>
|
||||
</span>
|
||||
<span v-if="!hideExpirationPolicyData" data-testid="expiration-policy">
|
||||
<gl-icon class="gl-mr-1" name="expire" />
|
||||
<gl-sprintf :message="expirationPolicyText">
|
||||
<template #time>
|
||||
{{ timeTillRun }}
|
||||
</template>
|
||||
</gl-sprintf>
|
||||
</span>
|
||||
</div>
|
||||
<div data-testid="info-area">
|
||||
<p>
|
||||
<span data-testid="default-intro">
|
||||
<gl-sprintf :message="$options.i18n.LIST_INTRO_TEXT">
|
||||
<template #docLink="{content}">
|
||||
<gl-link :href="helpPagePath" target="_blank">{{ content }}</gl-link>
|
||||
</template>
|
||||
</gl-sprintf>
|
||||
</span>
|
||||
<span v-if="showExpirationPolicyTip" data-testid="expiration-disabled-message">
|
||||
<gl-sprintf :message="$options.i18n.EXPIRATION_POLICY_DISABLED_MESSAGE">
|
||||
<template #docLink="{content}">
|
||||
<gl-link :href="expirationPolicyHelpPagePath" target="_blank">{{ content }}</gl-link>
|
||||
</template>
|
||||
</gl-sprintf>
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
|
@ -5,10 +5,10 @@ import { s__ } from '~/locale';
|
|||
export const CONTAINER_REGISTRY_TITLE = s__('ContainerRegistry|Container Registry');
|
||||
export const CONNECTION_ERROR_TITLE = s__('ContainerRegistry|Docker connection error');
|
||||
export const CONNECTION_ERROR_MESSAGE = s__(
|
||||
`ContainerRegistry|We are having trouble connecting to Docker, which could be due to an issue with your project name or path. %{docLinkStart}More Information%{docLinkEnd}`,
|
||||
`ContainerRegistry|We are having trouble connecting to the Registry, which could be due to an issue with your project name or path. %{docLinkStart}More information%{docLinkEnd}`,
|
||||
);
|
||||
export const LIST_INTRO_TEXT = s__(
|
||||
`ContainerRegistry|With the Docker Container Registry integrated into GitLab, every project can have its own space to store its Docker images. %{docLinkStart}More Information%{docLinkEnd}`,
|
||||
`ContainerRegistry|With the GitLab Container Registry, every project can have its own space to store images. %{docLinkStart}More information%{docLinkEnd}`,
|
||||
);
|
||||
|
||||
export const LIST_DELETE_BUTTON_DISABLED = s__(
|
||||
|
@ -103,20 +103,21 @@ export const ADMIN_GARBAGE_COLLECTION_TIP = s__(
|
|||
|
||||
// Expiration policies
|
||||
|
||||
export const EXPIRATION_POLICY_ALERT_TITLE = s__(
|
||||
'ContainerRegistry|Retention policy has been Enabled',
|
||||
export const EXPIRATION_POLICY_WILL_RUN_IN = s__(
|
||||
'ContainerRegistry|Expiration policy will run in %{time}',
|
||||
);
|
||||
export const EXPIRATION_POLICY_ALERT_PRIMARY_BUTTON = s__('ContainerRegistry|Edit Settings');
|
||||
export const EXPIRATION_POLICY_ALERT_FULL_MESSAGE = s__(
|
||||
'ContainerRegistry|The retention and expiration policy for this Container Registry has been enabled and will run in %{days}. For more information visit the %{linkStart}documentation%{linkEnd}',
|
||||
|
||||
export const EXPIRATION_POLICY_DISABLED_TEXT = s__(
|
||||
'ContainerRegistry|Expiration policy is disabled',
|
||||
);
|
||||
export const EXPIRATION_POLICY_ALERT_SHORT_MESSAGE = s__(
|
||||
'ContainerRegistry|The retention and expiration policy for this Container Registry has been enabled. For more information visit the %{linkStart}documentation%{linkEnd}',
|
||||
|
||||
export const EXPIRATION_POLICY_DISABLED_MESSAGE = s__(
|
||||
'ContainerRegistry|Expiration policies help manage the storage space used by the Container Registry, but the expiration policies for this registry are disabled. Contact your administrator to enable. %{docLinkStart}More information%{docLinkEnd}',
|
||||
);
|
||||
|
||||
// Quick Start
|
||||
|
||||
export const QUICK_START = s__('ContainerRegistry|Quick Start');
|
||||
export const QUICK_START = s__('ContainerRegistry|CLI Commands');
|
||||
export const LOGIN_COMMAND_LABEL = s__('ContainerRegistry|Login');
|
||||
export const COPY_LOGIN_TITLE = s__('ContainerRegistry|Copy login command');
|
||||
export const BUILD_COMMAND_LABEL = s__('ContainerRegistry|Build an image');
|
||||
|
|
|
@ -14,17 +14,15 @@ import Tracking from '~/tracking';
|
|||
|
||||
import ProjectEmptyState from '../components/project_empty_state.vue';
|
||||
import GroupEmptyState from '../components/group_empty_state.vue';
|
||||
import ProjectPolicyAlert from '../components/project_policy_alert.vue';
|
||||
import QuickstartDropdown from '../components/quickstart_dropdown.vue';
|
||||
import RegistryHeader from '../components/registry_header.vue';
|
||||
import ImageList from '../components/image_list.vue';
|
||||
import CliCommands from '../components/cli_commands.vue';
|
||||
|
||||
import {
|
||||
DELETE_IMAGE_SUCCESS_MESSAGE,
|
||||
DELETE_IMAGE_ERROR_MESSAGE,
|
||||
CONTAINER_REGISTRY_TITLE,
|
||||
CONNECTION_ERROR_TITLE,
|
||||
CONNECTION_ERROR_MESSAGE,
|
||||
LIST_INTRO_TEXT,
|
||||
REMOVE_REPOSITORY_MODAL_TEXT,
|
||||
REMOVE_REPOSITORY_LABEL,
|
||||
SEARCH_PLACEHOLDER_TEXT,
|
||||
|
@ -39,8 +37,6 @@ export default {
|
|||
GlEmptyState,
|
||||
ProjectEmptyState,
|
||||
GroupEmptyState,
|
||||
ProjectPolicyAlert,
|
||||
QuickstartDropdown,
|
||||
ImageList,
|
||||
GlModal,
|
||||
GlSprintf,
|
||||
|
@ -48,6 +44,8 @@ export default {
|
|||
GlAlert,
|
||||
GlSkeletonLoader,
|
||||
GlSearchBoxByClick,
|
||||
RegistryHeader,
|
||||
CliCommands,
|
||||
},
|
||||
directives: {
|
||||
GlTooltip: GlTooltipDirective,
|
||||
|
@ -59,10 +57,8 @@ export default {
|
|||
height: 40,
|
||||
},
|
||||
i18n: {
|
||||
CONTAINER_REGISTRY_TITLE,
|
||||
CONNECTION_ERROR_TITLE,
|
||||
CONNECTION_ERROR_MESSAGE,
|
||||
LIST_INTRO_TEXT,
|
||||
REMOVE_REPOSITORY_MODAL_TEXT,
|
||||
REMOVE_REPOSITORY_LABEL,
|
||||
SEARCH_PLACEHOLDER_TEXT,
|
||||
|
@ -85,7 +81,7 @@ export default {
|
|||
label: 'registry_repository_delete',
|
||||
};
|
||||
},
|
||||
showQuickStartDropdown() {
|
||||
showCommands() {
|
||||
return Boolean(!this.isLoading && !this.config?.isGroupPage && this.images?.length);
|
||||
},
|
||||
showDeleteAlert() {
|
||||
|
@ -149,8 +145,6 @@ export default {
|
|||
</gl-sprintf>
|
||||
</gl-alert>
|
||||
|
||||
<project-policy-alert v-if="!config.isGroupPage" class="mt-2" />
|
||||
|
||||
<gl-empty-state
|
||||
v-if="config.characterError"
|
||||
:title="$options.i18n.CONNECTION_ERROR_TITLE"
|
||||
|
@ -170,21 +164,17 @@ export default {
|
|||
</gl-empty-state>
|
||||
|
||||
<template v-else>
|
||||
<div>
|
||||
<div class="d-flex justify-content-between align-items-center">
|
||||
<h4>{{ $options.i18n.CONTAINER_REGISTRY_TITLE }}</h4>
|
||||
<quickstart-dropdown v-if="showQuickStartDropdown" class="d-none d-sm-block" />
|
||||
</div>
|
||||
<p>
|
||||
<gl-sprintf :message="$options.i18n.LIST_INTRO_TEXT">
|
||||
<template #docLink="{content}">
|
||||
<gl-link :href="config.helpPagePath" target="_blank">
|
||||
{{ content }}
|
||||
</gl-link>
|
||||
<registry-header
|
||||
:images-count="pagination.total"
|
||||
:expiration-policy="config.expirationPolicy"
|
||||
:help-page-path="config.helpPagePath"
|
||||
:expiration-policy-help-page-path="config.expirationPolicyHelpPagePath"
|
||||
:hide-expiration-policy-data="config.isGroupPage"
|
||||
>
|
||||
<template #commands>
|
||||
<cli-commands v-if="showCommands" />
|
||||
</template>
|
||||
</gl-sprintf>
|
||||
</p>
|
||||
</div>
|
||||
</registry-header>
|
||||
|
||||
<div v-if="isLoading" class="mt-2">
|
||||
<gl-skeleton-loader
|
||||
|
@ -201,7 +191,7 @@ export default {
|
|||
</div>
|
||||
<template v-else>
|
||||
<template v-if="!isEmpty">
|
||||
<div class="gl-display-flex gl-p-1" data-testid="listHeader">
|
||||
<div class="gl-display-flex gl-p-1 gl-mt-3" data-testid="listHeader">
|
||||
<div class="gl-flex-fill-1">
|
||||
<h5>{{ $options.i18n.IMAGE_REPOSITORY_LIST_LABEL }}</h5>
|
||||
</div>
|
||||
|
|
|
@ -1,5 +1,9 @@
|
|||
import { __ } from '~/locale';
|
||||
import { generateToolbarItem } from './toolbar_service';
|
||||
import { generateToolbarItem } from './editor_service';
|
||||
|
||||
export const CUSTOM_EVENTS = {
|
||||
openAddImageModal: 'gl_openAddImageModal',
|
||||
};
|
||||
|
||||
/* eslint-disable @gitlab/require-i18n-strings */
|
||||
const TOOLBAR_ITEM_CONFIGS = [
|
||||
|
|
|
@ -12,7 +12,6 @@ const buildWrapper = propsData => {
|
|||
return instance.$el;
|
||||
};
|
||||
|
||||
// eslint-disable-next-line import/prefer-default-export
|
||||
export const generateToolbarItem = config => {
|
||||
const { icon, classes, event, command, tooltip, isDivider } = config;
|
||||
|
||||
|
@ -30,3 +29,8 @@ export const generateToolbarItem = config => {
|
|||
},
|
||||
};
|
||||
};
|
||||
|
||||
export const addCustomEventListener = (editorInstance, event, handler) => {
|
||||
editorInstance.eventManager.addEventType(event);
|
||||
editorInstance.eventManager.listen(event, handler);
|
||||
};
|
|
@ -2,7 +2,15 @@
|
|||
import 'codemirror/lib/codemirror.css';
|
||||
import '@toast-ui/editor/dist/toastui-editor.css';
|
||||
|
||||
import { EDITOR_OPTIONS, EDITOR_TYPES, EDITOR_HEIGHT, EDITOR_PREVIEW_STYLE } from './constants';
|
||||
import {
|
||||
EDITOR_OPTIONS,
|
||||
EDITOR_TYPES,
|
||||
EDITOR_HEIGHT,
|
||||
EDITOR_PREVIEW_STYLE,
|
||||
CUSTOM_EVENTS,
|
||||
} from './constants';
|
||||
|
||||
import { addCustomEventListener } from './editor_service';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
|
@ -49,6 +57,16 @@ export default {
|
|||
getMarkdown() {
|
||||
return this.$refs.editor.invoke('getMarkdown');
|
||||
},
|
||||
onLoad(editorInstance) {
|
||||
addCustomEventListener(
|
||||
editorInstance,
|
||||
CUSTOM_EVENTS.openAddImageModal,
|
||||
this.onOpenAddImageModal,
|
||||
);
|
||||
},
|
||||
onOpenAddImageModal() {
|
||||
// TODO - add image modal (next MR)
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
@ -61,5 +79,6 @@ export default {
|
|||
:initial-edit-type="initialEditType"
|
||||
:height="height"
|
||||
@change="onContentChanged"
|
||||
@load="onLoad"
|
||||
/>
|
||||
</template>
|
||||
|
|
|
@ -60,7 +60,7 @@ module Mutations
|
|||
snippet = service_response.payload[:snippet]
|
||||
|
||||
{
|
||||
snippet: snippet.valid? ? snippet : nil,
|
||||
snippet: service_response.success? ? snippet : nil,
|
||||
errors: errors_on_object(snippet)
|
||||
}
|
||||
end
|
||||
|
|
|
@ -11,6 +11,7 @@
|
|||
- can_create_issue = show_new_issue_link?(@project)
|
||||
|
||||
= render_if_exists "projects/issues/alert_blocked", issue: @issue, current_user: current_user
|
||||
= render_if_exists "projects/issues/alert_moved_from_service_desk", issue: @issue
|
||||
|
||||
.detail-page-header
|
||||
.detail-page-header-body
|
||||
|
|
|
@ -5,7 +5,6 @@
|
|||
.row.registry-placeholder.prepend-bottom-10
|
||||
.col-12
|
||||
#js-container-registry{ data: { endpoint: project_container_registry_index_path(@project),
|
||||
settings_path: project_settings_ci_cd_path(@project),
|
||||
expiration_policy: @project.container_expiration_policy.to_json,
|
||||
"help_page_path" => help_page_path('user/packages/container_registry/index'),
|
||||
"two_factor_auth_help_link" => help_page_path('user/profile/account/two_factor_authentication'),
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
const BABEL_ENV = process.env.BABEL_ENV || process.env.NODE_ENV || null;
|
||||
|
||||
const presets = [
|
||||
let presets = [
|
||||
[
|
||||
'@babel/preset-env',
|
||||
{
|
||||
|
@ -49,6 +49,17 @@ if (isJest) {
|
|||
https://gitlab.com/gitlab-org/gitlab-foss/issues/58390
|
||||
*/
|
||||
plugins.push('babel-plugin-dynamic-import-node');
|
||||
|
||||
presets = [
|
||||
[
|
||||
'@babel/preset-env',
|
||||
{
|
||||
targets: {
|
||||
node: 'current',
|
||||
},
|
||||
},
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
module.exports = { presets, plugins, sourceType: 'unambiguous' };
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Improve Container Registry UI header
|
||||
merge_request: 32424
|
||||
author:
|
||||
type: changed
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Fix bug in snippet create mutation with non ActiveRecord errors
|
||||
merge_request: 33085
|
||||
author:
|
||||
type: fixed
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Remove unused WAF indexes from CI variables
|
||||
merge_request: 30021
|
||||
author:
|
||||
type: other
|
|
@ -3,7 +3,7 @@
|
|||
|
||||
require 'yaml'
|
||||
|
||||
SEE_DOC = "See [the documentation](https://docs.gitlab.com/ee/development/changelog.html)."
|
||||
SEE_DOC = "See the [changelog documentation](https://docs.gitlab.com/ee/development/changelog.html)."
|
||||
CREATE_CHANGELOG_MESSAGE = <<~MSG
|
||||
If you want to create a changelog entry for GitLab FOSS, run the following:
|
||||
|
||||
|
@ -20,14 +20,29 @@ bin/changelog --ee -m %<mr_iid>s "%<mr_title>s"
|
|||
If this merge request [doesn't need a CHANGELOG entry](https://docs.gitlab.com/ee/development/changelog.html#what-warrants-a-changelog-entry), feel free to ignore this message.
|
||||
MSG
|
||||
|
||||
SUGGEST_MR_COMMENT = <<~SUGGEST_COMMENT
|
||||
```suggestion
|
||||
merge_request: %<mr_iid>s
|
||||
```
|
||||
|
||||
#{SEE_DOC}
|
||||
SUGGEST_COMMENT
|
||||
|
||||
def check_changelog_yaml(path)
|
||||
yaml = YAML.safe_load(File.read(path))
|
||||
raw_file = File.read(path)
|
||||
yaml = YAML.safe_load(raw_file)
|
||||
|
||||
fail "`title` should be set, in #{gitlab.html_link(path)}! #{SEE_DOC}" if yaml["title"].nil?
|
||||
fail "`type` should be set, in #{gitlab.html_link(path)}! #{SEE_DOC}" if yaml["type"].nil?
|
||||
|
||||
if yaml["merge_request"].nil? && !helper.security_mr?
|
||||
mr_line = raw_file.lines.find_index("merge_request:\n")
|
||||
|
||||
if mr_line
|
||||
markdown(format(SUGGEST_MR_COMMENT, mr_iid: gitlab.mr_json["iid"]), file: path, line: mr_line.succ)
|
||||
else
|
||||
message "Consider setting `merge_request` to #{gitlab.mr_json["iid"]} in #{gitlab.html_link(path)}. #{SEE_DOC}"
|
||||
end
|
||||
elsif yaml["merge_request"] != gitlab.mr_json["iid"] && !helper.security_mr?
|
||||
fail "Merge request ID was not set to #{gitlab.mr_json["iid"]}! #{SEE_DOC}"
|
||||
end
|
||||
|
|
|
@ -0,0 +1,18 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class RemoveIndexOnPipelineIdFromCiPipelineVariables < ActiveRecord::Migration[6.0]
|
||||
include Gitlab::Database::MigrationHelpers
|
||||
|
||||
DOWNTIME = false
|
||||
INDEX_NAME = 'index_ci_pipeline_variables_on_pipeline_id'
|
||||
|
||||
disable_ddl_transaction!
|
||||
|
||||
def up
|
||||
remove_concurrent_index_by_name :ci_pipeline_variables, INDEX_NAME
|
||||
end
|
||||
|
||||
def down
|
||||
add_concurrent_index :ci_pipeline_variables, :pipeline_id, name: INDEX_NAME, where: "key = 'AUTO_DEVOPS_MODSECURITY_SEC_RULE_ENGINE'"
|
||||
end
|
||||
end
|
|
@ -0,0 +1,18 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class RemoveIndexOnPipelineIdFromCiVariables < ActiveRecord::Migration[6.0]
|
||||
include Gitlab::Database::MigrationHelpers
|
||||
|
||||
DOWNTIME = false
|
||||
INDEX_NAME = 'index_ci_variables_on_project_id'
|
||||
|
||||
disable_ddl_transaction!
|
||||
|
||||
def up
|
||||
remove_concurrent_index_by_name :ci_variables, INDEX_NAME
|
||||
end
|
||||
|
||||
def down
|
||||
add_concurrent_index :ci_variables, :project_id, name: INDEX_NAME, where: "key = 'AUTO_DEVOPS_MODSECURITY_SEC_RULE_ENGINE'"
|
||||
end
|
||||
end
|
|
@ -9338,8 +9338,6 @@ CREATE INDEX index_ci_pipeline_schedules_on_owner_id ON public.ci_pipeline_sched
|
|||
|
||||
CREATE INDEX index_ci_pipeline_schedules_on_project_id ON public.ci_pipeline_schedules USING btree (project_id);
|
||||
|
||||
CREATE INDEX index_ci_pipeline_variables_on_pipeline_id ON public.ci_pipeline_variables USING btree (pipeline_id) WHERE ((key)::text = 'AUTO_DEVOPS_MODSECURITY_SEC_RULE_ENGINE'::text);
|
||||
|
||||
CREATE UNIQUE INDEX index_ci_pipeline_variables_on_pipeline_id_and_key ON public.ci_pipeline_variables USING btree (pipeline_id, key);
|
||||
|
||||
CREATE INDEX index_ci_pipelines_config_on_pipeline_id ON public.ci_pipelines_config USING btree (pipeline_id);
|
||||
|
@ -9436,8 +9434,6 @@ CREATE INDEX index_ci_triggers_on_owner_id ON public.ci_triggers USING btree (ow
|
|||
|
||||
CREATE INDEX index_ci_triggers_on_project_id ON public.ci_triggers USING btree (project_id);
|
||||
|
||||
CREATE INDEX index_ci_variables_on_project_id ON public.ci_variables USING btree (project_id) WHERE ((key)::text = 'AUTO_DEVOPS_MODSECURITY_SEC_RULE_ENGINE'::text);
|
||||
|
||||
CREATE UNIQUE INDEX index_ci_variables_on_project_id_and_key_and_environment_scope ON public.ci_variables USING btree (project_id, key, environment_scope);
|
||||
|
||||
CREATE UNIQUE INDEX index_cluster_groups_on_cluster_id_and_group_id ON public.cluster_groups USING btree (cluster_id, group_id);
|
||||
|
@ -13885,6 +13881,8 @@ COPY "schema_migrations" (version) FROM STDIN;
|
|||
20200420172752
|
||||
20200420172927
|
||||
20200420201933
|
||||
20200421054930
|
||||
20200421054948
|
||||
20200421092907
|
||||
20200421111005
|
||||
20200421233150
|
||||
|
|
|
@ -104,7 +104,7 @@ generate a short-lived JWT that is pull-only-capable to access the
|
|||
|
||||
```ruby
|
||||
gitlab_rails['geo_registry_replication_enabled'] = true
|
||||
gitlab_rails['geo_registry_replication_primary_api_url'] = 'http://primary.example.com:4567/' # Primary registry address, it will be used by the secondary node to directly communicate to primary registry
|
||||
gitlab_rails['geo_registry_replication_primary_api_url'] = 'https://primary.example.com:5050/' # Primary registry address, it will be used by the secondary node to directly communicate to primary registry
|
||||
```
|
||||
|
||||
1. Reconfigure the **secondary** node for the change to take effect:
|
||||
|
|
|
@ -235,7 +235,7 @@ Kubernetes won't be shown.
|
|||
|
||||
Reports that go over the 20 MB limit won't be loaded. Affected reports:
|
||||
|
||||
- [Merge Request security reports](../user/project/merge_requests/index.md#security-reports-ultimate)
|
||||
- [Merge Request security reports](../user/project/merge_requests/testing_and_reports_in_merge_requests.md#security-reports-ultimate)
|
||||
- [CI/CD parameter `artifacts:expose_as`](../ci/yaml/README.md#artifactsexpose_as)
|
||||
- [JUnit test reports](../ci/junit_test_reports.md)
|
||||
|
||||
|
|
|
@ -28,7 +28,7 @@ best place to integrate your own product and its results into GitLab.
|
|||
implications for app security, corporate policy, or compliance. When complete,
|
||||
the job reports back on its status and creates a
|
||||
[job artifact](../../user/project/pipelines/job_artifacts.md) as a result.
|
||||
- The [Merge Request Security Widget](../../user/project/merge_requests/index.md#security-reports-ultimate)
|
||||
- The [Merge Request Security Widget](../../user/project/merge_requests/testing_and_reports_in_merge_requests.md#security-reports-ultimate)
|
||||
displays the results of the pipeline's security checks and the developer can
|
||||
review them. The developer can review both a summary and a detailed version
|
||||
of the results.
|
||||
|
@ -79,7 +79,7 @@ and complete an intgration with the Secure stage.
|
|||
- If you need a new kind of scan or report, [create an issue](https://gitlab.com/gitlab-org/gitlab/-/issues/new#)
|
||||
and add the label `devops::secure`.
|
||||
- Once the job is completed, the data can be seen:
|
||||
- In the [Merge Request Security Report](../../user/project/merge_requests/index.md#security-reports-ultimate) ([MR Security Report data flow](https://gitlab.com/snippets/1910005#merge-request-view)).
|
||||
- In the [Merge Request Security Report](../../user/project/merge_requests/testing_and_reports_in_merge_requests.md#security-reports-ultimate) ([MR Security Report data flow](https://gitlab.com/snippets/1910005#merge-request-view)).
|
||||
- While [browsing a Job Artifact](../../user/project/pipelines/job_artifacts.md).
|
||||
- In the [Security Dashboard](../../user/application_security/security_dashboard/index.md) ([Dashboard data flow](https://gitlab.com/snippets/1910005#project-and-group-dashboards)).
|
||||
1. Optional: Provide a way to interact with results as Vulnerabilities:
|
||||
|
|
|
@ -171,11 +171,10 @@ Please see the table below for some examples:
|
|||
|
||||
| Latest stable version | Your version | Recommended upgrade path | Note |
|
||||
| --------------------- | ------------ | ------------------------ | ---- |
|
||||
| 9.4.5 | 8.13.4 | `8.13.4` -> `8.17.7` -> `9.4.5` | `8.17.7` is the last version in version `8` |
|
||||
| 10.1.4 | 8.13.4 | `8.13.4 -> 8.17.7 -> 9.5.10 -> 10.1.4` | `8.17.7` is the last version in version `8`, `9.5.10` is the last version in version `9` |
|
||||
| 11.3.4 | 8.13.4 | `8.13.4` -> `8.17.7` -> `9.5.10` -> `10.8.7` -> `11.3.4` | `8.17.7` is the last version in version `8`, `9.5.10` is the last version in version `9`, `10.8.7` is the last version in version `10` |
|
||||
| 12.5.10 | 11.3.4 | `11.3.4` -> `11.11.8` -> `12.0.12` -> `12.5.10` | `11.11.8` is the last version in version `11`. `12.0.x` [is a required step](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/23211#note_272842444). |
|
||||
| 12.8.5 | 9.2.6 | `9.2.6` -> `9.5.10` -> `10.8.7` -> `11.11.8` -> `12.0.12` -> `12.8.5` | Four intermediate versions are required: the final 9.5, 10.8, 11.11 releases, plus 12.0. |
|
||||
| 13.2.0 | 11.5.0 | `11.5.0` -> `11.11.8` -> `12.0.12` -> `12.10.6` -> `13.0.0` -> `13.2.0` | Five intermediate versions are required: the final 11.11, 12.0, 12.10 releases, plus 13.0. |
|
||||
|
||||
NOTE: **Note:**
|
||||
Instructions for installing a specific version of GitLab or downloading the package locally for installation can be found at [GitLab Repositories](https://packages.gitlab.com/gitlab).
|
||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 41 KiB |
Binary file not shown.
After Width: | Height: | Size: 42 KiB |
Binary file not shown.
Before Width: | Height: | Size: 44 KiB |
Binary file not shown.
After Width: | Height: | Size: 46 KiB |
Binary file not shown.
Before Width: | Height: | Size: 48 KiB |
Binary file not shown.
After Width: | Height: | Size: 49 KiB |
|
@ -26,7 +26,7 @@ have its own space to store its Docker images.
|
|||
|
||||
You can read more about Docker Registry at <https://docs.docker.com/registry/introduction/>.
|
||||
|
||||
![Container Registry repositories](img/container_registry_repositories_v13_0.png)
|
||||
![Container Registry repositories](img/container_registry_repositories_v13_1.png)
|
||||
|
||||
## Enable the Container Registry for your project
|
||||
|
||||
|
@ -62,7 +62,7 @@ for both projects and groups.
|
|||
|
||||
Navigate to your project's **{package}** **Packages & Registries > Container Registry**.
|
||||
|
||||
![Container Registry project repositories](img/container_registry_repositories_with_quickstart_v13_0.png)
|
||||
![Container Registry project repositories](img/container_registry_repositories_with_quickstart_v13_1.png)
|
||||
|
||||
This view will:
|
||||
|
||||
|
@ -77,7 +77,7 @@ This view will:
|
|||
|
||||
Navigate to your groups's **{package}** **Packages & Registries > Container Registry**.
|
||||
|
||||
![Container Registry group repositories](img/container_registry_group_repositories_v13_0.png)
|
||||
![Container Registry group repositories](img/container_registry_group_repositories_v13_1.png)
|
||||
|
||||
This view will:
|
||||
|
||||
|
|
|
@ -86,35 +86,7 @@ See the features at your disposal to [review and manage merge requests](reviewin
|
|||
|
||||
## Testing and reports in merge requests
|
||||
|
||||
GitLab has the ability to test the changes included in a merge request, and can display
|
||||
or link to useful information directly in the merge request page:
|
||||
|
||||
| Feature | Description |
|
||||
|--------------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| [Accessibility Testing](accessibility_testing.md) | Automatically report A11y violations for changed pages in merge requests |
|
||||
| [Browser Performance Testing](browser_performance_testing.md) **(PREMIUM)** | Quickly determine the performance impact of pending code changes. |
|
||||
| [Code Quality](code_quality.md) **(STARTER)** | Analyze your source code quality using the [Code Climate](https://codeclimate.com/) analyzer and show the Code Climate report right in the merge request widget area. |
|
||||
| [Display arbitrary job artifacts](../../../ci/yaml/README.md#artifactsexpose_as) | Configure CI pipelines with the `artifacts:expose_as` parameter to directly link to selected [artifacts](../../../ci/pipelines/job_artifacts.md) in merge requests. |
|
||||
| [GitLab CI/CD](../../../ci/README.md) | Build, test, and deploy your code in a per-branch basis with built-in CI/CD. |
|
||||
| [JUnit test reports](../../../ci/junit_test_reports.md) | Configure your CI jobs to use JUnit test reports, and let GitLab display a report on the merge request so that it’s easier and faster to identify the failure without having to check the entire job log. |
|
||||
| [License Compliance](../../compliance/license_compliance/index.md) **(ULTIMATE)** | Manage the licenses of your dependencies. |
|
||||
| [Metrics Reports](../../../ci/metrics_reports.md) **(PREMIUM)** | Display the Metrics Report on the merge request so that it's fast and easy to identify changes to important metrics. |
|
||||
| [Multi-Project pipelines](../../../ci/multi_project_pipelines.md) **(PREMIUM)** | When you set up GitLab CI/CD across multiple projects, you can visualize the entire pipeline, including all cross-project interdependencies. |
|
||||
| [Pipelines for merge requests](../../../ci/merge_request_pipelines/index.md) | Customize a specific pipeline structure for merge requests in order to speed the cycle up by running only important jobs. |
|
||||
| [Pipeline Graphs](../../../ci/pipelines/index.md#visualize-pipelines) | View the status of pipelines within the merge request, including the deployment process. |
|
||||
| [Test Coverage visualization](test_coverage_visualization.md) | See test coverage results for merge requests, within the file diff. |
|
||||
|
||||
### Security Reports **(ULTIMATE)**
|
||||
|
||||
In addition to the reports listed above, GitLab can do many types of [Security reports](../../application_security/index.md),
|
||||
generated by scanning and reporting any vulnerabilities found in your project:
|
||||
|
||||
| Feature | Description |
|
||||
|-----------------------------------------------------------------------------------------|------------------------------------------------------------------|
|
||||
| [Container Scanning](../../application_security/container_scanning/index.md) | Analyze your Docker images for known vulnerabilities. |
|
||||
| [Dynamic Application Security Testing (DAST)](../../application_security/dast/index.md) | Analyze your running web applications for known vulnerabilities. |
|
||||
| [Dependency Scanning](../../application_security/dependency_scanning/index.md) | Analyze your dependencies for known vulnerabilities. |
|
||||
| [Static Application Security Testing (SAST)](../../application_security/sast/index.md) | Analyze your source code for known vulnerabilities. |
|
||||
Learn about the options for [testing and reports](testing_and_reports_in_merge_requests.md) on the changes in a merge request.
|
||||
|
||||
## Authorization for merge requests
|
||||
|
||||
|
|
|
@ -0,0 +1,36 @@
|
|||
---
|
||||
type: index
|
||||
description: "Test your code and display reports in merge requests"
|
||||
---
|
||||
|
||||
# Tests and reports in merge requests
|
||||
|
||||
GitLab has the ability to test the changes included in a feature branch and display reports
|
||||
or link to useful information directly from merge requests:
|
||||
|
||||
| Feature | Description |
|
||||
|--------------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| [Accessibility Testing](accessibility_testing.md) | Automatically report A11y violations for changed pages in merge requests. |
|
||||
| [Browser Performance Testing](browser_performance_testing.md) **(PREMIUM)** | Quickly determine the performance impact of pending code changes. |
|
||||
| [Code Quality](code_quality.md) **(STARTER)** | Analyze your source code quality using the [Code Climate](https://codeclimate.com/) analyzer and show the Code Climate report right in the merge request widget area. |
|
||||
| [Display arbitrary job artifacts](../../../ci/yaml/README.md#artifactsexpose_as) | Configure CI pipelines with the `artifacts:expose_as` parameter to directly link to selected [artifacts](../../../ci/pipelines/job_artifacts.md) in merge requests. |
|
||||
| [GitLab CI/CD](../../../ci/README.md) | Build, test, and deploy your code in a per-branch basis with built-in CI/CD. |
|
||||
| [JUnit test reports](../../../ci/junit_test_reports.md) | Configure your CI jobs to use JUnit test reports, and let GitLab display a report on the merge request so that it’s easier and faster to identify the failure without having to check the entire job log. |
|
||||
| [License Compliance](../../compliance/license_compliance/index.md) **(ULTIMATE)** | Manage the licenses of your dependencies. |
|
||||
| [Metrics Reports](../../../ci/metrics_reports.md) **(PREMIUM)** | Display the Metrics Report on the merge request so that it's fast and easy to identify changes to important metrics. |
|
||||
| [Multi-Project pipelines](../../../ci/multi_project_pipelines.md) **(PREMIUM)** | When you set up GitLab CI/CD across multiple projects, you can visualize the entire pipeline, including all cross-project interdependencies. |
|
||||
| [Pipelines for merge requests](../../../ci/merge_request_pipelines/index.md) | Customize a specific pipeline structure for merge requests in order to speed the cycle up by running only important jobs. |
|
||||
| [Pipeline Graphs](../../../ci/pipelines/index.md#visualize-pipelines) | View the status of pipelines within the merge request, including the deployment process. |
|
||||
| [Test Coverage visualization](test_coverage_visualization.md) | See test coverage results for merge requests, within the file diff. |
|
||||
|
||||
## Security Reports **(ULTIMATE)**
|
||||
|
||||
In addition to the reports listed above, GitLab can do many types of [Security reports](../../application_security/index.md),
|
||||
generated by scanning and reporting any vulnerabilities found in your project:
|
||||
|
||||
| Feature | Description |
|
||||
|-----------------------------------------------------------------------------------------|------------------------------------------------------------------|
|
||||
| [Container Scanning](../../application_security/container_scanning/index.md) | Analyze your Docker images for known vulnerabilities. |
|
||||
| [Dynamic Application Security Testing (DAST)](../../application_security/dast/index.md) | Analyze your running web applications for known vulnerabilities. |
|
||||
| [Dependency Scanning](../../application_security/dependency_scanning/index.md) | Analyze your dependencies for known vulnerabilities. |
|
||||
| [Static Application Security Testing (SAST)](../../application_security/sast/index.md) | Analyze your source code for known vulnerabilities. |
|
|
@ -5839,6 +5839,11 @@ msgstr ""
|
|||
msgid "ContainerRegistry| Please visit the %{linkStart}administration settings%{linkEnd} to enable this feature."
|
||||
msgstr ""
|
||||
|
||||
msgid "ContainerRegistry|%{count} Image repository"
|
||||
msgid_plural "ContainerRegistry|%{count} Image repositories"
|
||||
msgstr[0] ""
|
||||
msgstr[1] ""
|
||||
|
||||
msgid "ContainerRegistry|%{imageName} tags"
|
||||
msgstr ""
|
||||
|
||||
|
@ -5851,6 +5856,9 @@ msgstr ""
|
|||
msgid "ContainerRegistry|Build an image"
|
||||
msgstr ""
|
||||
|
||||
msgid "ContainerRegistry|CLI Commands"
|
||||
msgstr ""
|
||||
|
||||
msgid "ContainerRegistry|Compressed Size"
|
||||
msgstr ""
|
||||
|
||||
|
@ -5875,15 +5883,21 @@ msgstr ""
|
|||
msgid "ContainerRegistry|Docker tag expiration policy is %{toggleStatus}"
|
||||
msgstr ""
|
||||
|
||||
msgid "ContainerRegistry|Edit Settings"
|
||||
msgstr ""
|
||||
|
||||
msgid "ContainerRegistry|Expiration interval:"
|
||||
msgstr ""
|
||||
|
||||
msgid "ContainerRegistry|Expiration policies help manage the storage space used by the Container Registry, but the expiration policies for this registry are disabled. Contact your administrator to enable. %{docLinkStart}More information%{docLinkEnd}"
|
||||
msgstr ""
|
||||
|
||||
msgid "ContainerRegistry|Expiration policy is disabled"
|
||||
msgstr ""
|
||||
|
||||
msgid "ContainerRegistry|Expiration policy successfully saved."
|
||||
msgstr ""
|
||||
|
||||
msgid "ContainerRegistry|Expiration policy will run in %{time}"
|
||||
msgstr ""
|
||||
|
||||
msgid "ContainerRegistry|Expiration policy:"
|
||||
msgstr ""
|
||||
|
||||
|
@ -5923,9 +5937,6 @@ msgstr ""
|
|||
msgid "ContainerRegistry|Push an image"
|
||||
msgstr ""
|
||||
|
||||
msgid "ContainerRegistry|Quick Start"
|
||||
msgstr ""
|
||||
|
||||
msgid "ContainerRegistry|Regular expressions such as %{codeStart}.*-master%{codeEnd} or %{codeStart}release-.*%{codeEnd} are supported"
|
||||
msgstr ""
|
||||
|
||||
|
@ -5946,9 +5957,6 @@ msgid_plural "ContainerRegistry|Remove tags"
|
|||
msgstr[0] ""
|
||||
msgstr[1] ""
|
||||
|
||||
msgid "ContainerRegistry|Retention policy has been Enabled"
|
||||
msgstr ""
|
||||
|
||||
msgid "ContainerRegistry|Something went wrong while fetching the expiration policy."
|
||||
msgstr ""
|
||||
|
||||
|
@ -6000,12 +6008,6 @@ msgstr ""
|
|||
msgid "ContainerRegistry|The last tag related to this image was recently removed. This empty image and any associated data will be automatically removed as part of the regular Garbage Collection process. If you have any questions, contact your administrator."
|
||||
msgstr ""
|
||||
|
||||
msgid "ContainerRegistry|The retention and expiration policy for this Container Registry has been enabled and will run in %{days}. For more information visit the %{linkStart}documentation%{linkEnd}"
|
||||
msgstr ""
|
||||
|
||||
msgid "ContainerRegistry|The retention and expiration policy for this Container Registry has been enabled. For more information visit the %{linkStart}documentation%{linkEnd}"
|
||||
msgstr ""
|
||||
|
||||
msgid "ContainerRegistry|The value of this input should be less than 255 characters"
|
||||
msgstr ""
|
||||
|
||||
|
@ -6027,7 +6029,7 @@ msgstr ""
|
|||
msgid "ContainerRegistry|To widen your search, change or remove the filters above."
|
||||
msgstr ""
|
||||
|
||||
msgid "ContainerRegistry|We are having trouble connecting to Docker, which could be due to an issue with your project name or path. %{docLinkStart}More Information%{docLinkEnd}"
|
||||
msgid "ContainerRegistry|We are having trouble connecting to the Registry, which could be due to an issue with your project name or path. %{docLinkStart}More information%{docLinkEnd}"
|
||||
msgstr ""
|
||||
|
||||
msgid "ContainerRegistry|With the Container Registry, every project can have its own space to store its Docker images. %{docLinkStart}More Information%{docLinkEnd}"
|
||||
|
@ -6036,7 +6038,7 @@ msgstr ""
|
|||
msgid "ContainerRegistry|With the Container Registry, every project can have its own space to store its Docker images. Push at least one Docker image in one of this group's projects in order to show up here. %{docLinkStart}More Information%{docLinkEnd}"
|
||||
msgstr ""
|
||||
|
||||
msgid "ContainerRegistry|With the Docker Container Registry integrated into GitLab, every project can have its own space to store its Docker images. %{docLinkStart}More Information%{docLinkEnd}"
|
||||
msgid "ContainerRegistry|With the GitLab Container Registry, every project can have its own space to store images. %{docLinkStart}More information%{docLinkEnd}"
|
||||
msgstr ""
|
||||
|
||||
msgid "ContainerRegistry|You are about to remove %{item} tags. Are you sure?"
|
||||
|
@ -22388,6 +22390,9 @@ msgstr ""
|
|||
msgid "This project does not belong to a group and can therefore not make use of group Runners."
|
||||
msgstr ""
|
||||
|
||||
msgid "This project does not have %{service_desk_link_start}Service Desk%{service_desk_link_end} enabled, so the user who created the issue will no longer receive email notifications about new activity."
|
||||
msgstr ""
|
||||
|
||||
msgid "This project does not have a wiki homepage yet"
|
||||
msgstr ""
|
||||
|
||||
|
|
1
qa/qa.rb
1
qa/qa.rb
|
@ -89,6 +89,7 @@ module QA
|
|||
autoload :ProjectSnippet, 'qa/resource/project_snippet'
|
||||
autoload :UserGPG, 'qa/resource/user_gpg'
|
||||
autoload :Visibility, 'qa/resource/visibility'
|
||||
autoload :ProjectSnippet, 'qa/resource/project_snippet'
|
||||
|
||||
module KubernetesCluster
|
||||
autoload :Base, 'qa/resource/kubernetes_cluster/base'
|
||||
|
|
|
@ -35,6 +35,10 @@ module QA
|
|||
element :file_content
|
||||
end
|
||||
|
||||
view 'app/assets/javascripts/blob/components/blob_content.vue' do
|
||||
element :file_content
|
||||
end
|
||||
|
||||
view 'app/assets/javascripts/snippets/components/snippet_header.vue' do
|
||||
element :snippet_action_button
|
||||
element :delete_snippet_button
|
||||
|
@ -57,6 +61,10 @@ module QA
|
|||
has_element? :snippet_description_field, text: snippet_description
|
||||
end
|
||||
|
||||
def has_no_snippet_description?
|
||||
has_no_element?(:snippet_description_field)
|
||||
end
|
||||
|
||||
def has_visibility_type?(visibility_type)
|
||||
within_element(:snippet_box) do
|
||||
has_text?(visibility_type)
|
||||
|
|
|
@ -2,8 +2,8 @@
|
|||
|
||||
module QA
|
||||
context 'Create', :smoke do
|
||||
describe 'Snippet creation' do
|
||||
it 'User creates a snippet' do
|
||||
describe 'Personal snippet creation' do
|
||||
it 'User creates a personal snippet' do
|
||||
Flow::Login.sign_in
|
||||
|
||||
Page::Main::Menu.perform(&:go_to_snippets)
|
|
@ -0,0 +1,30 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module QA
|
||||
context 'Create' do # to be converted to a smoke test once proved to be stable
|
||||
describe 'Project snippet creation' do
|
||||
it 'User creates a project snippet' do
|
||||
Flow::Login.sign_in
|
||||
|
||||
Resource::ProjectSnippet.fabricate_via_browser_ui! do |snippet|
|
||||
snippet.title = 'Project snippet'
|
||||
snippet.description = ' '
|
||||
snippet.visibility = 'Internal'
|
||||
snippet.file_name = 'markdown_file.md'
|
||||
snippet.file_content = "### Snippet heading\n\n[Gitlab link](https://gitlab.com/)"
|
||||
end
|
||||
|
||||
Page::Dashboard::Snippet::Show.perform do |snippet|
|
||||
expect(snippet).to have_snippet_title('Project snippet')
|
||||
expect(snippet).to have_no_snippet_description
|
||||
expect(snippet).to have_visibility_type(/internal/i)
|
||||
expect(snippet).to have_file_name('markdown_file.md')
|
||||
expect(snippet).to have_file_content('Snippet heading')
|
||||
expect(snippet).to have_file_content('Gitlab link')
|
||||
expect(snippet).not_to have_file_content('###')
|
||||
expect(snippet).not_to have_file_content('https://gitlab.com/')
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -95,6 +95,45 @@ describe 'issue move to another project' do
|
|||
expect(page).to have_no_selector('#move_to_project_id')
|
||||
end
|
||||
end
|
||||
|
||||
context 'service desk issue moved to a project with service desk disabled', :js do
|
||||
let(:project_title) { 'service desk disabled project' }
|
||||
let(:warning_selector) { '.js-alert-moved-from-service-desk-warning' }
|
||||
let(:namespace) { create(:namespace) }
|
||||
let(:regular_project) { create(:project, title: project_title, service_desk_enabled: false) }
|
||||
let(:service_desk_project) { build(:project, :private, namespace: namespace, service_desk_enabled: true) }
|
||||
let(:service_desk_issue) { create(:issue, project: service_desk_project, author: ::User.support_bot) }
|
||||
|
||||
before do
|
||||
allow(::Gitlab).to receive(:com?).and_return(true)
|
||||
allow(::Gitlab::IncomingEmail).to receive(:enabled?).and_return(true)
|
||||
allow(::Gitlab::IncomingEmail).to receive(:supports_wildcard?).and_return(true)
|
||||
|
||||
regular_project.add_reporter(user)
|
||||
service_desk_project.add_reporter(user)
|
||||
|
||||
visit issue_path(service_desk_issue)
|
||||
|
||||
find('.js-move-issue').click
|
||||
wait_for_requests
|
||||
find('.js-move-issue-dropdown-item', text: project_title).click
|
||||
find('.js-move-issue-confirmation-button').click
|
||||
end
|
||||
|
||||
it 'shows an alert after being moved' do
|
||||
expect(page).to have_content('This project does not have Service Desk enabled')
|
||||
end
|
||||
|
||||
it 'does not show an alert after being dismissed' do
|
||||
find("#{warning_selector} .js-close").click
|
||||
|
||||
expect(page).to have_no_selector(warning_selector)
|
||||
|
||||
page.refresh
|
||||
|
||||
expect(page).to have_no_selector(warning_selector)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def issue_path(issue)
|
||||
|
|
|
@ -30,10 +30,10 @@ describe 'Container Registry', :js do
|
|||
expect(page).to have_content _('There are no container images stored for this project')
|
||||
end
|
||||
|
||||
it 'list page has quickstart' do
|
||||
it 'list page has cli commands' do
|
||||
visit_container_registry
|
||||
|
||||
expect(page).to have_content _('Quick Start')
|
||||
expect(page).to have_content _('CLI Commands')
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -4,9 +4,8 @@ import ClipboardButton from '~/vue_shared/components/clipboard_button.vue';
|
|||
import { Blob as MockBlob } from './mock_data';
|
||||
import { numberToHumanSize } from '~/lib/utils/number_utils';
|
||||
|
||||
const mockHumanReadableSize = 'a lot';
|
||||
jest.mock('~/lib/utils/number_utils', () => ({
|
||||
numberToHumanSize: jest.fn(() => mockHumanReadableSize),
|
||||
numberToHumanSize: jest.fn(() => 'a lot'),
|
||||
}));
|
||||
|
||||
describe('Blob Header Filepath', () => {
|
||||
|
@ -57,7 +56,7 @@ describe('Blob Header Filepath', () => {
|
|||
it('renders filesize in a human-friendly format', () => {
|
||||
createComponent();
|
||||
expect(numberToHumanSize).toHaveBeenCalled();
|
||||
expect(wrapper.vm.blobSize).toBe(mockHumanReadableSize);
|
||||
expect(wrapper.vm.blobSize).toBe('a lot');
|
||||
});
|
||||
|
||||
it('renders a slot and prepends its contents to the existing one', () => {
|
||||
|
|
|
@ -4,6 +4,7 @@ import './element_scroll_to';
|
|||
import './form_element';
|
||||
import './get_client_rects';
|
||||
import './inner_text';
|
||||
import './mutation_observer';
|
||||
import './window_scroll_to';
|
||||
import './scroll_by';
|
||||
import './size_properties';
|
||||
|
|
7
spec/frontend/helpers/dom_shims/mutation_observer.js
Normal file
7
spec/frontend/helpers/dom_shims/mutation_observer.js
Normal file
|
@ -0,0 +1,7 @@
|
|||
/* eslint-disable class-methods-use-this */
|
||||
class MutationObserverStub {
|
||||
disconnect() {}
|
||||
observe() {}
|
||||
}
|
||||
|
||||
global.MutationObserver = MutationObserverStub;
|
|
@ -69,26 +69,21 @@ describe('IDE commit form', () => {
|
|||
});
|
||||
});
|
||||
|
||||
it('collapses if lastCommitMsg is set to empty and current view is not commit view', () => {
|
||||
it('collapses if lastCommitMsg is set to empty and current view is not commit view', async () => {
|
||||
store.state.lastCommitMsg = 'abc';
|
||||
store.state.currentActivityView = leftSidebarViews.edit.name;
|
||||
await vm.$nextTick();
|
||||
|
||||
return vm
|
||||
.$nextTick()
|
||||
.then(() => {
|
||||
// if commit message is set, form is uncollapsed
|
||||
expect(vm.isCompact).toBe(false);
|
||||
|
||||
store.state.lastCommitMsg = '';
|
||||
await vm.$nextTick();
|
||||
|
||||
return vm.$nextTick();
|
||||
})
|
||||
.then(() => {
|
||||
// collapsed when set to empty
|
||||
expect(vm.isCompact).toBe(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('full', () => {
|
||||
beforeEach(() => {
|
||||
|
|
|
@ -4,6 +4,7 @@ import DashboardPanel from '~/monitoring/components/dashboard_panel.vue';
|
|||
import { TEST_HOST } from 'helpers/test_constants';
|
||||
import MetricEmbed from '~/monitoring/components/embeds/metric_embed.vue';
|
||||
import { groups, initialState, metricsData, metricsWithData } from './mock_data';
|
||||
import { setHTMLFixture } from 'helpers/fixtures';
|
||||
|
||||
const localVue = createLocalVue();
|
||||
localVue.use(Vuex);
|
||||
|
@ -25,6 +26,8 @@ describe('MetricEmbed', () => {
|
|||
}
|
||||
|
||||
beforeEach(() => {
|
||||
setHTMLFixture('<div class="layout-page"></div>');
|
||||
|
||||
actions = {
|
||||
setInitialState: jest.fn(),
|
||||
setShowErrorBanner: jest.fn(),
|
||||
|
|
|
@ -7,6 +7,7 @@ import linkedPipelinesColumn from '~/pipelines/components/graph/linked_pipelines
|
|||
import graphJSON from './mock_data';
|
||||
import linkedPipelineJSON from './linked_pipelines_mock_data';
|
||||
import PipelinesMediator from '~/pipelines/pipeline_details_mediator';
|
||||
import { setHTMLFixture } from 'helpers/fixtures';
|
||||
|
||||
describe('graph component', () => {
|
||||
const store = new PipelineStore();
|
||||
|
@ -15,6 +16,10 @@ describe('graph component', () => {
|
|||
|
||||
let wrapper;
|
||||
|
||||
beforeEach(() => {
|
||||
setHTMLFixture('<div class="layout-page"></div>');
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
wrapper.destroy();
|
||||
wrapper = null;
|
||||
|
|
|
@ -19,7 +19,7 @@ exports[`Registry Project Empty state to match the default snapshot 1`] = `
|
|||
</p>
|
||||
|
||||
<h5>
|
||||
Quick Start
|
||||
CLI Commands
|
||||
</h5>
|
||||
|
||||
<p
|
||||
|
|
|
@ -3,7 +3,7 @@ import { mount, createLocalVue } from '@vue/test-utils';
|
|||
import { GlDropdown, GlFormGroup, GlFormInputGroup } from '@gitlab/ui';
|
||||
import Tracking from '~/tracking';
|
||||
import * as getters from '~/registry/explorer/stores/getters';
|
||||
import QuickstartDropdown from '~/registry/explorer/components/quickstart_dropdown.vue';
|
||||
import QuickstartDropdown from '~/registry/explorer/components/cli_commands.vue';
|
||||
import ClipboardButton from '~/vue_shared/components/clipboard_button.vue';
|
||||
|
||||
import {
|
||||
|
@ -19,7 +19,7 @@ import {
|
|||
const localVue = createLocalVue();
|
||||
localVue.use(Vuex);
|
||||
|
||||
describe('quickstart_dropdown', () => {
|
||||
describe('cli_commands', () => {
|
||||
let wrapper;
|
||||
let store;
|
||||
|
|
@ -1,132 +0,0 @@
|
|||
import Vuex from 'vuex';
|
||||
import { shallowMount, createLocalVue } from '@vue/test-utils';
|
||||
import { GlSprintf, GlAlert, GlLink } from '@gitlab/ui';
|
||||
import * as dateTimeUtils from '~/lib/utils/datetime_utility';
|
||||
import component from '~/registry/explorer/components/project_policy_alert.vue';
|
||||
import {
|
||||
EXPIRATION_POLICY_ALERT_TITLE,
|
||||
EXPIRATION_POLICY_ALERT_PRIMARY_BUTTON,
|
||||
} from '~/registry/explorer/constants';
|
||||
|
||||
const localVue = createLocalVue();
|
||||
localVue.use(Vuex);
|
||||
|
||||
describe('Project Policy Alert', () => {
|
||||
let wrapper;
|
||||
let store;
|
||||
|
||||
const defaultState = {
|
||||
config: {
|
||||
expirationPolicy: {
|
||||
enabled: true,
|
||||
},
|
||||
settingsPath: 'foo',
|
||||
expirationPolicyHelpPagePath: 'bar',
|
||||
},
|
||||
images: [],
|
||||
isLoading: false,
|
||||
};
|
||||
|
||||
const findAlert = () => wrapper.find(GlAlert);
|
||||
const findLink = () => wrapper.find(GlLink);
|
||||
|
||||
const createComponent = (state = defaultState) => {
|
||||
store = new Vuex.Store({
|
||||
state,
|
||||
});
|
||||
wrapper = shallowMount(component, {
|
||||
localVue,
|
||||
store,
|
||||
stubs: {
|
||||
GlSprintf,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const documentationExpectation = () => {
|
||||
it('contain a documentation link', () => {
|
||||
createComponent();
|
||||
expect(findLink().attributes('href')).toBe(defaultState.config.expirationPolicyHelpPagePath);
|
||||
expect(findLink().text()).toBe('documentation');
|
||||
});
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
jest.spyOn(dateTimeUtils, 'approximateDuration').mockReturnValue('1 day');
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
wrapper.destroy();
|
||||
});
|
||||
|
||||
describe('is hidden', () => {
|
||||
it('when expiration policy does not exist', () => {
|
||||
createComponent({ config: {} });
|
||||
expect(findAlert().exists()).toBe(false);
|
||||
});
|
||||
|
||||
it('when expiration policy exist but is disabled', () => {
|
||||
createComponent({
|
||||
...defaultState,
|
||||
config: {
|
||||
expirationPolicy: {
|
||||
enabled: false,
|
||||
},
|
||||
},
|
||||
});
|
||||
expect(findAlert().exists()).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('is visible', () => {
|
||||
it('when expiration policy exists and is enabled', () => {
|
||||
createComponent();
|
||||
expect(findAlert().exists()).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('full info alert', () => {
|
||||
beforeEach(() => {
|
||||
createComponent({ ...defaultState, images: [1] });
|
||||
});
|
||||
|
||||
it('has a primary button', () => {
|
||||
const alert = findAlert();
|
||||
expect(alert.props('primaryButtonText')).toBe(EXPIRATION_POLICY_ALERT_PRIMARY_BUTTON);
|
||||
expect(alert.props('primaryButtonLink')).toBe(defaultState.config.settingsPath);
|
||||
});
|
||||
|
||||
it('has a title', () => {
|
||||
const alert = findAlert();
|
||||
expect(alert.props('title')).toBe(EXPIRATION_POLICY_ALERT_TITLE);
|
||||
});
|
||||
|
||||
it('has the full message', () => {
|
||||
expect(findAlert().html()).toContain('<strong>1 day</strong>');
|
||||
});
|
||||
|
||||
documentationExpectation();
|
||||
});
|
||||
|
||||
describe('compact info alert', () => {
|
||||
beforeEach(() => {
|
||||
createComponent({ ...defaultState, images: [] });
|
||||
});
|
||||
|
||||
it('does not have a button', () => {
|
||||
const alert = findAlert();
|
||||
expect(alert.props('primaryButtonText')).toBe(null);
|
||||
});
|
||||
|
||||
it('does not have a title', () => {
|
||||
const alert = findAlert();
|
||||
expect(alert.props('title')).toBe(null);
|
||||
});
|
||||
|
||||
it('has the short message', () => {
|
||||
expect(findAlert().html()).not.toContain('<strong>1 day</strong>');
|
||||
});
|
||||
|
||||
documentationExpectation();
|
||||
});
|
||||
});
|
|
@ -0,0 +1,221 @@
|
|||
import { shallowMount } from '@vue/test-utils';
|
||||
import { GlSprintf, GlLink } from '@gitlab/ui';
|
||||
import Component from '~/registry/explorer/components/registry_header.vue';
|
||||
import {
|
||||
CONTAINER_REGISTRY_TITLE,
|
||||
LIST_INTRO_TEXT,
|
||||
EXPIRATION_POLICY_DISABLED_MESSAGE,
|
||||
EXPIRATION_POLICY_DISABLED_TEXT,
|
||||
EXPIRATION_POLICY_WILL_RUN_IN,
|
||||
} from '~/registry/explorer/constants';
|
||||
|
||||
jest.mock('~/lib/utils/datetime_utility', () => ({
|
||||
approximateDuration: jest.fn(),
|
||||
calculateRemainingMilliseconds: jest.fn(),
|
||||
}));
|
||||
|
||||
describe('registry_header', () => {
|
||||
let wrapper;
|
||||
|
||||
const findHeader = () => wrapper.find('[data-testid="header"]');
|
||||
const findTitle = () => wrapper.find('[data-testid="title"]');
|
||||
const findCommandsSlot = () => wrapper.find('[data-testid="commands-slot"]');
|
||||
const findInfoArea = () => wrapper.find('[data-testid="info-area"]');
|
||||
const findIntroText = () => wrapper.find('[data-testid="default-intro"]');
|
||||
const findSubHeader = () => wrapper.find('[data-testid="subheader"]');
|
||||
const findImagesCountSubHeader = () => wrapper.find('[data-testid="images-count"]');
|
||||
const findExpirationPolicySubHeader = () => wrapper.find('[data-testid="expiration-policy"]');
|
||||
const findDisabledExpirationPolicyMessage = () =>
|
||||
wrapper.find('[data-testid="expiration-disabled-message"]');
|
||||
|
||||
const mountComponent = (propsData, slots) => {
|
||||
wrapper = shallowMount(Component, {
|
||||
stubs: {
|
||||
GlSprintf,
|
||||
},
|
||||
propsData,
|
||||
slots,
|
||||
});
|
||||
};
|
||||
|
||||
afterEach(() => {
|
||||
wrapper.destroy();
|
||||
wrapper = null;
|
||||
});
|
||||
|
||||
describe('header', () => {
|
||||
it('exists', () => {
|
||||
mountComponent();
|
||||
expect(findHeader().exists()).toBe(true);
|
||||
});
|
||||
|
||||
it('contains the title of the page', () => {
|
||||
mountComponent();
|
||||
const title = findTitle();
|
||||
expect(title.exists()).toBe(true);
|
||||
expect(title.text()).toBe(CONTAINER_REGISTRY_TITLE);
|
||||
});
|
||||
|
||||
it('has a commands slot', () => {
|
||||
mountComponent(null, { commands: 'baz' });
|
||||
expect(findCommandsSlot().text()).toBe('baz');
|
||||
});
|
||||
});
|
||||
|
||||
describe('subheader', () => {
|
||||
describe('when there are no images', () => {
|
||||
it('is hidden ', () => {
|
||||
mountComponent();
|
||||
expect(findSubHeader().exists()).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when there are images', () => {
|
||||
it('is visible', () => {
|
||||
mountComponent({ imagesCount: 1 });
|
||||
expect(findSubHeader().exists()).toBe(true);
|
||||
});
|
||||
|
||||
describe('sub header parts', () => {
|
||||
describe('images count', () => {
|
||||
it('exists', () => {
|
||||
mountComponent({ imagesCount: 1 });
|
||||
expect(findImagesCountSubHeader().exists()).toBe(true);
|
||||
});
|
||||
|
||||
it('when there is one image', () => {
|
||||
mountComponent({ imagesCount: 1 });
|
||||
expect(findImagesCountSubHeader().text()).toMatchInterpolatedText('1 Image repository');
|
||||
});
|
||||
|
||||
it('when there is more than one image', () => {
|
||||
mountComponent({ imagesCount: 3 });
|
||||
expect(findImagesCountSubHeader().text()).toMatchInterpolatedText(
|
||||
'3 Image repositories',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('expiration policy', () => {
|
||||
it('when is disabled', () => {
|
||||
mountComponent({
|
||||
expirationPolicy: { enabled: false },
|
||||
expirationPolicyHelpPagePath: 'foo',
|
||||
imagesCount: 1,
|
||||
});
|
||||
const text = findExpirationPolicySubHeader();
|
||||
expect(text.exists()).toBe(true);
|
||||
expect(text.text()).toMatchInterpolatedText(EXPIRATION_POLICY_DISABLED_TEXT);
|
||||
});
|
||||
|
||||
it('when is enabled', () => {
|
||||
mountComponent({
|
||||
expirationPolicy: { enabled: true },
|
||||
expirationPolicyHelpPagePath: 'foo',
|
||||
imagesCount: 1,
|
||||
});
|
||||
const text = findExpirationPolicySubHeader();
|
||||
expect(text.exists()).toBe(true);
|
||||
expect(text.text()).toMatchInterpolatedText(EXPIRATION_POLICY_WILL_RUN_IN);
|
||||
});
|
||||
it('when the expiration policy is completely disabled', () => {
|
||||
mountComponent({
|
||||
expirationPolicy: { enabled: true },
|
||||
expirationPolicyHelpPagePath: 'foo',
|
||||
imagesCount: 1,
|
||||
hideExpirationPolicyData: true,
|
||||
});
|
||||
const text = findExpirationPolicySubHeader();
|
||||
expect(text.exists()).toBe(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('info area', () => {
|
||||
it('exists', () => {
|
||||
mountComponent();
|
||||
expect(findInfoArea().exists()).toBe(true);
|
||||
});
|
||||
|
||||
describe('default message', () => {
|
||||
beforeEach(() => {
|
||||
mountComponent({ helpPagePath: 'bar' });
|
||||
});
|
||||
|
||||
it('exists', () => {
|
||||
expect(findIntroText().exists()).toBe(true);
|
||||
});
|
||||
|
||||
it('has the correct copy', () => {
|
||||
expect(findIntroText().text()).toMatchInterpolatedText(LIST_INTRO_TEXT);
|
||||
});
|
||||
|
||||
it('has the correct link', () => {
|
||||
expect(
|
||||
findIntroText()
|
||||
.find(GlLink)
|
||||
.attributes('href'),
|
||||
).toBe('bar');
|
||||
});
|
||||
});
|
||||
|
||||
describe('expiration policy info message', () => {
|
||||
describe('when there are no images', () => {
|
||||
it('is hidden', () => {
|
||||
mountComponent();
|
||||
expect(findDisabledExpirationPolicyMessage().exists()).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when there are images', () => {
|
||||
describe('when expiration policy is disabled', () => {
|
||||
beforeEach(() => {
|
||||
mountComponent({
|
||||
expirationPolicy: { enabled: false },
|
||||
expirationPolicyHelpPagePath: 'foo',
|
||||
imagesCount: 1,
|
||||
});
|
||||
});
|
||||
it('message exist', () => {
|
||||
expect(findDisabledExpirationPolicyMessage().exists()).toBe(true);
|
||||
});
|
||||
it('has the correct copy', () => {
|
||||
expect(findDisabledExpirationPolicyMessage().text()).toMatchInterpolatedText(
|
||||
EXPIRATION_POLICY_DISABLED_MESSAGE,
|
||||
);
|
||||
});
|
||||
|
||||
it('has the correct link', () => {
|
||||
expect(
|
||||
findDisabledExpirationPolicyMessage()
|
||||
.find(GlLink)
|
||||
.attributes('href'),
|
||||
).toBe('foo');
|
||||
});
|
||||
});
|
||||
|
||||
describe('when expiration policy is enabled', () => {
|
||||
it('message does not exist', () => {
|
||||
mountComponent({
|
||||
expirationPolicy: { enabled: true },
|
||||
imagesCount: 1,
|
||||
});
|
||||
expect(findDisabledExpirationPolicyMessage().exists()).toBe(false);
|
||||
});
|
||||
});
|
||||
describe('when the expiration policy is completely disabled', () => {
|
||||
it('message does not exist', () => {
|
||||
mountComponent({
|
||||
expirationPolicy: { enabled: true },
|
||||
imagesCount: 1,
|
||||
hideExpirationPolicyData: true,
|
||||
});
|
||||
expect(findDisabledExpirationPolicyMessage().exists()).toBe(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
|
@ -3,10 +3,10 @@ import { GlSkeletonLoader, GlSprintf, GlAlert, GlSearchBoxByClick } from '@gitla
|
|||
import Tracking from '~/tracking';
|
||||
import waitForPromises from 'helpers/wait_for_promises';
|
||||
import component from '~/registry/explorer/pages/list.vue';
|
||||
import QuickstartDropdown from '~/registry/explorer/components/quickstart_dropdown.vue';
|
||||
import CliCommands from '~/registry/explorer/components/cli_commands.vue';
|
||||
import GroupEmptyState from '~/registry/explorer/components/group_empty_state.vue';
|
||||
import ProjectEmptyState from '~/registry/explorer/components/project_empty_state.vue';
|
||||
import ProjectPolicyAlert from '~/registry/explorer/components/project_policy_alert.vue';
|
||||
import RegistryHeader from '~/registry/explorer/components/registry_header.vue';
|
||||
import ImageList from '~/registry/explorer/components/image_list.vue';
|
||||
import { createStore } from '~/registry/explorer/stores/';
|
||||
import {
|
||||
|
@ -32,14 +32,14 @@ describe('List Page', () => {
|
|||
|
||||
const findDeleteModal = () => wrapper.find(GlModal);
|
||||
const findSkeletonLoader = () => wrapper.find(GlSkeletonLoader);
|
||||
const findImagesList = () => wrapper.find({ ref: 'imagesList' });
|
||||
|
||||
const findEmptyState = () => wrapper.find(GlEmptyState);
|
||||
|
||||
const findQuickStartDropdown = () => wrapper.find(QuickstartDropdown);
|
||||
const findCliCommands = () => wrapper.find(CliCommands);
|
||||
const findProjectEmptyState = () => wrapper.find(ProjectEmptyState);
|
||||
const findGroupEmptyState = () => wrapper.find(GroupEmptyState);
|
||||
const findProjectPolicyAlert = () => wrapper.find(ProjectPolicyAlert);
|
||||
const findRegistryHeader = () => wrapper.find(RegistryHeader);
|
||||
|
||||
const findDeleteAlert = () => wrapper.find(GlAlert);
|
||||
const findImageList = () => wrapper.find(ImageList);
|
||||
const findListHeader = () => wrapper.find('[data-testid="listHeader"]');
|
||||
|
@ -53,6 +53,7 @@ describe('List Page', () => {
|
|||
GlModal,
|
||||
GlEmptyState,
|
||||
GlSprintf,
|
||||
RegistryHeader,
|
||||
},
|
||||
mocks: {
|
||||
$toast,
|
||||
|
@ -76,21 +77,6 @@ describe('List Page', () => {
|
|||
wrapper.destroy();
|
||||
});
|
||||
|
||||
describe('Expiration policy notification', () => {
|
||||
beforeEach(() => {
|
||||
mountComponent();
|
||||
});
|
||||
it('shows up on project page', () => {
|
||||
expect(findProjectPolicyAlert().exists()).toBe(true);
|
||||
});
|
||||
it('does show up on group page', () => {
|
||||
store.commit(SET_INITIAL_STATE, { isGroupPage: true });
|
||||
return wrapper.vm.$nextTick().then(() => {
|
||||
expect(findProjectPolicyAlert().exists()).toBe(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('API calls', () => {
|
||||
it.each`
|
||||
imageList | name | called
|
||||
|
@ -109,6 +95,11 @@ describe('List Page', () => {
|
|||
);
|
||||
});
|
||||
|
||||
it('contains registry header', () => {
|
||||
mountComponent();
|
||||
expect(findRegistryHeader().exists()).toBe(true);
|
||||
});
|
||||
|
||||
describe('connection error', () => {
|
||||
const config = {
|
||||
characterError: true,
|
||||
|
@ -139,7 +130,7 @@ describe('List Page', () => {
|
|||
|
||||
it('should not show the loading or default state', () => {
|
||||
expect(findSkeletonLoader().exists()).toBe(false);
|
||||
expect(findImagesList().exists()).toBe(false);
|
||||
expect(findImageList().exists()).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -156,11 +147,11 @@ describe('List Page', () => {
|
|||
});
|
||||
|
||||
it('imagesList is not visible', () => {
|
||||
expect(findImagesList().exists()).toBe(false);
|
||||
expect(findImageList().exists()).toBe(false);
|
||||
});
|
||||
|
||||
it('quick start is not visible', () => {
|
||||
expect(findQuickStartDropdown().exists()).toBe(false);
|
||||
it('cli commands is not visible', () => {
|
||||
expect(findCliCommands().exists()).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -171,8 +162,8 @@ describe('List Page', () => {
|
|||
return waitForPromises();
|
||||
});
|
||||
|
||||
it('quick start is not visible', () => {
|
||||
expect(findQuickStartDropdown().exists()).toBe(false);
|
||||
it('cli commands is not visible', () => {
|
||||
expect(findCliCommands().exists()).toBe(false);
|
||||
});
|
||||
|
||||
it('project empty state is visible', () => {
|
||||
|
@ -193,8 +184,8 @@ describe('List Page', () => {
|
|||
expect(findGroupEmptyState().exists()).toBe(true);
|
||||
});
|
||||
|
||||
it('quick start is not visible', () => {
|
||||
expect(findQuickStartDropdown().exists()).toBe(false);
|
||||
it('cli commands is not visible', () => {
|
||||
expect(findCliCommands().exists()).toBe(false);
|
||||
});
|
||||
|
||||
it('list header is not visible', () => {
|
||||
|
@ -210,7 +201,7 @@ describe('List Page', () => {
|
|||
});
|
||||
|
||||
it('quick start is visible', () => {
|
||||
expect(findQuickStartDropdown().exists()).toBe(true);
|
||||
expect(findCliCommands().exists()).toBe(true);
|
||||
});
|
||||
|
||||
it('list component is visible', () => {
|
||||
|
|
|
@ -43,15 +43,6 @@ Object.assign(global, {
|
|||
preloadFixtures() {},
|
||||
});
|
||||
|
||||
Object.assign(global, {
|
||||
MutationObserver() {
|
||||
return {
|
||||
disconnect() {},
|
||||
observe() {},
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
// custom-jquery-matchers was written for an old Jest version, we need to make it compatible
|
||||
Object.entries(jqueryMatchers).forEach(([matcherName, matcherFactory]) => {
|
||||
// Don't override existing Jest matcher
|
||||
|
@ -69,12 +60,6 @@ expect.extend(customMatchers);
|
|||
// Tech debt issue TBD
|
||||
testUtilsConfig.logModifiedComponents = false;
|
||||
|
||||
// Basic stub for MutationObserver
|
||||
global.MutationObserver = () => ({
|
||||
disconnect: () => {},
|
||||
observe: () => {},
|
||||
});
|
||||
|
||||
Object.assign(global, {
|
||||
requestIdleCallback(cb) {
|
||||
const start = Date.now();
|
||||
|
|
|
@ -0,0 +1,47 @@
|
|||
import {
|
||||
generateToolbarItem,
|
||||
addCustomEventListener,
|
||||
} from '~/vue_shared/components/rich_content_editor/editor_service';
|
||||
|
||||
describe('Editor Service', () => {
|
||||
describe('generateToolbarItem', () => {
|
||||
const config = {
|
||||
icon: 'bold',
|
||||
command: 'some-command',
|
||||
tooltip: 'Some Tooltip',
|
||||
event: 'some-event',
|
||||
};
|
||||
const generatedItem = generateToolbarItem(config);
|
||||
|
||||
it('generates the correct command', () => {
|
||||
expect(generatedItem.options.command).toBe(config.command);
|
||||
});
|
||||
|
||||
it('generates the correct tooltip', () => {
|
||||
expect(generatedItem.options.tooltip).toBe(config.tooltip);
|
||||
});
|
||||
|
||||
it('generates the correct event', () => {
|
||||
expect(generatedItem.options.event).toBe(config.event);
|
||||
});
|
||||
|
||||
it('generates a divider when isDivider is set to true', () => {
|
||||
const isDivider = true;
|
||||
|
||||
expect(generateToolbarItem({ isDivider })).toBe('divider');
|
||||
});
|
||||
});
|
||||
|
||||
describe('addCustomEventListener', () => {
|
||||
const mockInstance = { eventManager: { addEventType: jest.fn(), listen: jest.fn() } };
|
||||
const event = 'someCustomEvent';
|
||||
const handler = jest.fn();
|
||||
|
||||
it('registers an event type on the instance and adds an event handler', () => {
|
||||
addCustomEventListener(mockInstance, event, handler);
|
||||
|
||||
expect(mockInstance.eventManager.addEventType).toHaveBeenCalledWith(event);
|
||||
expect(mockInstance.eventManager.listen).toHaveBeenCalledWith(event, handler);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -5,8 +5,16 @@ import {
|
|||
EDITOR_TYPES,
|
||||
EDITOR_HEIGHT,
|
||||
EDITOR_PREVIEW_STYLE,
|
||||
CUSTOM_EVENTS,
|
||||
} from '~/vue_shared/components/rich_content_editor/constants';
|
||||
|
||||
import { addCustomEventListener } from '~/vue_shared/components/rich_content_editor/editor_service';
|
||||
|
||||
jest.mock('~/vue_shared/components/rich_content_editor/editor_service', () => ({
|
||||
...jest.requireActual('~/vue_shared/components/rich_content_editor/editor_service'),
|
||||
addCustomEventListener: jest.fn(),
|
||||
}));
|
||||
|
||||
describe('Rich Content Editor', () => {
|
||||
let wrapper;
|
||||
|
||||
|
@ -56,4 +64,17 @@ describe('Rich Content Editor', () => {
|
|||
expect(wrapper.emitted().input[0][0]).toBe(changedMarkdown);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when editor is loaded', () => {
|
||||
it('adds the CUSTOM_EVENTS.openAddImageModal custom event listener', () => {
|
||||
const mockInstance = { eventManager: { addEventType: jest.fn(), listen: jest.fn() } };
|
||||
findEditor().vm.$emit('load', mockInstance);
|
||||
|
||||
expect(addCustomEventListener).toHaveBeenCalledWith(
|
||||
mockInstance,
|
||||
CUSTOM_EVENTS.openAddImageModal,
|
||||
wrapper.vm.onOpenAddImageModal,
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,29 +0,0 @@
|
|||
import { generateToolbarItem } from '~/vue_shared/components/rich_content_editor/toolbar_service';
|
||||
|
||||
describe('Toolbar Service', () => {
|
||||
const config = {
|
||||
icon: 'bold',
|
||||
command: 'some-command',
|
||||
tooltip: 'Some Tooltip',
|
||||
event: 'some-event',
|
||||
};
|
||||
const generatedItem = generateToolbarItem(config);
|
||||
|
||||
it('generates the correct command', () => {
|
||||
expect(generatedItem.options.command).toBe(config.command);
|
||||
});
|
||||
|
||||
it('generates the correct tooltip', () => {
|
||||
expect(generatedItem.options.tooltip).toBe(config.tooltip);
|
||||
});
|
||||
|
||||
it('generates the correct event', () => {
|
||||
expect(generatedItem.options.event).toBe(config.event);
|
||||
});
|
||||
|
||||
it('generates a divider when isDivider is set to true', () => {
|
||||
const isDivider = true;
|
||||
|
||||
expect(generateToolbarItem({ isDivider })).toBe('divider');
|
||||
});
|
||||
});
|
|
@ -109,31 +109,21 @@ describe 'Creating a Snippet' do
|
|||
context 'when the project path is invalid' do
|
||||
let(:project_path) { 'foobar' }
|
||||
|
||||
it 'returns an an error' do
|
||||
subject
|
||||
errors = json_response['errors']
|
||||
|
||||
expect(errors.first['message']).to eq(Gitlab::Graphql::Authorize::AuthorizeResource::RESOURCE_ACCESS_ERROR)
|
||||
end
|
||||
it_behaves_like 'a mutation that returns top-level errors',
|
||||
errors: [Gitlab::Graphql::Authorize::AuthorizeResource::RESOURCE_ACCESS_ERROR]
|
||||
end
|
||||
|
||||
context 'when the feature is disabled' do
|
||||
it 'returns an an error' do
|
||||
before do
|
||||
project.project_feature.update_attribute(:snippets_access_level, ProjectFeature::DISABLED)
|
||||
|
||||
subject
|
||||
errors = json_response['errors']
|
||||
|
||||
expect(errors.first['message']).to eq(Gitlab::Graphql::Authorize::AuthorizeResource::RESOURCE_ACCESS_ERROR)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when there are ActiveRecord validation errors' do
|
||||
let(:title) { '' }
|
||||
|
||||
it_behaves_like 'a mutation that returns errors in the response', errors: ["Title can't be blank"]
|
||||
it_behaves_like 'a mutation that returns top-level errors',
|
||||
errors: [Gitlab::Graphql::Authorize::AuthorizeResource::RESOURCE_ACCESS_ERROR]
|
||||
end
|
||||
end
|
||||
|
||||
shared_examples 'does not create snippet' do
|
||||
it 'does not create the Snippet' do
|
||||
expect do
|
||||
subject
|
||||
|
@ -147,7 +137,21 @@ describe 'Creating a Snippet' do
|
|||
end
|
||||
end
|
||||
|
||||
context 'when there uploaded files' do
|
||||
context 'when there are ActiveRecord validation errors' do
|
||||
let(:title) { '' }
|
||||
|
||||
it_behaves_like 'a mutation that returns errors in the response', errors: ["Title can't be blank"]
|
||||
it_behaves_like 'does not create snippet'
|
||||
end
|
||||
|
||||
context 'when there non ActiveRecord errors' do
|
||||
let(:file_name) { 'invalid://file/path' }
|
||||
|
||||
it_behaves_like 'a mutation that returns errors in the response', errors: ['Repository Error creating the snippet - Invalid file name']
|
||||
it_behaves_like 'does not create snippet'
|
||||
end
|
||||
|
||||
context 'when there are uploaded files' do
|
||||
shared_examples 'expected files argument' do |file_value, expected_value|
|
||||
let(:uploaded_files) { file_value }
|
||||
|
||||
|
|
Loading…
Reference in a new issue