mirror of
https://gitlab.com/gitlab-org/gitlab-foss.git
synced 2025-08-15 21:39:00 +00:00
Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
2
Gemfile
2
Gemfile
@ -310,7 +310,7 @@ gem 'pg_query', '~> 2.1'
|
||||
gem 'premailer-rails', '~> 1.10.3'
|
||||
|
||||
# LabKit: Tracing and Correlation
|
||||
gem 'gitlab-labkit', '~> 0.18.0'
|
||||
gem 'gitlab-labkit', '~> 0.20.0'
|
||||
# Thrift is a dependency of gitlab-labkit, we want a version higher than 0.14.0
|
||||
# because of https://gitlab.com/gitlab-org/gitlab/-/issues/321900
|
||||
gem 'thrift', '>= 0.14.0'
|
||||
|
@ -482,13 +482,13 @@ GEM
|
||||
fog-json (~> 1.2.0)
|
||||
mime-types
|
||||
ms_rest_azure (~> 0.12.0)
|
||||
gitlab-labkit (0.18.0)
|
||||
gitlab-labkit (0.20.0)
|
||||
actionpack (>= 5.0.0, < 7.0.0)
|
||||
activesupport (>= 5.0.0, < 7.0.0)
|
||||
gitlab-pg_query (~> 2.0)
|
||||
grpc (~> 1.19)
|
||||
jaeger-client (~> 1.1)
|
||||
opentracing (~> 0.4)
|
||||
pg_query (~> 2.1)
|
||||
redis (> 3.0.0, < 5.0.0)
|
||||
gitlab-license (1.5.0)
|
||||
gitlab-mail_room (0.0.9)
|
||||
@ -498,8 +498,6 @@ GEM
|
||||
addressable (~> 2.7)
|
||||
omniauth (~> 1.9)
|
||||
openid_connect (~> 1.2)
|
||||
gitlab-pg_query (2.0.4)
|
||||
google-protobuf (>= 3.17.1)
|
||||
gitlab-sidekiq-fetcher (0.5.6)
|
||||
sidekiq (~> 5)
|
||||
gitlab-styles (6.2.0)
|
||||
@ -1492,7 +1490,7 @@ DEPENDENCIES
|
||||
gitlab-dangerfiles (~> 2.2.1)
|
||||
gitlab-experiment (~> 0.6.1)
|
||||
gitlab-fog-azure-rm (~> 1.1.1)
|
||||
gitlab-labkit (~> 0.18.0)
|
||||
gitlab-labkit (~> 0.20.0)
|
||||
gitlab-license (~> 1.5)
|
||||
gitlab-mail_room (~> 0.0.9)
|
||||
gitlab-markup (~> 1.7.1)
|
||||
|
@ -11,31 +11,10 @@ import { isObject } from './type_utility';
|
||||
import { getLocationHash } from './url_utility';
|
||||
|
||||
export const getPagePath = (index = 0) => {
|
||||
const page = $('body').attr('data-page') || '';
|
||||
|
||||
const { page = '' } = document?.body?.dataset;
|
||||
return page.split(':')[index];
|
||||
};
|
||||
|
||||
export const getDashPath = (path = window.location.pathname) => path.split('/-/')[1] || null;
|
||||
|
||||
export const isInGroupsPage = () => getPagePath() === 'groups';
|
||||
|
||||
export const isInProjectPage = () => getPagePath() === 'projects';
|
||||
|
||||
export const getProjectSlug = () => {
|
||||
if (isInProjectPage()) {
|
||||
return $('body').data('project');
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
export const getGroupSlug = () => {
|
||||
if (isInProjectPage() || isInGroupsPage()) {
|
||||
return $('body').data('group');
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
export const checkPageAndAction = (page, action) => {
|
||||
const pagePath = getPagePath(1);
|
||||
const actionPath = getPagePath(2);
|
||||
@ -49,6 +28,8 @@ export const isInDesignPage = () => checkPageAndAction('issues', 'designs');
|
||||
export const isInMRPage = () => checkPageAndAction('merge_requests', 'show');
|
||||
export const isInEpicPage = () => checkPageAndAction('epics', 'show');
|
||||
|
||||
export const getDashPath = (path = window.location.pathname) => path.split('/-/')[1] || null;
|
||||
|
||||
export const getCspNonceValue = () => {
|
||||
const metaTag = document.querySelector('meta[name=csp-nonce]');
|
||||
return metaTag && metaTag.content;
|
||||
@ -328,8 +309,8 @@ export const insertText = (target, text) => {
|
||||
};
|
||||
|
||||
/**
|
||||
this will take in the headers from an API response and normalize them
|
||||
this way we don't run into production issues when nginx gives us lowercased header keys
|
||||
this will take in the headers from an API response and normalize them
|
||||
this way we don't run into production issues when nginx gives us lowercased header keys
|
||||
*/
|
||||
export const normalizeHeaders = (headers) => {
|
||||
const upperCaseHeaders = {};
|
||||
|
@ -0,0 +1,71 @@
|
||||
<script>
|
||||
import { GlTooltipDirective, GlIcon } from '@gitlab/ui';
|
||||
import {
|
||||
ASYNC_DELETE_IMAGE_ERROR_MESSAGE,
|
||||
CLEANUP_STATUS_SCHEDULED,
|
||||
CLEANUP_STATUS_ONGOING,
|
||||
CLEANUP_STATUS_UNFINISHED,
|
||||
UNFINISHED_STATUS,
|
||||
UNSCHEDULED_STATUS,
|
||||
SCHEDULED_STATUS,
|
||||
ONGOING_STATUS,
|
||||
} from '../../constants/index';
|
||||
|
||||
export default {
|
||||
name: 'CleanupStatus',
|
||||
components: {
|
||||
GlIcon,
|
||||
},
|
||||
directives: {
|
||||
GlTooltip: GlTooltipDirective,
|
||||
},
|
||||
props: {
|
||||
status: {
|
||||
type: String,
|
||||
required: true,
|
||||
validator(value) {
|
||||
return [UNFINISHED_STATUS, UNSCHEDULED_STATUS, SCHEDULED_STATUS, ONGOING_STATUS].includes(
|
||||
value,
|
||||
);
|
||||
},
|
||||
},
|
||||
},
|
||||
i18n: {
|
||||
CLEANUP_STATUS_SCHEDULED,
|
||||
CLEANUP_STATUS_ONGOING,
|
||||
CLEANUP_STATUS_UNFINISHED,
|
||||
ASYNC_DELETE_IMAGE_ERROR_MESSAGE,
|
||||
},
|
||||
computed: {
|
||||
showStatus() {
|
||||
return this.status !== UNSCHEDULED_STATUS;
|
||||
},
|
||||
failedDelete() {
|
||||
return this.status === UNFINISHED_STATUS;
|
||||
},
|
||||
statusText() {
|
||||
return this.$options.i18n[`CLEANUP_STATUS_${this.status}`];
|
||||
},
|
||||
expireIconClass() {
|
||||
return this.failedDelete ? 'gl-text-orange-500' : '';
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div v-if="showStatus" class="gl-display-inline-flex gl-align-items-center">
|
||||
<gl-icon name="expire" data-testid="main-icon" :class="expireIconClass" />
|
||||
<span class="gl-mx-2">
|
||||
{{ statusText }}
|
||||
</span>
|
||||
<gl-icon
|
||||
v-if="failedDelete"
|
||||
v-gl-tooltip="{ title: $options.i18n.ASYNC_DELETE_IMAGE_ERROR_MESSAGE }"
|
||||
:size="14"
|
||||
class="gl-text-black-normal"
|
||||
data-testid="extra-info"
|
||||
name="information"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
@ -16,6 +16,7 @@ import {
|
||||
ROOT_IMAGE_TEXT,
|
||||
} from '../../constants/index';
|
||||
import DeleteButton from '../delete_button.vue';
|
||||
import CleanupStatus from './cleanup_status.vue';
|
||||
|
||||
export default {
|
||||
name: 'ImageListRow',
|
||||
@ -26,6 +27,7 @@ export default {
|
||||
GlIcon,
|
||||
ListItem,
|
||||
GlSkeletonLoader,
|
||||
CleanupStatus,
|
||||
},
|
||||
directives: {
|
||||
GlTooltip: GlTooltipDirective,
|
||||
@ -112,27 +114,24 @@ export default {
|
||||
:title="item.location"
|
||||
category="tertiary"
|
||||
/>
|
||||
<gl-icon
|
||||
v-if="warningIconText"
|
||||
v-gl-tooltip="{ title: warningIconText }"
|
||||
data-testid="warning-icon"
|
||||
name="warning"
|
||||
class="gl-text-orange-500"
|
||||
/>
|
||||
</template>
|
||||
<template #left-secondary>
|
||||
<span
|
||||
v-if="!metadataLoading"
|
||||
class="gl-display-flex gl-align-items-center"
|
||||
data-testid="tags-count"
|
||||
>
|
||||
<gl-icon name="tag" class="gl-mr-2" />
|
||||
<gl-sprintf :message="tagsCountText">
|
||||
<template #count>
|
||||
{{ item.tagsCount }}
|
||||
</template>
|
||||
</gl-sprintf>
|
||||
</span>
|
||||
<template v-if="!metadataLoading">
|
||||
<span class="gl-display-flex gl-align-items-center" data-testid="tags-count">
|
||||
<gl-icon name="tag" class="gl-mr-2" />
|
||||
<gl-sprintf :message="tagsCountText">
|
||||
<template #count>
|
||||
{{ item.tagsCount }}
|
||||
</template>
|
||||
</gl-sprintf>
|
||||
</span>
|
||||
|
||||
<cleanup-status
|
||||
v-if="item.expirationPolicyCleanupStatus"
|
||||
class="ml-2"
|
||||
:status="item.expirationPolicyCleanupStatus"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<div v-else class="gl-w-full">
|
||||
<gl-skeleton-loader :width="900" :height="16" preserve-aspect-ratio="xMinYMax meet">
|
||||
|
@ -89,6 +89,10 @@ export const CLEANUP_DISABLED_TOOLTIP = s__(
|
||||
'ContainerRegistry|Cleanup is disabled for this project',
|
||||
);
|
||||
|
||||
export const CLEANUP_STATUS_SCHEDULED = s__('ContainerRegistry|Cleanup will run soon');
|
||||
export const CLEANUP_STATUS_ONGOING = s__('ContainerRegistry|Cleanup is ongoing');
|
||||
export const CLEANUP_STATUS_UNFINISHED = s__('ContainerRegistry|Cleanup timed out');
|
||||
|
||||
export const DETAILS_DELETE_IMAGE_ERROR_MESSAGE = s__(
|
||||
'ContainerRegistry|Something went wrong while scheduling the image for deletion.',
|
||||
);
|
||||
|
@ -0,0 +1,60 @@
|
||||
<script>
|
||||
import { GlButton } from '@gitlab/ui';
|
||||
import { __ } from '~/locale';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
GlButton,
|
||||
},
|
||||
props: {
|
||||
value: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: '',
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
isMasked: true,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
label() {
|
||||
if (this.isMasked) {
|
||||
return __('Click to reveal');
|
||||
}
|
||||
return __('Click to hide');
|
||||
},
|
||||
icon() {
|
||||
if (this.isMasked) {
|
||||
return 'eye';
|
||||
}
|
||||
return 'eye-slash';
|
||||
},
|
||||
displayedValue() {
|
||||
if (this.isMasked && this.value?.length) {
|
||||
return '*'.repeat(this.value.length);
|
||||
}
|
||||
return this.value;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
toggleMasked() {
|
||||
this.isMasked = !this.isMasked;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<template>
|
||||
<span
|
||||
>{{ displayedValue }}
|
||||
<gl-button
|
||||
:aria-label="label"
|
||||
:icon="icon"
|
||||
class="gl-text-body!"
|
||||
data-testid="toggle-masked"
|
||||
variant="link"
|
||||
@click="toggleMasked"
|
||||
/>
|
||||
</span>
|
||||
</template>
|
@ -1,6 +1,7 @@
|
||||
<script>
|
||||
import { GlLink, GlSprintf, GlTooltipDirective } from '@gitlab/ui';
|
||||
import { s__ } from '~/locale';
|
||||
import MaskedValue from '~/runner/components/helpers/masked_value.vue';
|
||||
import RunnerRegistrationTokenReset from '~/runner/components/runner_registration_token_reset.vue';
|
||||
import ClipboardButton from '~/vue_shared/components/clipboard_button.vue';
|
||||
import RunnerInstructions from '~/vue_shared/components/runner_instructions/runner_instructions.vue';
|
||||
@ -11,6 +12,7 @@ export default {
|
||||
GlLink,
|
||||
GlSprintf,
|
||||
ClipboardButton,
|
||||
MaskedValue,
|
||||
RunnerInstructions,
|
||||
RunnerRegistrationTokenReset,
|
||||
},
|
||||
@ -92,7 +94,9 @@ export default {
|
||||
{{ __('And this registration token:') }}
|
||||
<br />
|
||||
|
||||
<code data-testid="registration-token">{{ currentRegistrationToken }}</code>
|
||||
<code data-testid="registration-token"
|
||||
><masked-value :value="currentRegistrationToken"
|
||||
/></code>
|
||||
<clipboard-button :title="__('Copy token')" :text="currentRegistrationToken" />
|
||||
</li>
|
||||
</ol>
|
||||
|
@ -8,13 +8,13 @@ import { DEFAULT_DEBOUNCE_AND_THROTTLE_MS } from '~/lib/utils/constants';
|
||||
import { s__, __, sprintf } from '~/locale';
|
||||
import Tracking from '~/tracking';
|
||||
import axios from './lib/utils/axios_utils';
|
||||
import { spriteIcon } from './lib/utils/common_utils';
|
||||
import {
|
||||
isInGroupsPage,
|
||||
isInProjectPage,
|
||||
getGroupSlug,
|
||||
getProjectSlug,
|
||||
spriteIcon,
|
||||
} from './lib/utils/common_utils';
|
||||
} from './search_autocomplete_utils';
|
||||
|
||||
/**
|
||||
* Search input in top navigation bar.
|
||||
|
19
app/assets/javascripts/search_autocomplete_utils.js
Normal file
19
app/assets/javascripts/search_autocomplete_utils.js
Normal file
@ -0,0 +1,19 @@
|
||||
import { getPagePath } from './lib/utils/common_utils';
|
||||
|
||||
export const isInGroupsPage = () => getPagePath() === 'groups';
|
||||
|
||||
export const isInProjectPage = () => getPagePath() === 'projects';
|
||||
|
||||
export const getProjectSlug = () => {
|
||||
if (isInProjectPage()) {
|
||||
return document?.body?.dataset?.project;
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
export const getGroupSlug = () => {
|
||||
if (isInProjectPage() || isInGroupsPage()) {
|
||||
return document?.body?.dataset?.group;
|
||||
}
|
||||
return null;
|
||||
};
|
@ -7,6 +7,7 @@
|
||||
.diff-files-holder {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
z-index: 203;
|
||||
|
||||
.vue-recycle-scroller__item-wrapper {
|
||||
overflow: visible;
|
||||
|
@ -29,6 +29,7 @@ query getProjectContainerRepositories(
|
||||
canDelete
|
||||
createdAt
|
||||
expirationPolicyStartedAt
|
||||
expirationPolicyCleanupStatus
|
||||
__typename
|
||||
}
|
||||
pageInfo {
|
||||
@ -61,6 +62,7 @@ query getProjectContainerRepositories(
|
||||
canDelete
|
||||
createdAt
|
||||
expirationPolicyStartedAt
|
||||
expirationPolicyCleanupStatus
|
||||
__typename
|
||||
}
|
||||
pageInfo {
|
||||
|
@ -10,9 +10,7 @@
|
||||
= f.label :repository_checks_enabled, class: 'form-check-label' do
|
||||
= _("Enable repository checks")
|
||||
.form-text.text-muted
|
||||
= _("Run")
|
||||
%code= _("git fsck")
|
||||
= _("periodically in all project and wiki repositories to look for silent disk corruption issues.")
|
||||
= html_escape(s_('Run %{code_start}git fsck%{code_end} periodically in all project and wiki repositories to look for silent disk corruption issues.')) % { code_start: '<code>'.html_safe, code_end: '</code>'.html_safe }
|
||||
.form-group
|
||||
.form-text.text-muted
|
||||
= _("If you get a lot of false alarms from repository checks, you can clear all repository check information from the database.")
|
||||
@ -41,22 +39,16 @@
|
||||
= f.label :housekeeping_incremental_repack_period, 'Incremental repack period', class: 'label-bold'
|
||||
= f.number_field :housekeeping_incremental_repack_period, class: 'form-control gl-form-input'
|
||||
.form-text.text-muted
|
||||
= _("Number of Git pushes after which an incremental")
|
||||
%code= _("git repack")
|
||||
= _("is run.")
|
||||
= html_escape(s_('Number of Git pushes after which an incremental %{code_start}git repack%{code_end} is run.')) % { code_start: '<code>'.html_safe, code_end: '</code>'.html_safe }
|
||||
.form-group
|
||||
= f.label :housekeeping_full_repack_period, 'Full repack period', class: 'label-bold'
|
||||
= f.number_field :housekeeping_full_repack_period, class: 'form-control gl-form-input'
|
||||
.form-text.text-muted
|
||||
= _("Number of Git pushes after which a full")
|
||||
%code= _("git repack")
|
||||
= _("is run.")
|
||||
= html_escape(s_('Number of Git pushes after which a full %{code_start}git repack%{code_end} is run.')) % { code_start: '<code>'.html_safe, code_end: '</code>'.html_safe }
|
||||
.form-group
|
||||
= f.label :housekeeping_gc_period, _('Git GC period'), class: 'label-bold'
|
||||
= f.number_field :housekeeping_gc_period, class: 'form-control gl-form-input'
|
||||
.form-text.text-muted
|
||||
= _("Number of Git pushes after which")
|
||||
%code= _("git gc")
|
||||
= _("is run.")
|
||||
= html_escape(s_('Number of Git pushes after which %{code_start}git gc%{code_end} is run.')) % { code_start: '<code>'.html_safe, code_end: '</code>'.html_safe }
|
||||
|
||||
= f.submit _('Save changes'), class: "gl-button btn btn-confirm"
|
||||
|
@ -43,11 +43,11 @@
|
||||
%button.btn.gl-button.btn-default.js-settings-toggle{ type: 'button' }
|
||||
= expanded_by_default? ? _('Collapse') : _('Expand')
|
||||
%p
|
||||
= _('Configure')
|
||||
= link_to s_('repository checks'), help_page_path('administration/repository_checks.md'), target: '_blank', rel: 'noopener noreferrer'
|
||||
= _('and')
|
||||
= link_to s_('housekeeping'), help_page_path('administration/housekeeping.md'), target: '_blank', rel: 'noopener noreferrer'
|
||||
= _('on repositories.')
|
||||
- repository_checks_link_url = help_page_path('administration/repository_checks.md')
|
||||
- repository_checks_link_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: repository_checks_link_url }
|
||||
- housekeeping_link_url = help_page_path('administration/housekeeping.md')
|
||||
- housekeeping_link_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: housekeeping_link_url }
|
||||
= html_escape(s_('Configure %{repository_checks_link_start}repository checks%{link_end} and %{housekeeping_link_start}housekeeping%{link_end} on repositories.')) % { repository_checks_link_start: repository_checks_link_start, housekeeping_link_start: housekeeping_link_start, link_end: '</a>'.html_safe }
|
||||
.settings-content
|
||||
= render 'repository_check'
|
||||
|
||||
|
@ -1,5 +1,4 @@
|
||||
- page_title _("Blame"), @blob.path, @ref
|
||||
- link_icon = sprite_icon("link", size: 12)
|
||||
|
||||
#blob-content-holder.tree-holder
|
||||
= render "projects/blob/breadcrumb", blob: @blob, blame: true
|
||||
@ -48,8 +47,8 @@
|
||||
%td.line-numbers
|
||||
- (current_line...(current_line + line_count)).each do |i|
|
||||
%a.diff-line-num.gl-justify-content-end{ href: "#L#{i}", id: "L#{i}", 'data-line-number' => i, class: "gl-display-flex!" }
|
||||
= link_icon
|
||||
= i
|
||||
.file-line-num
|
||||
= i
|
||||
\
|
||||
|
||||
%td.lines
|
||||
|
@ -122,6 +122,7 @@ From there, you can see the following actions:
|
||||
- When default branch changes for a project ([Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/52339) in GitLab 13.9)
|
||||
- Created, updated, or deleted DAST profiles, DAST scanner profiles, and DAST site profiles
|
||||
([Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/217872) in GitLab 14.1)
|
||||
- Changed a project's compliance framework ([Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/329362) in GitLab 14.1)
|
||||
|
||||
Project events can also be accessed via the [Project Audit Events API](../api/audit_events.md#project-audit-events).
|
||||
|
||||
|
@ -73,6 +73,10 @@ WARNING:
|
||||
GitLab does not back up any configuration files, SSL certificates, or system
|
||||
files. You are highly advised to read about [storing configuration files](#storing-configuration-files).
|
||||
|
||||
WARNING:
|
||||
The backup command requires [additional parameters](#backup-and-restore-for-installations-using-pgbouncer) when
|
||||
your installation is using PgBouncer, for either performance reasons or when using it with a Patroni cluster.
|
||||
|
||||
Depending on your version of GitLab, use the following command if you installed
|
||||
GitLab using the Omnibus package:
|
||||
|
||||
@ -955,8 +959,9 @@ installed version of GitLab, the restore command aborts with an error
|
||||
message. Install the [correct GitLab version](https://packages.gitlab.com/gitlab/),
|
||||
and then try again.
|
||||
|
||||
NOTE:
|
||||
There is a known issue with restore not working with `pgbouncer`. [Read more about backup and restore with `pgbouncer`](#backup-and-restore-for-installations-using-pgbouncer).
|
||||
WARNING:
|
||||
The restore command requires [additional parameters](#backup-and-restore-for-installations-using-pgbouncer) when
|
||||
your installation is using PgBouncer, for either performance reasons or when using it with a Patroni cluster.
|
||||
|
||||
Next, restore `/etc/gitlab/gitlab-secrets.json` if necessary,
|
||||
[as previously mentioned](#restore-prerequisites).
|
||||
|
@ -720,10 +720,10 @@ msgstr ""
|
||||
msgid "%{mrText}, this issue will be closed automatically."
|
||||
msgstr ""
|
||||
|
||||
msgid "%{name_with_link} has %{percent} or less Shared Runner Pipeline minutes remaining. Once it runs out, no new jobs or pipelines in its projects will run."
|
||||
msgid "%{name_with_link} namespace has %{percent} or less Shared Runner Pipeline minutes remaining. Once it runs out, no new jobs or pipelines in its projects will run."
|
||||
msgstr ""
|
||||
|
||||
msgid "%{name_with_link} has run out of Shared Runner Pipeline minutes so no new jobs or pipelines in its projects will run."
|
||||
msgid "%{name_with_link} namespace has run out of Shared Runner Pipeline minutes. No new jobs or pipelines in its projects will run."
|
||||
msgstr ""
|
||||
|
||||
msgid "%{name} %{status}"
|
||||
@ -747,10 +747,10 @@ msgstr ""
|
||||
msgid "%{name}'s avatar"
|
||||
msgstr ""
|
||||
|
||||
msgid "%{name}(%{url}) has %{percent} or less Shared Runner Pipeline minutes remaining. Once it runs out, no new jobs or pipelines in its projects will run."
|
||||
msgid "%{name}(%{url}) namespace has %{percent} or less Shared Runner Pipeline minutes remaining. After it runs out, no new jobs or pipelines in its projects will run."
|
||||
msgstr ""
|
||||
|
||||
msgid "%{name}(%{url}) has run out of Shared Runner Pipeline minutes so no new jobs or pipelines in its projects will run."
|
||||
msgid "%{name}(%{url}) namespace has run out of Shared Runner Pipeline minutes so no new jobs or pipelines in its projects will run."
|
||||
msgstr ""
|
||||
|
||||
msgid "%{name}, confirm your email address now!"
|
||||
@ -6840,6 +6840,12 @@ msgstr ""
|
||||
msgid "Click to expand text"
|
||||
msgstr ""
|
||||
|
||||
msgid "Click to hide"
|
||||
msgstr ""
|
||||
|
||||
msgid "Click to reveal"
|
||||
msgstr ""
|
||||
|
||||
msgid "Client authentication certificate"
|
||||
msgstr ""
|
||||
|
||||
@ -8237,15 +8243,15 @@ msgstr ""
|
||||
msgid "Configuration help"
|
||||
msgstr ""
|
||||
|
||||
msgid "Configure"
|
||||
msgstr ""
|
||||
|
||||
msgid "Configure %{italic_start}What's new%{italic_end} drawer and content."
|
||||
msgstr ""
|
||||
|
||||
msgid "Configure %{link} to track events. %{link_start}Learn more.%{link_end}"
|
||||
msgstr ""
|
||||
|
||||
msgid "Configure %{repository_checks_link_start}repository checks%{link_end} and %{housekeeping_link_start}housekeeping%{link_end} on repositories."
|
||||
msgstr ""
|
||||
|
||||
msgid "Configure GitLab runners to start using the Web Terminal. %{helpStart}Learn more.%{helpEnd}"
|
||||
msgstr ""
|
||||
|
||||
@ -8490,6 +8496,9 @@ msgstr ""
|
||||
msgid "ContainerRegistry|Cleanup is disabled for this project"
|
||||
msgstr ""
|
||||
|
||||
msgid "ContainerRegistry|Cleanup is ongoing"
|
||||
msgstr ""
|
||||
|
||||
msgid "ContainerRegistry|Cleanup pending"
|
||||
msgstr ""
|
||||
|
||||
@ -8505,6 +8514,9 @@ msgstr ""
|
||||
msgid "ContainerRegistry|Cleanup ran but some tags were not removed"
|
||||
msgstr ""
|
||||
|
||||
msgid "ContainerRegistry|Cleanup timed out"
|
||||
msgstr ""
|
||||
|
||||
msgid "ContainerRegistry|Cleanup timed out before it could delete all tags"
|
||||
msgstr ""
|
||||
|
||||
@ -22616,13 +22628,13 @@ msgstr ""
|
||||
msgid "Number of Elasticsearch shards and replicas (per index)"
|
||||
msgstr ""
|
||||
|
||||
msgid "Number of Git pushes after which"
|
||||
msgid "Number of Git pushes after which %{code_start}git gc%{code_end} is run."
|
||||
msgstr ""
|
||||
|
||||
msgid "Number of Git pushes after which a full"
|
||||
msgid "Number of Git pushes after which a full %{code_start}git repack%{code_end} is run."
|
||||
msgstr ""
|
||||
|
||||
msgid "Number of Git pushes after which an incremental"
|
||||
msgid "Number of Git pushes after which an incremental %{code_start}git repack%{code_end} is run."
|
||||
msgstr ""
|
||||
|
||||
msgid "Number of LOCs per commit"
|
||||
@ -24127,12 +24139,6 @@ msgstr ""
|
||||
msgid "Pipelines|GitLab CI/CD can automatically build, test, and deploy your code. Let GitLab take care of time consuming tasks, so you can spend more time creating."
|
||||
msgstr ""
|
||||
|
||||
msgid "Pipelines|Group %{namespace_name} has %{percentage}%% or less Shared Runner Pipeline minutes remaining. Once it runs out, no new jobs or pipelines in its projects will run."
|
||||
msgstr ""
|
||||
|
||||
msgid "Pipelines|Group %{namespace_name} has exceeded its pipeline minutes quota. Unless you buy additional pipeline minutes, no new jobs or pipelines in its projects will run."
|
||||
msgstr ""
|
||||
|
||||
msgid "Pipelines|If you are unsure, please ask a project maintainer to review it for you."
|
||||
msgstr ""
|
||||
|
||||
@ -24187,6 +24193,12 @@ msgstr ""
|
||||
msgid "Pipelines|Something went wrong while cleaning runners cache."
|
||||
msgstr ""
|
||||
|
||||
msgid "Pipelines|The %{namespace_name} namespace has %{percentage}%% or less Shared Runner Pipeline minutes remaining. After it runs out, no new jobs or pipelines in its projects will run."
|
||||
msgstr ""
|
||||
|
||||
msgid "Pipelines|The %{namespace_name} namespace has exceeded its pipeline minutes quota. Buy additional pipeline minutes, or no new jobs or pipelines in its projects will run."
|
||||
msgstr ""
|
||||
|
||||
msgid "Pipelines|The CI configuration was not loaded, please try again."
|
||||
msgstr ""
|
||||
|
||||
@ -28134,7 +28146,7 @@ msgstr ""
|
||||
msgid "Rules that define what git pushes are accepted for a project. All newly created projects will use these settings."
|
||||
msgstr ""
|
||||
|
||||
msgid "Run"
|
||||
msgid "Run %{code_start}git fsck%{code_end} periodically in all project and wiki repositories to look for silent disk corruption issues."
|
||||
msgstr ""
|
||||
|
||||
msgid "Run CI/CD pipelines for external repositories"
|
||||
@ -36570,10 +36582,10 @@ msgstr ""
|
||||
msgid "We recommend leaving all SAST analyzers enabled"
|
||||
msgstr ""
|
||||
|
||||
msgid "We recommend that you buy more Pipeline minutes to avoid any interruption of service."
|
||||
msgid "We recommend that you buy additional Pipeline minutes to avoid any interruption of service."
|
||||
msgstr ""
|
||||
|
||||
msgid "We recommend that you buy more Pipeline minutes to resume normal service."
|
||||
msgid "We recommend that you buy additional Pipeline minutes to resume normal service."
|
||||
msgstr ""
|
||||
|
||||
msgid "We sent you an email with reset password instructions"
|
||||
@ -38752,15 +38764,6 @@ msgid_plural "from %d jobs"
|
||||
msgstr[0] ""
|
||||
msgstr[1] ""
|
||||
|
||||
msgid "git fsck"
|
||||
msgstr ""
|
||||
|
||||
msgid "git gc"
|
||||
msgstr ""
|
||||
|
||||
msgid "git repack"
|
||||
msgstr ""
|
||||
|
||||
msgid "group"
|
||||
msgstr ""
|
||||
|
||||
@ -38794,9 +38797,6 @@ msgstr ""
|
||||
msgid "here"
|
||||
msgstr ""
|
||||
|
||||
msgid "housekeeping"
|
||||
msgstr ""
|
||||
|
||||
msgid "http:"
|
||||
msgstr ""
|
||||
|
||||
@ -38892,9 +38892,6 @@ msgstr ""
|
||||
msgid "is read-only"
|
||||
msgstr ""
|
||||
|
||||
msgid "is run."
|
||||
msgstr ""
|
||||
|
||||
msgid "is too long (%{current_value}). The maximum size is %{max_size}."
|
||||
msgstr ""
|
||||
|
||||
@ -39395,9 +39392,6 @@ msgstr ""
|
||||
msgid "nounSeries|%{item}, and %{lastItem}"
|
||||
msgstr ""
|
||||
|
||||
msgid "on repositories."
|
||||
msgstr ""
|
||||
|
||||
msgid "on track"
|
||||
msgstr ""
|
||||
|
||||
@ -39441,9 +39435,6 @@ msgstr ""
|
||||
msgid "per day"
|
||||
msgstr ""
|
||||
|
||||
msgid "periodically in all project and wiki repositories to look for silent disk corruption issues."
|
||||
msgstr ""
|
||||
|
||||
msgid "personal access token"
|
||||
msgstr ""
|
||||
|
||||
@ -39549,9 +39540,6 @@ msgid_plural "replies"
|
||||
msgstr[0] ""
|
||||
msgstr[1] ""
|
||||
|
||||
msgid "repository checks"
|
||||
msgstr ""
|
||||
|
||||
msgid "repository:"
|
||||
msgstr ""
|
||||
|
||||
|
@ -56,7 +56,7 @@ module DeprecationToolkitEnv
|
||||
def self.allowed_kwarg_warning_paths
|
||||
%w[
|
||||
asciidoctor-2.0.12/lib/asciidoctor/extensions.rb
|
||||
gitlab-labkit-0.18.0/lib/labkit/correlation/grpc/client_interceptor.rb
|
||||
gitlab-labkit-0.20.0/lib/labkit/correlation/grpc/client_interceptor.rb
|
||||
]
|
||||
end
|
||||
|
||||
|
@ -1,6 +1,56 @@
|
||||
import * as commonUtils from '~/lib/utils/common_utils';
|
||||
|
||||
describe('common_utils', () => {
|
||||
describe('getPagePath', () => {
|
||||
const { getPagePath } = commonUtils;
|
||||
|
||||
let originalBody;
|
||||
|
||||
beforeEach(() => {
|
||||
originalBody = document.body;
|
||||
document.body = document.createElement('body');
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
document.body = originalBody;
|
||||
});
|
||||
|
||||
it('returns an empty path if none is defined', () => {
|
||||
expect(getPagePath()).toBe('');
|
||||
expect(getPagePath(0)).toBe('');
|
||||
});
|
||||
|
||||
describe('returns a path', () => {
|
||||
const mockSection = 'my_section';
|
||||
const mockSubSection = 'my_sub_section';
|
||||
const mockPage = 'my_page';
|
||||
|
||||
it('returns a page', () => {
|
||||
document.body.dataset.page = mockPage;
|
||||
|
||||
expect(getPagePath()).toBe(mockPage);
|
||||
expect(getPagePath(0)).toBe(mockPage);
|
||||
});
|
||||
|
||||
it('returns a section and page', () => {
|
||||
document.body.dataset.page = `${mockSection}:${mockPage}`;
|
||||
|
||||
expect(getPagePath()).toBe(mockSection);
|
||||
expect(getPagePath(0)).toBe(mockSection);
|
||||
expect(getPagePath(1)).toBe(mockPage);
|
||||
});
|
||||
|
||||
it('returns a section and subsection', () => {
|
||||
document.body.dataset.page = `${mockSection}:${mockSubSection}:${mockPage}`;
|
||||
|
||||
expect(getPagePath()).toBe(mockSection);
|
||||
expect(getPagePath(0)).toBe(mockSection);
|
||||
expect(getPagePath(1)).toBe(mockSubSection);
|
||||
expect(getPagePath(2)).toBe(mockPage);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('parseUrl', () => {
|
||||
it('returns an anchor tag with url', () => {
|
||||
expect(commonUtils.parseUrl('/some/absolute/url').pathname).toContain('some/absolute/url');
|
||||
|
@ -0,0 +1,87 @@
|
||||
import { createMockDirective, getBinding } from 'helpers/vue_mock_directive';
|
||||
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
|
||||
import CleanupStatus from '~/registry/explorer/components/list_page/cleanup_status.vue';
|
||||
import {
|
||||
ASYNC_DELETE_IMAGE_ERROR_MESSAGE,
|
||||
CLEANUP_STATUS_SCHEDULED,
|
||||
CLEANUP_STATUS_ONGOING,
|
||||
CLEANUP_STATUS_UNFINISHED,
|
||||
UNFINISHED_STATUS,
|
||||
UNSCHEDULED_STATUS,
|
||||
SCHEDULED_STATUS,
|
||||
ONGOING_STATUS,
|
||||
} from '~/registry/explorer/constants';
|
||||
|
||||
describe('cleanup_status', () => {
|
||||
let wrapper;
|
||||
|
||||
const findMainIcon = () => wrapper.findByTestId('main-icon');
|
||||
const findExtraInfoIcon = () => wrapper.findByTestId('extra-info');
|
||||
|
||||
const mountComponent = (propsData = { status: SCHEDULED_STATUS }) => {
|
||||
wrapper = shallowMountExtended(CleanupStatus, {
|
||||
propsData,
|
||||
directives: {
|
||||
GlTooltip: createMockDirective(),
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
afterEach(() => {
|
||||
wrapper.destroy();
|
||||
});
|
||||
|
||||
it.each`
|
||||
status | visible | text
|
||||
${UNFINISHED_STATUS} | ${true} | ${CLEANUP_STATUS_UNFINISHED}
|
||||
${SCHEDULED_STATUS} | ${true} | ${CLEANUP_STATUS_SCHEDULED}
|
||||
${ONGOING_STATUS} | ${true} | ${CLEANUP_STATUS_ONGOING}
|
||||
${UNSCHEDULED_STATUS} | ${false} | ${''}
|
||||
`(
|
||||
'when the status is $status is $visible that the component is mounted and has the correct text',
|
||||
({ status, visible, text }) => {
|
||||
mountComponent({ status });
|
||||
|
||||
expect(findMainIcon().exists()).toBe(visible);
|
||||
expect(wrapper.text()).toBe(text);
|
||||
},
|
||||
);
|
||||
|
||||
describe('main icon', () => {
|
||||
it('exists', () => {
|
||||
mountComponent();
|
||||
|
||||
expect(findMainIcon().exists()).toBe(true);
|
||||
});
|
||||
|
||||
it(`has the orange class when the status is ${UNFINISHED_STATUS}`, () => {
|
||||
mountComponent({ status: UNFINISHED_STATUS });
|
||||
|
||||
expect(findMainIcon().classes('gl-text-orange-500')).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('extra info icon', () => {
|
||||
it.each`
|
||||
status | visible
|
||||
${UNFINISHED_STATUS} | ${true}
|
||||
${SCHEDULED_STATUS} | ${false}
|
||||
${ONGOING_STATUS} | ${false}
|
||||
`(
|
||||
'when the status is $status is $visible that the extra icon is visible',
|
||||
({ status, visible }) => {
|
||||
mountComponent({ status });
|
||||
|
||||
expect(findExtraInfoIcon().exists()).toBe(visible);
|
||||
},
|
||||
);
|
||||
|
||||
it(`has a tooltip`, () => {
|
||||
mountComponent({ status: UNFINISHED_STATUS });
|
||||
|
||||
const tooltip = getBinding(findExtraInfoIcon().element, 'gl-tooltip');
|
||||
|
||||
expect(tooltip.value.title).toBe(ASYNC_DELETE_IMAGE_ERROR_MESSAGE);
|
||||
});
|
||||
});
|
||||
});
|
@ -3,15 +3,14 @@ import { shallowMount } from '@vue/test-utils';
|
||||
import { createMockDirective, getBinding } from 'helpers/vue_mock_directive';
|
||||
import { getIdFromGraphQLId } from '~/graphql_shared/utils';
|
||||
import DeleteButton from '~/registry/explorer/components/delete_button.vue';
|
||||
import CleanupStatus from '~/registry/explorer/components/list_page/cleanup_status.vue';
|
||||
import Component from '~/registry/explorer/components/list_page/image_list_row.vue';
|
||||
import {
|
||||
ROW_SCHEDULED_FOR_DELETION,
|
||||
LIST_DELETE_BUTTON_DISABLED,
|
||||
REMOVE_REPOSITORY_LABEL,
|
||||
ASYNC_DELETE_IMAGE_ERROR_MESSAGE,
|
||||
CLEANUP_TIMED_OUT_ERROR_MESSAGE,
|
||||
IMAGE_DELETE_SCHEDULED_STATUS,
|
||||
IMAGE_FAILED_DELETED_STATUS,
|
||||
SCHEDULED_STATUS,
|
||||
ROOT_IMAGE_TEXT,
|
||||
} from '~/registry/explorer/constants';
|
||||
import ClipboardButton from '~/vue_shared/components/clipboard_button.vue';
|
||||
@ -27,7 +26,7 @@ describe('Image List Row', () => {
|
||||
const findTagsCount = () => wrapper.find('[data-testid="tags-count"]');
|
||||
const findDeleteBtn = () => wrapper.findComponent(DeleteButton);
|
||||
const findClipboardButton = () => wrapper.findComponent(ClipboardButton);
|
||||
const findWarningIcon = () => wrapper.find('[data-testid="warning-icon"]');
|
||||
const findCleanupStatus = () => wrapper.findComponent(CleanupStatus);
|
||||
const findSkeletonLoader = () => wrapper.findComponent(GlSkeletonLoader);
|
||||
const findListItemComponent = () => wrapper.findComponent(ListItem);
|
||||
|
||||
@ -106,23 +105,22 @@ describe('Image List Row', () => {
|
||||
expect(button.props('title')).toBe(item.location);
|
||||
});
|
||||
|
||||
describe('warning icon', () => {
|
||||
describe('cleanup status component', () => {
|
||||
it.each`
|
||||
status | expirationPolicyStartedAt | shown | title
|
||||
${IMAGE_FAILED_DELETED_STATUS} | ${true} | ${true} | ${ASYNC_DELETE_IMAGE_ERROR_MESSAGE}
|
||||
${''} | ${true} | ${true} | ${CLEANUP_TIMED_OUT_ERROR_MESSAGE}
|
||||
${''} | ${false} | ${false} | ${''}
|
||||
expirationPolicyCleanupStatus | shown
|
||||
${null} | ${false}
|
||||
${SCHEDULED_STATUS} | ${true}
|
||||
`(
|
||||
'when status is $status and expirationPolicyStartedAt is $expirationPolicyStartedAt',
|
||||
({ expirationPolicyStartedAt, status, shown, title }) => {
|
||||
mountComponent({ item: { ...item, status, expirationPolicyStartedAt } });
|
||||
'when expirationPolicyCleanupStatus is $expirationPolicyCleanupStatus it is $shown that the component exists',
|
||||
({ expirationPolicyCleanupStatus, shown }) => {
|
||||
mountComponent({ item: { ...item, expirationPolicyCleanupStatus } });
|
||||
|
||||
const icon = findWarningIcon();
|
||||
expect(icon.exists()).toBe(shown);
|
||||
expect(findCleanupStatus().exists()).toBe(shown);
|
||||
|
||||
if (shown) {
|
||||
const tooltip = getBinding(icon.element, 'gl-tooltip');
|
||||
expect(tooltip.value.title).toBe(title);
|
||||
expect(findCleanupStatus().props()).toMatchObject({
|
||||
status: expirationPolicyCleanupStatus,
|
||||
});
|
||||
}
|
||||
},
|
||||
);
|
||||
|
@ -9,6 +9,7 @@ export const imagesListResponse = [
|
||||
canDelete: true,
|
||||
createdAt: '2020-11-03T13:29:21Z',
|
||||
expirationPolicyStartedAt: null,
|
||||
expirationPolicyCleanupStatus: 'UNSCHEDULED',
|
||||
},
|
||||
{
|
||||
__typename: 'ContainerRepository',
|
||||
@ -20,6 +21,7 @@ export const imagesListResponse = [
|
||||
canDelete: true,
|
||||
createdAt: '2020-09-21T06:57:43Z',
|
||||
expirationPolicyStartedAt: null,
|
||||
expirationPolicyCleanupStatus: 'UNSCHEDULED',
|
||||
},
|
||||
];
|
||||
|
||||
|
51
spec/frontend/runner/components/helpers/masked_value_spec.js
Normal file
51
spec/frontend/runner/components/helpers/masked_value_spec.js
Normal file
@ -0,0 +1,51 @@
|
||||
import { GlButton } from '@gitlab/ui';
|
||||
import { shallowMount } from '@vue/test-utils';
|
||||
import MaskedValue from '~/runner/components/helpers/masked_value.vue';
|
||||
|
||||
const mockSecret = '01234567890';
|
||||
const mockMasked = '***********';
|
||||
|
||||
describe('MaskedValue', () => {
|
||||
let wrapper;
|
||||
|
||||
const findButton = () => wrapper.findComponent(GlButton);
|
||||
|
||||
const createComponent = ({ props = {} } = {}) => {
|
||||
wrapper = shallowMount(MaskedValue, {
|
||||
propsData: {
|
||||
value: mockSecret,
|
||||
...props,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
createComponent();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
wrapper.destroy();
|
||||
});
|
||||
|
||||
it('Displays masked value by default', () => {
|
||||
expect(wrapper.text()).toBe(mockMasked);
|
||||
});
|
||||
|
||||
describe('When the icon is clicked', () => {
|
||||
beforeEach(() => {
|
||||
findButton().vm.$emit('click');
|
||||
});
|
||||
|
||||
it('Displays the actual value', () => {
|
||||
expect(wrapper.text()).toBe(mockSecret);
|
||||
expect(wrapper.text()).not.toBe(mockMasked);
|
||||
});
|
||||
|
||||
it('When user clicks again, displays masked value', async () => {
|
||||
await findButton().vm.$emit('click');
|
||||
|
||||
expect(wrapper.text()).toBe(mockMasked);
|
||||
expect(wrapper.text()).not.toBe(mockSecret);
|
||||
});
|
||||
});
|
||||
});
|
@ -3,6 +3,7 @@ import { shallowMount } from '@vue/test-utils';
|
||||
import { nextTick } from 'vue';
|
||||
import { TEST_HOST } from 'helpers/test_constants';
|
||||
import { extendedWrapper } from 'helpers/vue_test_utils_helper';
|
||||
import MaskedValue from '~/runner/components/helpers/masked_value.vue';
|
||||
import RunnerManualSetupHelp from '~/runner/components/runner_manual_setup_help.vue';
|
||||
import RunnerRegistrationTokenReset from '~/runner/components/runner_registration_token_reset.vue';
|
||||
import { INSTANCE_TYPE, GROUP_TYPE, PROJECT_TYPE } from '~/runner/constants';
|
||||
@ -37,6 +38,7 @@ describe('RunnerManualSetupHelp', () => {
|
||||
...props,
|
||||
},
|
||||
stubs: {
|
||||
MaskedValue,
|
||||
GlSprintf,
|
||||
},
|
||||
}),
|
||||
@ -93,7 +95,11 @@ describe('RunnerManualSetupHelp', () => {
|
||||
expect(findRunnerInstructions().exists()).toBe(true);
|
||||
});
|
||||
|
||||
it('Displays the registration token', () => {
|
||||
it('Displays the registration token', async () => {
|
||||
findRegistrationToken().find('[data-testid="toggle-masked"]').vm.$emit('click');
|
||||
|
||||
await nextTick();
|
||||
|
||||
expect(findRegistrationToken().text()).toBe(mockRegistrationToken);
|
||||
expect(findClipboardButtons().at(1).props('text')).toBe(mockRegistrationToken);
|
||||
});
|
||||
@ -105,6 +111,7 @@ describe('RunnerManualSetupHelp', () => {
|
||||
it('Replaces the runner reset button', async () => {
|
||||
const mockNewRegistrationToken = 'NEW_MOCK_REGISTRATION_TOKEN';
|
||||
|
||||
findRegistrationToken().find('[data-testid="toggle-masked"]').vm.$emit('click');
|
||||
findRunnerRegistrationTokenReset().vm.$emit('tokenReset', mockNewRegistrationToken);
|
||||
|
||||
await nextTick();
|
||||
|
@ -1,4 +1,3 @@
|
||||
/* eslint-disable no-unused-expressions, consistent-return, no-param-reassign, default-case, no-return-assign */
|
||||
import AxiosMockAdapter from 'axios-mock-adapter';
|
||||
import $ from 'jquery';
|
||||
import { mockTracking, unmockTracking } from 'helpers/tracking_helper';
|
||||
@ -22,31 +21,33 @@ describe('Search autocomplete dropdown', () => {
|
||||
const groupName = 'Gitlab Org';
|
||||
|
||||
const removeBodyAttributes = () => {
|
||||
const $body = $('body');
|
||||
const { body } = document;
|
||||
|
||||
$body.removeAttr('data-page');
|
||||
$body.removeAttr('data-project');
|
||||
$body.removeAttr('data-group');
|
||||
delete body.dataset.page;
|
||||
delete body.dataset.project;
|
||||
delete body.dataset.group;
|
||||
};
|
||||
|
||||
// Add required attributes to body before starting the test.
|
||||
// section would be dashboard|group|project
|
||||
const addBodyAttributes = (section) => {
|
||||
if (section == null) {
|
||||
section = 'dashboard';
|
||||
}
|
||||
|
||||
const $body = $('body');
|
||||
const addBodyAttributes = (section = 'dashboard') => {
|
||||
removeBodyAttributes();
|
||||
|
||||
const { body } = document;
|
||||
switch (section) {
|
||||
case 'dashboard':
|
||||
return $body.attr('data-page', 'root:index');
|
||||
body.dataset.page = 'root:index';
|
||||
break;
|
||||
case 'group':
|
||||
$body.attr('data-page', 'groups:show');
|
||||
return $body.data('group', 'gitlab-org');
|
||||
body.dataset.page = 'groups:show';
|
||||
body.dataset.group = 'gitlab-org';
|
||||
break;
|
||||
case 'project':
|
||||
$body.attr('data-page', 'projects:show');
|
||||
return $body.data('project', 'gitlab-ce');
|
||||
body.dataset.page = 'projects:show';
|
||||
body.dataset.project = 'gitlab-ce';
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
@ -56,34 +57,31 @@ describe('Search autocomplete dropdown', () => {
|
||||
|
||||
// Mock `gl` object in window for dashboard specific page. App code will need it.
|
||||
const mockDashboardOptions = () => {
|
||||
window.gl || (window.gl = {});
|
||||
return (window.gl.dashboardOptions = {
|
||||
window.gl.dashboardOptions = {
|
||||
issuesPath: dashboardIssuesPath,
|
||||
mrPath: dashboardMRsPath,
|
||||
});
|
||||
};
|
||||
};
|
||||
|
||||
// Mock `gl` object in window for project specific page. App code will need it.
|
||||
const mockProjectOptions = () => {
|
||||
window.gl || (window.gl = {});
|
||||
return (window.gl.projectOptions = {
|
||||
window.gl.projectOptions = {
|
||||
'gitlab-ce': {
|
||||
issuesPath: projectIssuesPath,
|
||||
mrPath: projectMRsPath,
|
||||
projectName,
|
||||
},
|
||||
});
|
||||
};
|
||||
};
|
||||
|
||||
const mockGroupOptions = () => {
|
||||
window.gl || (window.gl = {});
|
||||
return (window.gl.groupOptions = {
|
||||
window.gl.groupOptions = {
|
||||
'gitlab-org': {
|
||||
issuesPath: groupIssuesPath,
|
||||
mrPath: groupMRsPath,
|
||||
projectName: groupName,
|
||||
},
|
||||
});
|
||||
};
|
||||
};
|
||||
|
||||
const assertLinks = (list, issuesPath, mrsPath) => {
|
||||
@ -113,7 +111,7 @@ describe('Search autocomplete dropdown', () => {
|
||||
window.gon.current_username = userName;
|
||||
window.gl = window.gl || (window.gl = {});
|
||||
|
||||
return (widget = initSearchAutocomplete({ autocompletePath }));
|
||||
widget = initSearchAutocomplete({ autocompletePath });
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
|
114
spec/frontend/search_autocomplete_utils_spec.js
Normal file
114
spec/frontend/search_autocomplete_utils_spec.js
Normal file
@ -0,0 +1,114 @@
|
||||
import {
|
||||
isInGroupsPage,
|
||||
isInProjectPage,
|
||||
getGroupSlug,
|
||||
getProjectSlug,
|
||||
} from '~/search_autocomplete_utils';
|
||||
|
||||
describe('search_autocomplete_utils', () => {
|
||||
let originalBody;
|
||||
|
||||
beforeEach(() => {
|
||||
originalBody = document.body;
|
||||
document.body = document.createElement('body');
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
document.body = originalBody;
|
||||
});
|
||||
|
||||
describe('isInGroupsPage', () => {
|
||||
it.each`
|
||||
page | result
|
||||
${'groups:index'} | ${true}
|
||||
${'groups:show'} | ${true}
|
||||
${'projects:show'} | ${false}
|
||||
`(`returns $result in for page $page`, ({ page, result }) => {
|
||||
document.body.dataset.page = page;
|
||||
|
||||
expect(isInGroupsPage()).toBe(result);
|
||||
});
|
||||
});
|
||||
|
||||
describe('isInProjectPage', () => {
|
||||
it.each`
|
||||
page | result
|
||||
${'projects:index'} | ${true}
|
||||
${'projects:show'} | ${true}
|
||||
${'groups:show'} | ${false}
|
||||
`(`returns $result in for page $page`, ({ page, result }) => {
|
||||
document.body.dataset.page = page;
|
||||
|
||||
expect(isInProjectPage()).toBe(result);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getProjectSlug', () => {
|
||||
it('returns null when no project is present or on project page', () => {
|
||||
expect(getProjectSlug()).toBe(null);
|
||||
});
|
||||
|
||||
it('returns null when not on project page', () => {
|
||||
document.body.dataset.project = 'gitlab';
|
||||
|
||||
expect(getProjectSlug()).toBe(null);
|
||||
});
|
||||
|
||||
it('returns null when project is missing', () => {
|
||||
document.body.dataset.page = 'projects';
|
||||
|
||||
expect(getProjectSlug()).toBe(undefined);
|
||||
});
|
||||
|
||||
it('returns project', () => {
|
||||
document.body.dataset.page = 'projects';
|
||||
document.body.dataset.project = 'gitlab';
|
||||
|
||||
expect(getProjectSlug()).toBe('gitlab');
|
||||
});
|
||||
|
||||
it('returns project in edit page', () => {
|
||||
document.body.dataset.page = 'projects:edit';
|
||||
document.body.dataset.project = 'gitlab';
|
||||
|
||||
expect(getProjectSlug()).toBe('gitlab');
|
||||
});
|
||||
});
|
||||
|
||||
describe('getGroupSlug', () => {
|
||||
it('returns null when no group is present or on group page', () => {
|
||||
expect(getGroupSlug()).toBe(null);
|
||||
});
|
||||
|
||||
it('returns null when not on group page', () => {
|
||||
document.body.dataset.group = 'gitlab-org';
|
||||
|
||||
expect(getGroupSlug()).toBe(null);
|
||||
});
|
||||
|
||||
it('returns null when group is missing on groups page', () => {
|
||||
document.body.dataset.page = 'groups';
|
||||
|
||||
expect(getGroupSlug()).toBe(undefined);
|
||||
});
|
||||
|
||||
it('returns null when group is missing on project page', () => {
|
||||
document.body.dataset.page = 'project';
|
||||
|
||||
expect(getGroupSlug()).toBe(null);
|
||||
});
|
||||
|
||||
it.each`
|
||||
page
|
||||
${'groups'}
|
||||
${'groups:edit'}
|
||||
${'projects'}
|
||||
${'projects:edit'}
|
||||
`(`returns group in page $page`, ({ page }) => {
|
||||
document.body.dataset.page = page;
|
||||
document.body.dataset.group = 'gitlab-org';
|
||||
|
||||
expect(getGroupSlug()).toBe('gitlab-org');
|
||||
});
|
||||
});
|
||||
});
|
Reference in New Issue
Block a user