diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2020-10-14 00:09:27 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2020-10-14 00:09:27 +0300 |
commit | 60ea1fab8ea970796c359dddb7d20fcead651c7d (patch) | |
tree | f8f674ded2c858e2708633c4761ecbc78d234798 | |
parent | b4b6bff01d33ddf1ebd78001f16027b3ccd6443e (diff) |
Add latest changes from gitlab-org/gitlab@master
37 files changed, 490 insertions, 130 deletions
diff --git a/app/assets/javascripts/blob/viewer/index.js b/app/assets/javascripts/blob/viewer/index.js index 0fb803cdfec..aa76364c466 100644 --- a/app/assets/javascripts/blob/viewer/index.js +++ b/app/assets/javascripts/blob/viewer/index.js @@ -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) { diff --git a/app/assets/javascripts/emoji/support/index.js b/app/assets/javascripts/emoji/support/index.js index 1f7852dd487..14b80be9b43 100644 --- a/app/assets/javascripts/emoji/support/index.js +++ b/app/assets/javascripts/emoji/support/index.js @@ -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); } diff --git a/app/assets/javascripts/feature_flags/components/feature_flags.vue b/app/assets/javascripts/feature_flags/components/feature_flags.vue index 2ea92ca0d9c..a86c3fec1ab 100644 --- a/app/assets/javascripts/feature_flags/components/feature_flags.vue +++ b/app/assets/javascripts/feature_flags/components/feature_flags.vue @@ -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: { + ConfigureFeatureFlagsModal, + FeatureFlagsTab, FeatureFlagsTable, - UserListsTable, - TablePagination, + GlAlert, GlButton, + GlSprintf, GlTabs, - FeatureFlagsTab, - ConfigureFeatureFlagsModal, + 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> diff --git a/app/assets/javascripts/feature_flags/index.js b/app/assets/javascripts/feature_flags/index.js index 48160fe687c..784e58125f7 100644 --- a/app/assets/javascripts/feature_flags/index.js +++ b/app/assets/javascripts/feature_flags/index.js @@ -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, diff --git a/app/assets/javascripts/frequent_items/index.js b/app/assets/javascripts/frequent_items/index.js index 1998bf4358a..5b03e1d19db 100644 --- a/app/assets/javascripts/frequent_items/index.js +++ b/app/assets/javascripts/frequent_items/index.js @@ -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`); }); }); diff --git a/app/assets/javascripts/issue_show/components/incidents/highlight_bar.vue b/app/assets/javascripts/issue_show/components/incidents/highlight_bar.vue index b2aa5265331..96f187f26dd 100644 --- a/app/assets/javascripts/issue_show/components/incidents/highlight_bar.vue +++ b/app/assets/javascripts/issue_show/components/incidents/highlight_bar.vue @@ -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> diff --git a/app/assets/javascripts/issue_show/components/incidents/incident_tabs.vue b/app/assets/javascripts/issue_show/components/incidents/incident_tabs.vue index 5925c013e89..19a9c67553a 100644 --- a/app/assets/javascripts/issue_show/components/incidents/incident_tabs.vue +++ b/app/assets/javascripts/issue_show/components/incidents/incident_tabs.vue @@ -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')"> diff --git a/app/assets/javascripts/issue_show/incident.js b/app/assets/javascripts/issue_show/incident.js index a34e75ee64a..618fb551f28 100644 --- a/app/assets/javascripts/issue_show/incident.js +++ b/app/assets/javascripts/issue_show/incident.js @@ -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', { diff --git a/app/presenters/issue_presenter.rb b/app/presenters/issue_presenter.rb index 185fcd3e934..0b498ce97d8 100644 --- a/app/presenters/issue_presenter.rb +++ b/app/presenters/issue_presenter.rb @@ -11,3 +11,5 @@ class IssuePresenter < Gitlab::View::Presenter::Delegated issue.subscribed?(current_user, issue.project) end end + +IssuePresenter.prepend_if_ee('EE::IssuePresenter') diff --git a/app/views/projects/feature_flags/index.html.haml b/app/views/projects/feature_flags/index.html.haml index f425de91d12..7d48cba74d0 100644 --- a/app/views/projects/feature_flags/index.html.haml +++ b/app/views/projects/feature_flags/index.html.haml @@ -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), diff --git a/changelogs/unreleased/241663-incident-sla-logic.yml b/changelogs/unreleased/241663-incident-sla-logic.yml new file mode 100644 index 00000000000..5b06e8e751f --- /dev/null +++ b/changelogs/unreleased/241663-incident-sla-logic.yml @@ -0,0 +1,5 @@ +--- +title: Add Issuable Service Level Agreement (SLA) table +merge_request: 44253 +author: +type: added diff --git a/changelogs/unreleased/feature-flag-limits-ux.yml b/changelogs/unreleased/feature-flag-limits-ux.yml new file mode 100644 index 00000000000..7b1fa628ea9 --- /dev/null +++ b/changelogs/unreleased/feature-flag-limits-ux.yml @@ -0,0 +1,5 @@ +--- +title: Feature Flags limits UX and documentation +merge_request: 44089 +author: +type: added diff --git a/db/migrate/20201002012659_add_issuable_sla_table.rb b/db/migrate/20201002012659_add_issuable_sla_table.rb new file mode 100644 index 00000000000..c43187bf93a --- /dev/null +++ b/db/migrate/20201002012659_add_issuable_sla_table.rb @@ -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 diff --git a/db/schema_migrations/20201002012659 b/db/schema_migrations/20201002012659 new file mode 100644 index 00000000000..6a6d33389f9 --- /dev/null +++ b/db/schema_migrations/20201002012659 @@ -0,0 +1 @@ +8a12c3c4f674d2a36df56a89bfd32e0f3945e73605460bdf2a8b0aa1308f5b19
\ No newline at end of file diff --git a/db/structure.sql b/db/structure.sql index 7ac26402d8e..38a89fe7dc4 100644 --- a/db/structure.sql +++ b/db/structure.sql @@ -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; diff --git a/doc/administration/index.md b/doc/administration/index.md index 076658ead0e..fda3816312c 100644 --- a/doc/administration/index.md +++ b/doc/administration/index.md @@ -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 diff --git a/doc/administration/instance_limits.md b/doc/administration/instance_limits.md index c25390e5f98..e647c020e01 100644 --- a/doc/administration/instance_limits.md +++ b/doc/administration/instance_limits.md @@ -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. - -Limits are set per package type. +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: + +- 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): diff --git a/doc/administration/instance_review.md b/doc/administration/instance_review.md index 326305f4517..7eadb54804b 100644 --- a/doc/administration/instance_review.md +++ b/doc/administration/instance_review.md @@ -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. -![Instance Review button](img/instance_review_button.png) +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. + ![Instance Review button](img/instance_review_button.png) -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. diff --git a/doc/api/container_registry.md b/doc/api/container_registry.md index 2fe72f53d87..3ce04b0c189 100644 --- a/doc/api/container_registry.md +++ b/doc/api/container_registry.md @@ -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`. | diff --git a/doc/api/graphql/reference/gitlab_schema.graphql b/doc/api/graphql/reference/gitlab_schema.graphql index 146f15cf3a7..bd231c76b14 100644 --- a/doc/api/graphql/reference/gitlab_schema.graphql +++ b/doc/api/graphql/reference/gitlab_schema.graphql @@ -6726,6 +6726,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 """ state: IssueState! @@ -8881,6 +8886,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 """ state: IssueState! diff --git a/doc/api/graphql/reference/gitlab_schema.json b/doc/api/graphql/reference/gitlab_schema.json index 25c4014a4dc..f70814267ef 100644 --- a/doc/api/graphql/reference/gitlab_schema.json +++ b/doc/api/graphql/reference/gitlab_schema.json @@ -18533,6 +18533,20 @@ "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", "args": [ @@ -24221,6 +24235,20 @@ "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", "args": [ diff --git a/doc/api/graphql/reference/index.md b/doc/api/graphql/reference/index.md index 11205622c82..51bc2176102 100644 --- a/doc/api/graphql/reference/index.md +++ b/doc/api/graphql/reference/index.md @@ -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 | diff --git a/doc/operations/feature_flags.md b/doc/operations/feature_flags.md index db12e6eb316..00ebfe5ccf8 100644 --- a/doc/operations/feature_flags.md +++ b/doc/operations/feature_flags.md @@ -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. diff --git a/doc/subscriptions/self_managed/index.md b/doc/subscriptions/self_managed/index.md index 6fedcb41a89..6b65a4a9e5e 100644 --- a/doc/subscriptions/self_managed/index.md +++ b/doc/subscriptions/self_managed/index.md @@ -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 diff --git a/doc/user/application_security/container_scanning/index.md b/doc/user/application_security/container_scanning/index.md index 73f5404713f..9e7f98dd4fc 100644 --- a/doc/user/application_security/container_scanning/index.md +++ b/doc/user/application_security/container_scanning/index.md @@ -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 diff --git a/doc/user/application_security/dependency_scanning/index.md b/doc/user/application_security/dependency_scanning/index.md index 67d2ae2d3a7..9290c51a8b8 100644 --- a/doc/user/application_security/dependency_scanning/index.md +++ b/doc/user/application_security/dependency_scanning/index.md @@ -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 diff --git a/doc/user/application_security/sast/index.md b/doc/user/application_security/sast/index.md index 3b1c1b05e66..2ea60513d68 100644 --- a/doc/user/application_security/sast/index.md +++ b/doc/user/application_security/sast/index.md @@ -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 diff --git a/doc/user/application_security/secret_detection/index.md b/doc/user/application_security/secret_detection/index.md index 4cc5bd020f3..1204460bd75 100644 --- a/doc/user/application_security/secret_detection/index.md +++ b/doc/user/application_security/secret_detection/index.md @@ -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` diff --git a/doc/user/packages/nuget_repository/index.md b/doc/user/packages/nuget_repository/index.md index c40db409903..113bb2a6d78 100644 --- a/doc/user/packages/nuget_repository/index.md +++ b/doc/user/packages/nuget_repository/index.md @@ -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 diff --git a/lib/gitlab/import_export/project/import_export.yml b/lib/gitlab/import_export/project/import_export.yml index 9ec5df8cde9..a0526ba0414 100644 --- a/lib/gitlab/import_export/project/import_export.yml +++ b/lib/gitlab/import_export/project/import_export.yml @@ -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 diff --git a/locale/gitlab.pot b/locale/gitlab.pot index ed78572427a..3e7ff0e5ee7 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -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 "" diff --git a/spec/frontend/feature_flags/components/feature_flags_spec.js b/spec/frontend/feature_flags/components/feature_flags_spec.js index c9ab7695158..7f9e98c97ef 100644 --- a/spec/frontend/feature_flags/components/feature_flags_spec.js +++ b/spec/frontend/feature_flags/components/feature_flags_spec.js @@ -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 = { + canUserConfigure: true, + // canUserRotateToken: true, csrfToken: 'testToken', - featureFlagsClientLibrariesHelpPagePath: '/help/feature-flags#unleash-clients', featureFlagsClientExampleHelpPagePath: '/help/feature-flags#client-example', - unleashApiUrl: `${TEST_HOST}/api/unleash`, - canUserConfigure: true, - canUserRotateToken: true, + 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', () => { diff --git a/spec/frontend/issue_show/components/incidents/highlight_bar_spec.js b/spec/frontend/issue_show/components/incidents/highlight_bar_spec.js index 766a27015bb..c1ab4433761 100644 --- a/spec/frontend/issue_show/components/incidents/highlight_bar_spec.js +++ b/spec/frontend/issue_show/components/incidents/highlight_bar_spec.js @@ -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); + }); }); }); diff --git a/spec/frontend/issue_show/components/incidents/incident_tabs_spec.js b/spec/frontend/issue_show/components/incidents/incident_tabs_spec.js index 6babba37b57..9b22fe4e85a 100644 --- a/spec/frontend/issue_show/components/incidents/incident_tabs_spec.js +++ b/spec/frontend/issue_show/components/incidents/incident_tabs_spec.js @@ -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); }); }); diff --git a/spec/lib/gitlab/import_export/all_models.yml b/spec/lib/gitlab/import_export/all_models.yml index edb9a613f85..5ee7fb2adbf 100644 --- a/spec/lib/gitlab/import_export/all_models.yml +++ b/spec/lib/gitlab/import_export/all_models.yml @@ -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 diff --git a/spec/lib/gitlab/import_export/import_test_coverage_spec.rb b/spec/lib/gitlab/import_export/import_test_coverage_spec.rb index 9737a0f39fc..7a9e7d8afba 100644 --- a/spec/lib/gitlab/import_export/import_test_coverage_spec.rb +++ b/spec/lib/gitlab/import_export/import_test_coverage_spec.rb @@ -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 diff --git a/spec/lib/gitlab/import_export/safe_model_attributes.yml b/spec/lib/gitlab/import_export/safe_model_attributes.yml index 5ca7c5b7a91..e3d1f2c9368 100644 --- a/spec/lib/gitlab/import_export/safe_model_attributes.yml +++ b/spec/lib/gitlab/import_export/safe_model_attributes.yml @@ -855,3 +855,6 @@ ProjectSecuritySetting: - auto_fix_sast - created_at - updated_at +IssuableSla: + - issue_id + - due_at |