Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
b4b6bff01d
commit
60ea1fab8e
|
|
@ -5,6 +5,7 @@ import { handleLocationHash } from '../../lib/utils/common_utils';
|
|||
import axios from '../../lib/utils/axios_utils';
|
||||
import eventHub from '../../notes/event_hub';
|
||||
import { __ } from '~/locale';
|
||||
import { fixTitle } from '~/tooltips';
|
||||
|
||||
const loadRichBlobViewer = type => {
|
||||
switch (type) {
|
||||
|
|
@ -124,7 +125,7 @@ export default class BlobViewer {
|
|||
this.copySourceBtn.classList.add('disabled');
|
||||
}
|
||||
|
||||
$(this.copySourceBtn).tooltip('_fixTitle');
|
||||
fixTitle($(this.copySourceBtn));
|
||||
}
|
||||
|
||||
switchToViewer(name) {
|
||||
|
|
|
|||
|
|
@ -5,6 +5,14 @@ import getUnicodeSupportMap from './unicode_support_map';
|
|||
let browserUnicodeSupportMap;
|
||||
|
||||
export default function isEmojiUnicodeSupportedByBrowser(emojiUnicode, unicodeVersion) {
|
||||
// Skipping the map creation for Bots + RSPec
|
||||
if (
|
||||
navigator.userAgent.indexOf('HeadlessChrome') > -1 ||
|
||||
navigator.userAgent.indexOf('Lighthouse') > -1 ||
|
||||
navigator.userAgent.indexOf('Speedindex') > -1
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
browserUnicodeSupportMap = browserUnicodeSupportMap || getUnicodeSupportMap();
|
||||
return isEmojiUnicodeSupported(browserUnicodeSupportMap, emojiUnicode, unicodeVersion);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,8 @@
|
|||
<script>
|
||||
import { mapState, mapActions } from 'vuex';
|
||||
import { isEmpty } from 'lodash';
|
||||
import { GlButton, GlModalDirective, GlTabs } from '@gitlab/ui';
|
||||
import { GlAlert, GlButton, GlModalDirective, GlSprintf, GlTabs } from '@gitlab/ui';
|
||||
|
||||
import { FEATURE_FLAG_SCOPE, USER_LIST_SCOPE } from '../constants';
|
||||
import FeatureFlagsTab from './feature_flags_tab.vue';
|
||||
import FeatureFlagsTable from './feature_flags_table.vue';
|
||||
|
|
@ -9,9 +10,9 @@ import UserListsTable from './user_lists_table.vue';
|
|||
import { s__ } from '~/locale';
|
||||
import TablePagination from '~/vue_shared/components/pagination/table_pagination.vue';
|
||||
import {
|
||||
buildUrlWithCurrentLocation,
|
||||
getParameterByName,
|
||||
historyPushState,
|
||||
buildUrlWithCurrentLocation,
|
||||
} from '~/lib/utils/common_utils';
|
||||
|
||||
import ConfigureFeatureFlagsModal from './configure_feature_flags_modal.vue';
|
||||
|
|
@ -20,13 +21,15 @@ const SCOPES = { FEATURE_FLAG_SCOPE, USER_LIST_SCOPE };
|
|||
|
||||
export default {
|
||||
components: {
|
||||
FeatureFlagsTable,
|
||||
UserListsTable,
|
||||
TablePagination,
|
||||
GlButton,
|
||||
GlTabs,
|
||||
FeatureFlagsTab,
|
||||
ConfigureFeatureFlagsModal,
|
||||
FeatureFlagsTab,
|
||||
FeatureFlagsTable,
|
||||
GlAlert,
|
||||
GlButton,
|
||||
GlSprintf,
|
||||
GlTabs,
|
||||
TablePagination,
|
||||
UserListsTable,
|
||||
},
|
||||
directives: {
|
||||
GlModal: GlModalDirective,
|
||||
|
|
@ -44,6 +47,20 @@ export default {
|
|||
type: String,
|
||||
required: true,
|
||||
},
|
||||
featureFlagsLimit: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
featureFlagsLimitExceeded: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false,
|
||||
},
|
||||
rotateInstanceIdPath: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: '',
|
||||
},
|
||||
unleashApiUrl: {
|
||||
type: String,
|
||||
required: true,
|
||||
|
|
@ -69,6 +86,7 @@ export default {
|
|||
scope,
|
||||
page: getParameterByName('page') || '1',
|
||||
isUserListAlertDismissed: false,
|
||||
shouldShowFeatureFlagsLimitWarning: this.featureFlagsLimitExceeded,
|
||||
selectedTab: Object.values(SCOPES).indexOf(scope),
|
||||
};
|
||||
},
|
||||
|
|
@ -184,11 +202,36 @@ export default {
|
|||
dataForScope(scope) {
|
||||
return this[scope];
|
||||
},
|
||||
onDismissFeatureFlagsLimitWarning() {
|
||||
this.shouldShowFeatureFlagsLimitWarning = false;
|
||||
},
|
||||
onNewFeatureFlagCLick() {
|
||||
if (this.featureFlagsLimitExceeded) {
|
||||
this.shouldShowFeatureFlagsLimitWarning = true;
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<template>
|
||||
<div>
|
||||
<gl-alert
|
||||
v-if="shouldShowFeatureFlagsLimitWarning"
|
||||
variant="warning"
|
||||
@dismiss="onDismissFeatureFlagsLimitWarning"
|
||||
>
|
||||
<gl-sprintf
|
||||
:message="
|
||||
s__(
|
||||
'FeatureFlags|Feature flags limit reached (%{featureFlagsLimit}). Delete one or more feature flags before adding new ones.',
|
||||
)
|
||||
"
|
||||
>
|
||||
<template #featureFlagsLimit>
|
||||
<span>{{ featureFlagsLimit }}</span>
|
||||
</template>
|
||||
</gl-sprintf>
|
||||
</gl-alert>
|
||||
<configure-feature-flags-modal
|
||||
v-if="canUserConfigure"
|
||||
:help-client-libraries-path="featureFlagsClientLibrariesHelpPagePath"
|
||||
|
|
@ -228,9 +271,10 @@ export default {
|
|||
|
||||
<gl-button
|
||||
v-if="hasNewPath"
|
||||
:href="newFeatureFlagPath"
|
||||
:href="featureFlagsLimitExceeded ? '' : newFeatureFlagPath"
|
||||
variant="success"
|
||||
data-testid="ff-new-button"
|
||||
@click="onNewFeatureFlagCLick"
|
||||
>
|
||||
{{ s__('FeatureFlags|New feature flag') }}
|
||||
</gl-button>
|
||||
|
|
@ -306,9 +350,10 @@ export default {
|
|||
|
||||
<gl-button
|
||||
v-if="hasNewPath"
|
||||
:href="newFeatureFlagPath"
|
||||
:href="featureFlagsLimitExceeded ? '' : newFeatureFlagPath"
|
||||
variant="success"
|
||||
data-testid="ff-new-button"
|
||||
@click="onNewFeatureFlagCLick"
|
||||
>
|
||||
{{ s__('FeatureFlags|New feature flag') }}
|
||||
</gl-button>
|
||||
|
|
|
|||
|
|
@ -36,6 +36,8 @@ export default () => {
|
|||
el.dataset.featureFlagsClientLibrariesHelpPagePath,
|
||||
featureFlagsClientExampleHelpPagePath: el.dataset.featureFlagsClientExampleHelpPagePath,
|
||||
unleashApiUrl: el.dataset.unleashApiUrl,
|
||||
featureFlagsLimitExceeded: el.dataset.featureFlagsLimitExceeded,
|
||||
featureFlagsLimit: el.dataset.featureFlagsLimit,
|
||||
csrfToken: csrf.token,
|
||||
canUserConfigure: el.dataset.canUserAdminFeatureFlag,
|
||||
newFeatureFlagPath: el.dataset.newFeatureFlagPath,
|
||||
|
|
|
|||
|
|
@ -16,53 +16,63 @@ const frequentItemDropdowns = [
|
|||
},
|
||||
];
|
||||
|
||||
const initFrequentItemList = (namespace, key) => {
|
||||
const el = document.getElementById(`js-${namespace}-dropdown`);
|
||||
|
||||
// Don't do anything if element doesn't exist (No groups dropdown)
|
||||
// This is for when the user accesses GitLab without logging in
|
||||
if (!el) {
|
||||
return;
|
||||
}
|
||||
|
||||
import('./components/app.vue')
|
||||
.then(({ default: FrequentItems }) => {
|
||||
// eslint-disable-next-line no-new
|
||||
new Vue({
|
||||
el,
|
||||
data() {
|
||||
const { dataset } = this.$options.el;
|
||||
const item = {
|
||||
id: Number(dataset[`${key}Id`]),
|
||||
name: dataset[`${key}Name`],
|
||||
namespace: dataset[`${key}Namespace`],
|
||||
webUrl: dataset[`${key}WebUrl`],
|
||||
avatarUrl: dataset[`${key}AvatarUrl`] || null,
|
||||
lastAccessedOn: Date.now(),
|
||||
};
|
||||
|
||||
return {
|
||||
currentUserName: dataset.userName,
|
||||
currentItem: item,
|
||||
};
|
||||
},
|
||||
render(createElement) {
|
||||
return createElement(FrequentItems, {
|
||||
props: {
|
||||
namespace,
|
||||
currentUserName: this.currentUserName,
|
||||
currentItem: this.currentItem,
|
||||
},
|
||||
});
|
||||
},
|
||||
});
|
||||
})
|
||||
.catch(() => {});
|
||||
};
|
||||
|
||||
export default function initFrequentItemDropdowns() {
|
||||
frequentItemDropdowns.forEach(dropdown => {
|
||||
const { namespace, key } = dropdown;
|
||||
const el = document.getElementById(`js-${namespace}-dropdown`);
|
||||
const navEl = document.getElementById(`nav-${namespace}-dropdown`);
|
||||
|
||||
// Don't do anything if element doesn't exist (No groups dropdown)
|
||||
// This is for when the user accesses GitLab without logging in
|
||||
if (!el || !navEl) {
|
||||
if (!navEl) {
|
||||
return;
|
||||
}
|
||||
|
||||
import('./components/app.vue')
|
||||
.then(({ default: FrequentItems }) => {
|
||||
// eslint-disable-next-line no-new
|
||||
new Vue({
|
||||
el,
|
||||
data() {
|
||||
const { dataset } = this.$options.el;
|
||||
const item = {
|
||||
id: Number(dataset[`${key}Id`]),
|
||||
name: dataset[`${key}Name`],
|
||||
namespace: dataset[`${key}Namespace`],
|
||||
webUrl: dataset[`${key}WebUrl`],
|
||||
avatarUrl: dataset[`${key}AvatarUrl`] || null,
|
||||
lastAccessedOn: Date.now(),
|
||||
};
|
||||
|
||||
return {
|
||||
currentUserName: dataset.userName,
|
||||
currentItem: item,
|
||||
};
|
||||
},
|
||||
render(createElement) {
|
||||
return createElement(FrequentItems, {
|
||||
props: {
|
||||
namespace,
|
||||
currentUserName: this.currentUserName,
|
||||
currentItem: this.currentItem,
|
||||
},
|
||||
});
|
||||
},
|
||||
});
|
||||
})
|
||||
.catch(() => {});
|
||||
|
||||
$(navEl).on('shown.bs.dropdown', () => {
|
||||
initFrequentItemList(namespace, key);
|
||||
eventHub.$emit(`${namespace}-dropdownOpen`);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import { formatDate } from '~/lib/utils/datetime_utility';
|
|||
export default {
|
||||
components: {
|
||||
GlLink,
|
||||
IncidentSla: () => import('ee_component/issue_show/components/incidents/incident_sla.vue'),
|
||||
},
|
||||
directives: {
|
||||
GlTooltip: GlTooltipDirective,
|
||||
|
|
@ -12,36 +13,51 @@ export default {
|
|||
props: {
|
||||
alert: {
|
||||
type: Object,
|
||||
required: true,
|
||||
required: false,
|
||||
default: null,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return { childHasData: false };
|
||||
},
|
||||
computed: {
|
||||
startTime() {
|
||||
return formatDate(this.alert.startedAt, 'yyyy-mm-dd Z');
|
||||
},
|
||||
showHighlightBar() {
|
||||
return this.alert || this.childHasData;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
update(hasData) {
|
||||
this.childHasData = hasData;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
v-show="showHighlightBar"
|
||||
class="gl-border-solid gl-border-1 gl-border-gray-100 gl-p-5 gl-mb-3 gl-rounded-base gl-display-flex gl-justify-content-space-between gl-xs-flex-direction-column"
|
||||
>
|
||||
<div class="gl-pr-3">
|
||||
<div v-if="alert" class="gl-mr-3">
|
||||
<span class="gl-font-weight-bold">{{ s__('HighlightBar|Original alert:') }}</span>
|
||||
<gl-link v-gl-tooltip :title="alert.title" :href="alert.detailsUrl">
|
||||
#{{ alert.iid }}
|
||||
</gl-link>
|
||||
</div>
|
||||
|
||||
<div class="gl-pr-3">
|
||||
<div v-if="alert" class="gl-mr-3">
|
||||
<span class="gl-font-weight-bold">{{ s__('HighlightBar|Alert start time:') }}</span>
|
||||
{{ startTime }}
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div v-if="alert" class="gl-mr-3">
|
||||
<span class="gl-font-weight-bold">{{ s__('HighlightBar|Alert events:') }}</span>
|
||||
<span>{{ alert.eventCount }}</span>
|
||||
</div>
|
||||
|
||||
<incident-sla @update="update" />
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -53,7 +53,7 @@ export default {
|
|||
<div>
|
||||
<gl-tabs content-class="gl-reset-line-height" class="gl-mt-n3" data-testid="incident-tabs">
|
||||
<gl-tab :title="s__('Incident|Summary')">
|
||||
<highlight-bar v-if="alert" :alert="alert" />
|
||||
<highlight-bar :alert="alert" />
|
||||
<description-component v-bind="$attrs" />
|
||||
</gl-tab>
|
||||
<gl-tab v-if="alert" class="alert-management-details" :title="s__('Incident|Alert details')">
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import VueApollo from 'vue-apollo';
|
|||
import createDefaultClient from '~/lib/graphql';
|
||||
import issuableApp from './components/app.vue';
|
||||
import incidentTabs from './components/incidents/incident_tabs.vue';
|
||||
import { parseBoolean } from '~/lib/utils/common_utils';
|
||||
|
||||
Vue.use(VueApollo);
|
||||
|
||||
|
|
@ -11,7 +12,7 @@ export default function initIssuableApp(issuableData = {}) {
|
|||
defaultClient: createDefaultClient(),
|
||||
});
|
||||
|
||||
const { projectNamespace, projectPath, iid } = issuableData;
|
||||
const { iid, projectNamespace, projectPath, slaFeatureAvailable } = issuableData;
|
||||
|
||||
return new Vue({
|
||||
el: document.getElementById('js-issuable-app'),
|
||||
|
|
@ -22,6 +23,7 @@ export default function initIssuableApp(issuableData = {}) {
|
|||
provide: {
|
||||
fullPath: `${projectNamespace}/${projectPath}`,
|
||||
iid,
|
||||
slaFeatureAvailable: parseBoolean(slaFeatureAvailable),
|
||||
},
|
||||
render(createElement) {
|
||||
return createElement('issuable-app', {
|
||||
|
|
|
|||
|
|
@ -11,3 +11,5 @@ class IssuePresenter < Gitlab::View::Presenter::Delegated
|
|||
issue.subscribed?(current_user, issue.project)
|
||||
end
|
||||
end
|
||||
|
||||
IssuePresenter.prepend_if_ee('EE::IssuePresenter')
|
||||
|
|
|
|||
|
|
@ -7,6 +7,8 @@
|
|||
"feature-flags-help-page-path" => help_page_path("operations/feature_flags"),
|
||||
"feature-flags-client-libraries-help-page-path" => help_page_path("operations/feature_flags", anchor: "choose-a-client-library"),
|
||||
"feature-flags-client-example-help-page-path" => help_page_path("operations/feature_flags", anchor: "golang-application-example"),
|
||||
"feature-flags-limit-exceeded" => @project.actual_limits.exceeded?(:project_feature_flags, @project.operations_feature_flags.count),
|
||||
"feature-flags-limit" => @project.actual_limits.project_feature_flags,
|
||||
"unleash-api-url" => (unleash_api_url(@project) if can?(current_user, :admin_feature_flag, @project)),
|
||||
"unleash-api-instance-id" => (unleash_api_instance_id(@project) if can?(current_user, :admin_feature_flag, @project)),
|
||||
"can-user-admin-feature-flag" => can?(current_user, :admin_feature_flag, @project),
|
||||
|
|
|
|||
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Add Issuable Service Level Agreement (SLA) table
|
||||
merge_request: 44253
|
||||
author:
|
||||
type: added
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Feature Flags limits UX and documentation
|
||||
merge_request: 44089
|
||||
author:
|
||||
type: added
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class AddIssuableSlaTable < ActiveRecord::Migration[6.0]
|
||||
DOWNTIME = false
|
||||
|
||||
def change
|
||||
create_table :issuable_slas do |t|
|
||||
t.references :issue, null: false, index: { unique: true }, foreign_key: { on_delete: :cascade }
|
||||
t.datetime_with_timezone :due_at, null: false
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1 @@
|
|||
8a12c3c4f674d2a36df56a89bfd32e0f3945e73605460bdf2a8b0aa1308f5b19
|
||||
|
|
@ -12739,6 +12739,21 @@ CREATE SEQUENCE issuable_severities_id_seq
|
|||
|
||||
ALTER SEQUENCE issuable_severities_id_seq OWNED BY issuable_severities.id;
|
||||
|
||||
CREATE TABLE issuable_slas (
|
||||
id bigint NOT NULL,
|
||||
issue_id bigint NOT NULL,
|
||||
due_at timestamp with time zone NOT NULL
|
||||
);
|
||||
|
||||
CREATE SEQUENCE issuable_slas_id_seq
|
||||
START WITH 1
|
||||
INCREMENT BY 1
|
||||
NO MINVALUE
|
||||
NO MAXVALUE
|
||||
CACHE 1;
|
||||
|
||||
ALTER SEQUENCE issuable_slas_id_seq OWNED BY issuable_slas.id;
|
||||
|
||||
CREATE TABLE issue_assignees (
|
||||
user_id integer NOT NULL,
|
||||
issue_id integer NOT NULL
|
||||
|
|
@ -17561,6 +17576,8 @@ ALTER TABLE ONLY ip_restrictions ALTER COLUMN id SET DEFAULT nextval('ip_restric
|
|||
|
||||
ALTER TABLE ONLY issuable_severities ALTER COLUMN id SET DEFAULT nextval('issuable_severities_id_seq'::regclass);
|
||||
|
||||
ALTER TABLE ONLY issuable_slas ALTER COLUMN id SET DEFAULT nextval('issuable_slas_id_seq'::regclass);
|
||||
|
||||
ALTER TABLE ONLY issue_email_participants ALTER COLUMN id SET DEFAULT nextval('issue_email_participants_id_seq'::regclass);
|
||||
|
||||
ALTER TABLE ONLY issue_links ALTER COLUMN id SET DEFAULT nextval('issue_links_id_seq'::regclass);
|
||||
|
|
@ -18689,6 +18706,9 @@ ALTER TABLE ONLY ip_restrictions
|
|||
ALTER TABLE ONLY issuable_severities
|
||||
ADD CONSTRAINT issuable_severities_pkey PRIMARY KEY (id);
|
||||
|
||||
ALTER TABLE ONLY issuable_slas
|
||||
ADD CONSTRAINT issuable_slas_pkey PRIMARY KEY (id);
|
||||
|
||||
ALTER TABLE ONLY issue_email_participants
|
||||
ADD CONSTRAINT issue_email_participants_pkey PRIMARY KEY (id);
|
||||
|
||||
|
|
@ -20485,6 +20505,8 @@ CREATE INDEX index_ip_restrictions_on_group_id ON ip_restrictions USING btree (g
|
|||
|
||||
CREATE UNIQUE INDEX index_issuable_severities_on_issue_id ON issuable_severities USING btree (issue_id);
|
||||
|
||||
CREATE UNIQUE INDEX index_issuable_slas_on_issue_id ON issuable_slas USING btree (issue_id);
|
||||
|
||||
CREATE UNIQUE INDEX index_issue_assignees_on_issue_id_and_user_id ON issue_assignees USING btree (issue_id, user_id);
|
||||
|
||||
CREATE INDEX index_issue_assignees_on_user_id ON issue_assignees USING btree (user_id);
|
||||
|
|
@ -20541,8 +20563,6 @@ CREATE INDEX index_issues_on_updated_at ON issues USING btree (updated_at);
|
|||
|
||||
CREATE INDEX index_issues_on_updated_by_id ON issues USING btree (updated_by_id) WHERE (updated_by_id IS NOT NULL);
|
||||
|
||||
CREATE INDEX index_issues_project_id_issue_type_incident ON issues USING btree (project_id) WHERE (issue_type = 1);
|
||||
|
||||
CREATE UNIQUE INDEX index_jira_connect_installations_on_client_key ON jira_connect_installations USING btree (client_key);
|
||||
|
||||
CREATE INDEX index_jira_connect_subscriptions_on_namespace_id ON jira_connect_subscriptions USING btree (namespace_id);
|
||||
|
|
@ -22852,6 +22872,9 @@ ALTER TABLE ONLY gpg_signatures
|
|||
ALTER TABLE ONLY vulnerability_user_mentions
|
||||
ADD CONSTRAINT fk_rails_1a41c485cd FOREIGN KEY (vulnerability_id) REFERENCES vulnerabilities(id) ON DELETE CASCADE;
|
||||
|
||||
ALTER TABLE ONLY issuable_slas
|
||||
ADD CONSTRAINT fk_rails_1b8768cd63 FOREIGN KEY (issue_id) REFERENCES issues(id) ON DELETE CASCADE;
|
||||
|
||||
ALTER TABLE ONLY board_assignees
|
||||
ADD CONSTRAINT fk_rails_1c0ff59e82 FOREIGN KEY (assignee_id) REFERENCES users(id) ON DELETE CASCADE;
|
||||
|
||||
|
|
|
|||
|
|
@ -84,6 +84,7 @@ Learn how to install, configure, update, and maintain your GitLab instance.
|
|||
- [Operations](operations/index.md): Keeping GitLab up and running (clean up Redis sessions, moving repositories, Sidekiq MemoryKiller, Puma).
|
||||
- [Restart GitLab](restart_gitlab.md): Learn how to restart GitLab and its components.
|
||||
- [Invalidate Markdown cache](invalidate_markdown_cache.md): Invalidate any cached Markdown.
|
||||
- [Instance review](instance_review.md): Request a free review of your GitLab instance.
|
||||
|
||||
#### Updating GitLab
|
||||
|
||||
|
|
|
|||
|
|
@ -528,10 +528,14 @@ More information can be found in the [Push event activities limit and bulk push
|
|||
|
||||
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/218017) in GitLab 13.4.
|
||||
|
||||
On GitLab.com, the maximum file size for a package that's uploaded to the [GitLab Package Registry](../user/packages/package_registry/index.md)
|
||||
is 5 gigabytes.
|
||||
On GitLab.com, the maximum file size for a package that's uploaded to the [GitLab Package Registry](../user/packages/package_registry/index.md) varies by format:
|
||||
|
||||
Limits are set per package type.
|
||||
- Conan: 3GB
|
||||
- Generic: 5GB
|
||||
- Maven: 3GB
|
||||
- NPM: 500MB
|
||||
- NuGet: 500MB
|
||||
- PyPI: 3GB
|
||||
|
||||
To set this limit on a self-managed installation, run the following in the
|
||||
[GitLab Rails console](troubleshooting/debug.md#starting-a-rails-console-session):
|
||||
|
|
|
|||
|
|
@ -1,13 +1,25 @@
|
|||
---
|
||||
stage: Growth
|
||||
group: Conversion
|
||||
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#designated-technical-writers
|
||||
---
|
||||
|
||||
# Instance Review **(CORE ONLY)**
|
||||
|
||||
> [Introduced](https://gitlab.com/gitlab-org/gitlab-foss/-/merge_requests/6995) in [GitLab Core](https://about.gitlab.com/pricing/) 11.3.
|
||||
|
||||
If you are running a medium size instance (50+ users) of GitLab Core edition you are qualified for a free Instance Review. You can find the button in the User menu.
|
||||
If you are running a medium size instance (50+ users) of
|
||||
[GitLab Core](https://about.gitlab.com/pricing/) edition, you are qualified for a
|
||||
free Instance Review.
|
||||
|
||||

|
||||
1. Sign in as a user with Admin [permissions](../user/permissions.md).
|
||||
1. In the top menu, click your user icon, and select
|
||||
**Get a free instance review**:
|
||||
|
||||
When you click the button you will be redirected to a form with prefilled data obtained from your instance.
|
||||

|
||||
|
||||
Once you submit the data to GitLab Inc. you can see the initial report.
|
||||
1. GitLab redirects you to a form with prefilled data obtained from your instance.
|
||||
1. Click **Submit** to see the initial report.
|
||||
|
||||
Additionally you will be contacted by our team for further review which should help you to improve your usage of GitLab.
|
||||
A GitLab team member will contact you for further review, to provide suggestions
|
||||
that will help you improve your usage of GitLab.
|
||||
|
|
|
|||
|
|
@ -23,8 +23,8 @@ GET /projects/:id/registry/repositories
|
|||
| Attribute | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) accessible by the authenticated user. |
|
||||
| `tags` | boolean | no | If the parameter is included as true, each repository will include an array of `"tags"` in the response. |
|
||||
| `tags_count` | boolean | no | If the parameter is included as true, each repository will include `"tags_count"` in the response ([Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/32141) in GitLab 13.1). |
|
||||
| `tags` | boolean | no | If the parameter is included as true, each repository includes an array of `"tags"` in the response. |
|
||||
| `tags_count` | boolean | no | If the parameter is included as true, each repository includes `"tags_count"` in the response ([Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/32141) in GitLab 13.1). |
|
||||
|
||||
```shell
|
||||
curl --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/5/registry/repositories"
|
||||
|
|
@ -66,8 +66,8 @@ GET /groups/:id/registry/repositories
|
|||
| Attribute | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `id` | integer/string | yes | The ID or [URL-encoded path of the group](README.md#namespaced-path-encoding) accessible by the authenticated user. |
|
||||
| `tags` | boolean | no | If the parameter is included as true, each repository will include an array of `"tags"` in the response. |
|
||||
| `tags_count` | boolean | no | If the parameter is included as true, each repository will include `"tags_count"` in the response ([Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/32141) in GitLab 13.1). |
|
||||
| `tags` | boolean | no | If the parameter is included as true, each repository includes an array of `"tags"` in the response. |
|
||||
| `tags_count` | boolean | no | If the parameter is included as true, each repository includes `"tags_count"` in the response ([Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/32141) in GitLab 13.1). |
|
||||
|
||||
```shell
|
||||
curl --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/groups/2/registry/repositories?tags=1&tags_count=true"
|
||||
|
|
@ -250,7 +250,7 @@ DELETE /projects/:id/registry/repositories/:repository_id/tags
|
|||
| `repository_id` | integer | yes | The ID of registry repository. |
|
||||
| `name_regex` | string | no | The [re2](https://github.com/google/re2/wiki/Syntax) regex of the name to delete. To delete all tags specify `.*`. **Note:** `name_regex` is deprecated in favor of `name_regex_delete`. This field is validated. |
|
||||
| `name_regex_delete` | string | yes | The [re2](https://github.com/google/re2/wiki/Syntax) regex of the name to delete. To delete all tags specify `.*`. This field is validated. |
|
||||
| `name_regex_keep` | string | no | The [re2](https://github.com/google/re2/wiki/Syntax) regex of the name to keep. This value will override any matches from `name_regex_delete`. This field is validated. Note: setting to `.*` will result in a no-op. |
|
||||
| `name_regex_keep` | string | no | The [re2](https://github.com/google/re2/wiki/Syntax) regex of the name to keep. This value overrides any matches from `name_regex_delete`. This field is validated. Note: setting to `.*` results in a no-op. |
|
||||
| `keep_n` | integer | no | The amount of latest tags of given name to keep. |
|
||||
| `older_than` | string | no | Tags to delete that are older than the given time, written in human readable form `1h`, `1d`, `1month`. |
|
||||
|
||||
|
|
|
|||
|
|
@ -6725,6 +6725,11 @@ type EpicIssue implements CurrentUserTodos & Noteable {
|
|||
"""
|
||||
severity: IssuableSeverity
|
||||
|
||||
"""
|
||||
Timestamp of when the issue SLA expires. Returns null if `incident_sla_dev` feature flag is disabled.
|
||||
"""
|
||||
slaDueAt: Time
|
||||
|
||||
"""
|
||||
State of the issue
|
||||
"""
|
||||
|
|
@ -8880,6 +8885,11 @@ type Issue implements CurrentUserTodos & Noteable {
|
|||
"""
|
||||
severity: IssuableSeverity
|
||||
|
||||
"""
|
||||
Timestamp of when the issue SLA expires. Returns null if `incident_sla_dev` feature flag is disabled.
|
||||
"""
|
||||
slaDueAt: Time
|
||||
|
||||
"""
|
||||
State of the issue
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -18532,6 +18532,20 @@
|
|||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "slaDueAt",
|
||||
"description": "Timestamp of when the issue SLA expires. Returns null if `incident_sla_dev` feature flag is disabled.",
|
||||
"args": [
|
||||
|
||||
],
|
||||
"type": {
|
||||
"kind": "SCALAR",
|
||||
"name": "Time",
|
||||
"ofType": null
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "state",
|
||||
"description": "State of the issue",
|
||||
|
|
@ -24220,6 +24234,20 @@
|
|||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "slaDueAt",
|
||||
"description": "Timestamp of when the issue SLA expires. Returns null if `incident_sla_dev` feature flag is disabled.",
|
||||
"args": [
|
||||
|
||||
],
|
||||
"type": {
|
||||
"kind": "SCALAR",
|
||||
"name": "Time",
|
||||
"ofType": null
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "state",
|
||||
"description": "State of the issue",
|
||||
|
|
|
|||
|
|
@ -1066,6 +1066,7 @@ Relationship between an epic and an issue.
|
|||
| `relationPath` | String | URI path of the epic-issue relation |
|
||||
| `relativePosition` | Int | Relative position of the issue (used for positioning in epic tree and issue boards) |
|
||||
| `severity` | IssuableSeverity | Severity level of the incident |
|
||||
| `slaDueAt` | Time | Timestamp of when the issue SLA expires. Returns null if `incident_sla_dev` feature flag is disabled. |
|
||||
| `state` | IssueState! | State of the issue |
|
||||
| `statusPagePublishedIncident` | Boolean | Indicates whether an issue is published to the status page |
|
||||
| `subscribed` | Boolean! | Indicates the currently logged in user is subscribed to the issue |
|
||||
|
|
@ -1256,6 +1257,7 @@ Represents a recorded measurement (object count) for the Admins.
|
|||
| `reference` | String! | Internal reference of the issue. Returned in shortened format by default |
|
||||
| `relativePosition` | Int | Relative position of the issue (used for positioning in epic tree and issue boards) |
|
||||
| `severity` | IssuableSeverity | Severity level of the incident |
|
||||
| `slaDueAt` | Time | Timestamp of when the issue SLA expires. Returns null if `incident_sla_dev` feature flag is disabled. |
|
||||
| `state` | IssueState! | State of the issue |
|
||||
| `statusPagePublishedIncident` | Boolean | Indicates whether an issue is published to the status page |
|
||||
| `subscribed` | Boolean! | Indicates the currently logged in user is subscribed to the issue |
|
||||
|
|
|
|||
|
|
@ -56,6 +56,20 @@ To create and enable a feature flag:
|
|||
You can change these settings by clicking the **{pencil}** (edit) button
|
||||
next to any feature flag in the list.
|
||||
|
||||
## Maximum number of feature flags
|
||||
|
||||
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/254379) in GitLab 13.5.
|
||||
|
||||
The maximum number of feature flags per project on self-managed GitLab instances
|
||||
is 200. On GitLab.com, the maximum number is determined by [GitLab.com tier](https://about.gitlab.com/pricing/):
|
||||
|
||||
| Tier | Number of feature flags per project |
|
||||
|----------|-------------------------------------|
|
||||
| Free | 50 |
|
||||
| Bronze | 100 |
|
||||
| Silver | 150 |
|
||||
| Gold | 200 |
|
||||
|
||||
## Feature flag strategies
|
||||
|
||||
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/35555) in GitLab 13.0.
|
||||
|
|
|
|||
|
|
@ -253,20 +253,24 @@ production: &base
|
|||
|
||||
## Upgrade your subscription tier
|
||||
|
||||
To upgrade your [GitLab tier](https://about.gitlab.com/pricing/), contact our sales team as this
|
||||
can't be done in the Customers Portal. You can either send an email to `renewals@gitlab.com`, or
|
||||
complete the [**Contact Sales**](https://about.gitlab.com/sales/) form. Include details of which subscription you want to upgrade and the desired tier in your message.
|
||||
To upgrade your [GitLab tier](https://about.gitlab.com/pricing/):
|
||||
|
||||
After messaging the sales team, the workflow is as follows:
|
||||
1. Log in to the [Customers Portal](https://customers.gitlab.com/customers/sign_in).
|
||||
1. Select the **Upgrade** button on the relevant subscription card on the
|
||||
[Manage purchases](https://customers.gitlab.com/subscriptions) page.
|
||||
1. Select the desired upgrade.
|
||||
1. Confirm the active form of payment, or add a new form of payment.
|
||||
1. Select the **I accept the Privacy Policy and Terms of Service** checkbox.
|
||||
1. Select **Purchase**.
|
||||
|
||||
1. Receive a reply from the sales team, asking for confirmation of the upgrade.
|
||||
1. Reply to the sales team, confirming details of the upgrade.
|
||||
1. Receive a quote from the sales team.
|
||||
1. Sign and return the quote.
|
||||
1. Receive the new license.
|
||||
1. Upload the new license. For details, see [Uploading your license](../../user/admin_area/license.md#uploading-your-license).
|
||||
The following is emailed to you:
|
||||
|
||||
The new subscription tier is active when the license file is uploaded.
|
||||
- A payment receipt. You can also access this information in the Customers Portal under
|
||||
[**View invoices**](https://customers.gitlab.com/receipts).
|
||||
- A new license.
|
||||
|
||||
[Upload the new license](../../user/admin_area/license.md#uploading-your-license) to your instance.
|
||||
The new tier takes effect when the new license is uploaded.
|
||||
|
||||
## Subscription expiry
|
||||
|
||||
|
|
|
|||
|
|
@ -223,6 +223,14 @@ in an offline environment if you prefer using only locally available Docker imag
|
|||
recommend keeping the pull policy setting to `always` if not in an offline environment, as this
|
||||
enables the use of updated scanners in your CI/CD pipelines.
|
||||
|
||||
##### Support for Custom Certificate Authorities
|
||||
|
||||
Support for custom certificate authorities was introduced in the following versions:
|
||||
|
||||
| Analyzer | Version |
|
||||
| -------- | ------- |
|
||||
| `klar` | [v2.3.0](https://gitlab.com/gitlab-org/security-products/analyzers/klar/-/releases/v2.3.0) |
|
||||
|
||||
#### Make GitLab container scanning analyzer images available inside your Docker registry
|
||||
|
||||
For container scanning, import the following default images from `registry.gitlab.com` into your
|
||||
|
|
|
|||
|
|
@ -392,6 +392,18 @@ For details on saving and transporting Docker images as a file, see Docker's doc
|
|||
[`docker save`](https://docs.docker.com/engine/reference/commandline/save/), [`docker load`](https://docs.docker.com/engine/reference/commandline/load/),
|
||||
[`docker export`](https://docs.docker.com/engine/reference/commandline/export/), and [`docker import`](https://docs.docker.com/engine/reference/commandline/import/).
|
||||
|
||||
#### Support for Custom Certificate Authorities
|
||||
|
||||
Support for custom certificate authorities was introduced in the following versions.
|
||||
|
||||
| Analyzer | Version |
|
||||
| -------- | ------- |
|
||||
| `gemnasium` | [v2.8.0](https://gitlab.com/gitlab-org/security-products/analyzers/gemnasium/-/releases/v2.8.0) |
|
||||
| `gemnasium-maven` | [v2.9.0](https://gitlab.com/gitlab-org/security-products/analyzers/gemnasium-maven/-/releases/v2.9.0) |
|
||||
| `gemnasium-python` | [v2.7.0](https://gitlab.com/gitlab-org/security-products/analyzers/gemnasium-python/-/releases/v2.7.0) |
|
||||
| `retire.js` | [v2.4.0](https://gitlab.com/gitlab-org/security-products/analyzers/retire.js/-/releases/v2.4.0) |
|
||||
| `bundler-audit` | [v2.4.0](https://gitlab.com/gitlab-org/security-products/analyzers/bundler-audit/-/releases/v2.4.0) |
|
||||
|
||||
### Set dependency scanning CI job variables to use local dependency scanning analyzers
|
||||
|
||||
Add the following configuration to your `.gitlab-ci.yml` file. You must change the value of
|
||||
|
|
|
|||
|
|
@ -519,6 +519,25 @@ For details on saving and transporting Docker images as a file, see Docker's doc
|
|||
[`docker save`](https://docs.docker.com/engine/reference/commandline/save/), [`docker load`](https://docs.docker.com/engine/reference/commandline/load/),
|
||||
[`docker export`](https://docs.docker.com/engine/reference/commandline/export/), and [`docker import`](https://docs.docker.com/engine/reference/commandline/import/).
|
||||
|
||||
#### If support for Custom Certificate Authorities are needed
|
||||
|
||||
Support for custom certificate authorities was introduced in the following versions.
|
||||
|
||||
| Analyzer | Version |
|
||||
| -------- | ------- |
|
||||
| `bandit` | [v2.3.0](https://gitlab.com/gitlab-org/security-products/analyzers/bandit/-/releases/v2.3.0) |
|
||||
| `brakeman` | [v2.1.0](https://gitlab.com/gitlab-org/security-products/analyzers/brakeman/-/releases/v2.1.0) |
|
||||
| `eslint` | [v2.9.2](https://gitlab.com/gitlab-org/security-products/analyzers/eslint/-/releases/v2.9.2) |
|
||||
| `flawfinder` | [v2.3.0](https://gitlab.com/gitlab-org/security-products/analyzers/flawfinder/-/releases/v2.3.0) |
|
||||
| `gosec` | [v2.5.0](https://gitlab.com/gitlab-org/security-products/analyzers/gosec/-/releases/v2.5.0) |
|
||||
| `kubesec` | [v2.1.0](https://gitlab.com/gitlab-org/security-products/analyzers/kubesec/-/releases/v2.1.0) |
|
||||
| `nodejs-scan` | [v2.9.5](https://gitlab.com/gitlab-org/security-products/analyzers/nodejs-scan/-/releases/v2.9.5) |
|
||||
| `phpcs-security-audit` | [v2.8.2](https://gitlab.com/gitlab-org/security-products/analyzers/phpcs-security-audit/-/releases/v2.8.2) |
|
||||
| `pmd-apex` | [v2.1.0](https://gitlab.com/gitlab-org/security-products/analyzers/pmd-apex/-/releases/v2.1.0) |
|
||||
| `security-code-scan` | [v2.7.3](https://gitlab.com/gitlab-org/security-products/analyzers/security-code-scan/-/releases/v2.7.3) |
|
||||
| `sobelow` | [v2.2.0](https://gitlab.com/gitlab-org/security-products/analyzers/sobelow/-/releases/v2.2.0) |
|
||||
| `spotbugs` | [v2.7.1](https://gitlab.com/gitlab-org/security-products/analyzers/spotbugs/-/releases/v2.7.1) |
|
||||
|
||||
### Set SAST CI job variables to use local SAST analyzers
|
||||
|
||||
Add the following configuration to your `.gitlab-ci.yml` file. You must replace
|
||||
|
|
|
|||
|
|
@ -173,6 +173,32 @@ We have created a [short video walkthrough](https://youtu.be/wDtc_K00Y0A) showca
|
|||
<iframe src="https://www.youtube.com/embed/wDtc_K00Y0A" frameborder="0" allowfullscreen="true"> </iframe>
|
||||
</figure>
|
||||
|
||||
### Make GitLab Secret Detection analyzer image available inside your Docker registry
|
||||
|
||||
Import the following default Secret Detection analyzer images from `registry.gitlab.com` into your
|
||||
[local Docker container registry](../../packages/container_registry/index.md):
|
||||
|
||||
```plaintext
|
||||
registry.gitlab.com/gitlab-org/security-products/analyzers/secrets:3
|
||||
```
|
||||
|
||||
The process for importing Docker images into a local offline Docker registry depends on
|
||||
**your network security policy**. Please consult your IT staff to find an accepted and approved
|
||||
process by which external resources can be imported or temporarily accessed. Note that these scanners are [updated periodically](../index.md#maintenance-and-update-of-the-vulnerabilities-database)
|
||||
with new definitions, so consider if you're able to make periodic updates yourself.
|
||||
|
||||
For details on saving and transporting Docker images as a file, see Docker's documentation on
|
||||
[`docker save`](https://docs.docker.com/engine/reference/commandline/save/), [`docker load`](https://docs.docker.com/engine/reference/commandline/load/),
|
||||
[`docker export`](https://docs.docker.com/engine/reference/commandline/export/), and [`docker import`](https://docs.docker.com/engine/reference/commandline/import/).
|
||||
|
||||
#### If support for Custom Certificate Authorities are needed
|
||||
|
||||
Support for custom certificate authorities was introduced in the following versions.
|
||||
|
||||
| Analyzer | Version |
|
||||
| -------- | ------- |
|
||||
| secrets | [v3.0.0](https://gitlab.com/gitlab-org/security-products/analyzers/secrets/-/releases/v3.0.0) |
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Getting warning message `gl-secret-detection-report.json: no matching files`
|
||||
|
|
|
|||
|
|
@ -154,7 +154,7 @@ To add the GitLab NuGet Repository as a source for .NET, create a file named `nu
|
|||
|
||||
When uploading packages, note that:
|
||||
|
||||
- The maximum allowed size is 50 Megabytes.
|
||||
- The Package Registry on GitLab.com can store up to 500 MB of content. This limit is [configurable for self-managed GitLab instances](../../../administration/instance_limits.md#package-registry-limits).
|
||||
- If you upload the same package with the same version multiple times, each consecutive upload
|
||||
is saved as a separate file. When installing a package, GitLab serves the most recent file.
|
||||
- When uploading packages to GitLab, they are not displayed in the packages UI of your project
|
||||
|
|
|
|||
|
|
@ -403,9 +403,15 @@ ee:
|
|||
- issues:
|
||||
- epic_issue:
|
||||
- :epic
|
||||
- :issuable_sla
|
||||
- protected_branches:
|
||||
- :unprotect_access_levels
|
||||
- protected_environments:
|
||||
- :deploy_access_levels
|
||||
- :service_desk_setting
|
||||
- :security_setting
|
||||
|
||||
included_attributes:
|
||||
issuable_sla:
|
||||
- :issue
|
||||
- :due_at
|
||||
|
|
|
|||
|
|
@ -11152,6 +11152,9 @@ msgstr ""
|
|||
msgid "FeatureFlags|Feature flags allow you to configure your code into different flavors by dynamically toggling certain functionality."
|
||||
msgstr ""
|
||||
|
||||
msgid "FeatureFlags|Feature flags limit reached (%{featureFlagsLimit}). Delete one or more feature flags before adding new ones."
|
||||
msgstr ""
|
||||
|
||||
msgid "FeatureFlags|Flag becomes read only soon"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -13280,6 +13283,9 @@ msgstr ""
|
|||
msgid "HighlightBar|Original alert:"
|
||||
msgstr ""
|
||||
|
||||
msgid "HighlightBar|Time to SLA:"
|
||||
msgstr ""
|
||||
|
||||
msgid "History"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -13836,6 +13842,9 @@ msgstr ""
|
|||
msgid "Incident|There was an issue loading alert data. Please try again."
|
||||
msgstr ""
|
||||
|
||||
msgid "Incident|There was an issue loading incident data. Please try again."
|
||||
msgstr ""
|
||||
|
||||
msgid "Include a Terms of Service agreement and Privacy Policy that all users must accept."
|
||||
msgstr ""
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { shallowMount, createLocalVue } from '@vue/test-utils';
|
||||
import Vuex from 'vuex';
|
||||
import MockAdapter from 'axios-mock-adapter';
|
||||
import { GlEmptyState, GlLoadingIcon } from '@gitlab/ui';
|
||||
import { GlAlert, GlEmptyState, GlLoadingIcon, GlSprintf } from '@gitlab/ui';
|
||||
import { TEST_HOST } from 'spec/test_constants';
|
||||
import Api from '~/api';
|
||||
import createStore from '~/feature_flags/store/index';
|
||||
|
|
@ -20,14 +20,17 @@ localVue.use(Vuex);
|
|||
|
||||
describe('Feature flags', () => {
|
||||
const mockData = {
|
||||
csrfToken: 'testToken',
|
||||
featureFlagsClientLibrariesHelpPagePath: '/help/feature-flags#unleash-clients',
|
||||
featureFlagsClientExampleHelpPagePath: '/help/feature-flags#client-example',
|
||||
unleashApiUrl: `${TEST_HOST}/api/unleash`,
|
||||
canUserConfigure: true,
|
||||
canUserRotateToken: true,
|
||||
// canUserRotateToken: true,
|
||||
csrfToken: 'testToken',
|
||||
featureFlagsClientExampleHelpPagePath: '/help/feature-flags#client-example',
|
||||
featureFlagsClientLibrariesHelpPagePath: '/help/feature-flags#unleash-clients',
|
||||
featureFlagsHelpPagePath: '/help/feature-flags',
|
||||
featureFlagsLimit: '200',
|
||||
featureFlagsLimitExceeded: false,
|
||||
newFeatureFlagPath: 'feature-flags/new',
|
||||
newUserListPath: '/user-list/new',
|
||||
unleashApiUrl: `${TEST_HOST}/api/unleash`,
|
||||
};
|
||||
|
||||
const mockState = {
|
||||
|
|
@ -60,6 +63,7 @@ describe('Feature flags', () => {
|
|||
const configureButton = () => wrapper.find('[data-testid="ff-configure-button"]');
|
||||
const newButton = () => wrapper.find('[data-testid="ff-new-button"]');
|
||||
const newUserListButton = () => wrapper.find('[data-testid="ff-new-list-button"]');
|
||||
const limitAlert = () => wrapper.find(GlAlert);
|
||||
|
||||
beforeEach(() => {
|
||||
mock = new MockAdapter(axios);
|
||||
|
|
@ -82,28 +86,64 @@ describe('Feature flags', () => {
|
|||
wrapper = null;
|
||||
});
|
||||
|
||||
describe('when limit exceeded', () => {
|
||||
const propsData = { ...mockData, featureFlagsLimitExceeded: true };
|
||||
|
||||
beforeEach(done => {
|
||||
mock
|
||||
.onGet(`${TEST_HOST}/endpoint.json`, { params: { scope: FEATURE_FLAG_SCOPE, page: '1' } })
|
||||
.reply(200, getRequestData, {});
|
||||
factory(propsData);
|
||||
setImmediate(done);
|
||||
});
|
||||
|
||||
it('makes the new feature flag button do nothing if clicked', () => {
|
||||
expect(newButton().exists()).toBe(true);
|
||||
expect(newButton().props('disabled')).toBe(false);
|
||||
expect(newButton().props('href')).toBe(undefined);
|
||||
});
|
||||
|
||||
it('shows a feature flags limit reached alert', () => {
|
||||
expect(limitAlert().exists()).toBe(true);
|
||||
expect(
|
||||
limitAlert()
|
||||
.find(GlSprintf)
|
||||
.attributes('message'),
|
||||
).toContain('Feature flags limit reached');
|
||||
});
|
||||
|
||||
describe('when the alert is dismissed', () => {
|
||||
beforeEach(async () => {
|
||||
await limitAlert().vm.$emit('dismiss');
|
||||
});
|
||||
|
||||
it('hides the alert', async () => {
|
||||
expect(limitAlert().exists()).toBe(false);
|
||||
});
|
||||
|
||||
it('re-shows the alert if the new feature flag button is clicked', async () => {
|
||||
await newButton().vm.$emit('click');
|
||||
|
||||
expect(limitAlert().exists()).toBe(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('without permissions', () => {
|
||||
const propsData = {
|
||||
csrfToken: 'testToken',
|
||||
errorStateSvgPath: '/assets/illustrations/feature_flag.svg',
|
||||
featureFlagsHelpPagePath: '/help/feature-flags',
|
||||
...mockData,
|
||||
canUserConfigure: false,
|
||||
canUserRotateToken: false,
|
||||
featureFlagsClientLibrariesHelpPagePath: '/help/feature-flags#unleash-clients',
|
||||
featureFlagsClientExampleHelpPagePath: '/help/feature-flags#client-example',
|
||||
unleashApiUrl: `${TEST_HOST}/api/unleash`,
|
||||
newFeatureFlagPath: null,
|
||||
newUserListPath: null,
|
||||
};
|
||||
|
||||
beforeEach(done => {
|
||||
mock
|
||||
.onGet(`${TEST_HOST}/endpoint.json`, { params: { scope: FEATURE_FLAG_SCOPE, page: '1' } })
|
||||
.reply(200, getRequestData, {});
|
||||
|
||||
factory(propsData);
|
||||
|
||||
setImmediate(() => {
|
||||
done();
|
||||
});
|
||||
setImmediate(done);
|
||||
});
|
||||
|
||||
it('does not render configure button', () => {
|
||||
|
|
@ -197,9 +237,7 @@ describe('Feature flags', () => {
|
|||
|
||||
factory();
|
||||
jest.spyOn(store, 'dispatch');
|
||||
setImmediate(() => {
|
||||
done();
|
||||
});
|
||||
setImmediate(done);
|
||||
});
|
||||
|
||||
it('should render a table with feature flags', () => {
|
||||
|
|
@ -267,10 +305,7 @@ describe('Feature flags', () => {
|
|||
describe('in user lists tab', () => {
|
||||
beforeEach(done => {
|
||||
factory();
|
||||
|
||||
setImmediate(() => {
|
||||
done();
|
||||
});
|
||||
setImmediate(done);
|
||||
});
|
||||
beforeEach(() => {
|
||||
wrapper.find('[data-testid="user-lists-tab"]').vm.$emit('changeTab');
|
||||
|
|
@ -295,10 +330,7 @@ describe('Feature flags', () => {
|
|||
Api.fetchFeatureFlagUserLists.mockRejectedValueOnce();
|
||||
|
||||
factory();
|
||||
|
||||
setImmediate(() => {
|
||||
done();
|
||||
});
|
||||
setImmediate(done);
|
||||
});
|
||||
|
||||
it('should render error state', () => {
|
||||
|
|
@ -329,10 +361,7 @@ describe('Feature flags', () => {
|
|||
.onGet(`${TEST_HOST}/endpoint.json`, { params: { scope: FEATURE_FLAG_SCOPE, page: '1' } })
|
||||
.reply(200, getRequestData, {});
|
||||
factory();
|
||||
|
||||
setImmediate(() => {
|
||||
done();
|
||||
});
|
||||
setImmediate(done);
|
||||
});
|
||||
|
||||
it('should fire the rotate action when a `token` event is received', () => {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import { shallowMount } from '@vue/test-utils';
|
||||
import merge from 'lodash/merge';
|
||||
import { GlLink } from '@gitlab/ui';
|
||||
import HighlightBar from '~/issue_show/components/incidents/highlight_bar.vue';
|
||||
import { formatDate } from '~/lib/utils/datetime_utility';
|
||||
|
|
@ -16,12 +17,17 @@ describe('Highlight Bar', () => {
|
|||
title: 'Alert 1',
|
||||
};
|
||||
|
||||
const mountComponent = () => {
|
||||
wrapper = shallowMount(HighlightBar, {
|
||||
propsData: {
|
||||
alert,
|
||||
},
|
||||
});
|
||||
const mountComponent = options => {
|
||||
wrapper = shallowMount(
|
||||
HighlightBar,
|
||||
merge(
|
||||
{
|
||||
propsData: { alert },
|
||||
provide: { fullPath: 'test', iid: 1, slaFeatureAvailable: true },
|
||||
},
|
||||
options,
|
||||
),
|
||||
);
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
|
|
@ -37,22 +43,52 @@ describe('Highlight Bar', () => {
|
|||
|
||||
const findLink = () => wrapper.find(GlLink);
|
||||
|
||||
it('renders a link to the alert page', () => {
|
||||
expect(findLink().exists()).toBe(true);
|
||||
expect(findLink().attributes('href')).toBe(alert.detailsUrl);
|
||||
expect(findLink().attributes('title')).toBe(alert.title);
|
||||
expect(findLink().text()).toBe(`#${alert.iid}`);
|
||||
describe('empty state', () => {
|
||||
beforeEach(() => {
|
||||
mountComponent({ propsData: { alert: null } });
|
||||
});
|
||||
|
||||
it('renders a empty component', () => {
|
||||
expect(wrapper.isVisible()).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
it('renders formatted start time of the alert', () => {
|
||||
const formattedDate = '2020-05-29 UTC';
|
||||
formatDate.mockReturnValueOnce(formattedDate);
|
||||
mountComponent();
|
||||
expect(formatDate).toHaveBeenCalledWith(alert.startedAt, 'yyyy-mm-dd Z');
|
||||
expect(wrapper.text()).toContain(formattedDate);
|
||||
describe('alert present', () => {
|
||||
beforeEach(() => {
|
||||
mountComponent();
|
||||
});
|
||||
|
||||
it('renders a link to the alert page', () => {
|
||||
expect(findLink().exists()).toBe(true);
|
||||
expect(findLink().attributes('href')).toBe(alert.detailsUrl);
|
||||
expect(findLink().attributes('title')).toBe(alert.title);
|
||||
expect(findLink().text()).toBe(`#${alert.iid}`);
|
||||
});
|
||||
|
||||
it('renders formatted start time of the alert', () => {
|
||||
const formattedDate = '2020-05-29 UTC';
|
||||
formatDate.mockReturnValueOnce(formattedDate);
|
||||
mountComponent();
|
||||
expect(formatDate).toHaveBeenCalledWith(alert.startedAt, 'yyyy-mm-dd Z');
|
||||
expect(wrapper.text()).toContain(formattedDate);
|
||||
});
|
||||
|
||||
it('renders a number of alert events', () => {
|
||||
expect(wrapper.text()).toContain(alert.eventCount);
|
||||
});
|
||||
});
|
||||
|
||||
it('renders a number of alert events', () => {
|
||||
expect(wrapper.text()).toContain(alert.eventCount);
|
||||
describe('when child data is present', () => {
|
||||
beforeEach(() => {
|
||||
mountComponent({
|
||||
data() {
|
||||
return { hasChildData: true };
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('renders the highlight bar component', () => {
|
||||
expect(wrapper.isVisible()).toBe(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -57,7 +57,6 @@ describe('Incident Tabs component', () => {
|
|||
|
||||
it('does not show the alert details tab', () => {
|
||||
expect(findAlertDetailsComponent().exists()).toBe(false);
|
||||
expect(findHighlightBarComponent().exists()).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -30,6 +30,7 @@ issues:
|
|||
- metrics
|
||||
- timelogs
|
||||
- issuable_severity
|
||||
- issuable_sla
|
||||
- issue_assignees
|
||||
- closed_by
|
||||
- epic_issue
|
||||
|
|
@ -713,3 +714,5 @@ system_note_metadata:
|
|||
- description_version
|
||||
status_page_published_incident:
|
||||
- issue
|
||||
issuable_sla:
|
||||
- issue
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ RSpec.describe 'Test coverage of the Project Import' do
|
|||
project.issues.notes.events
|
||||
project.issues.notes.events.push_event_payload
|
||||
project.issues.milestone.events.push_event_payload
|
||||
project.issues.issuable_sla
|
||||
project.issues.issue_milestones
|
||||
project.issues.issue_milestones.milestone
|
||||
project.issues.resource_label_events.label.priorities
|
||||
|
|
|
|||
|
|
@ -855,3 +855,6 @@ ProjectSecuritySetting:
|
|||
- auto_fix_sast
|
||||
- created_at
|
||||
- updated_at
|
||||
IssuableSla:
|
||||
- issue_id
|
||||
- due_at
|
||||
|
|
|
|||
Loading…
Reference in New Issue