Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2023-03-28 00:11:35 +00:00
parent ff2a881e20
commit 9968d39440
31 changed files with 249 additions and 206 deletions

View File

@ -302,7 +302,7 @@ export default {
<div :class="newSubgroup && 'row gl-mb-3'">
<gl-form-group v-if="newSubgroup" class="col-sm-6 gl-pr-0" :label="inputLabels.subgroupPath">
<div class="input-group gl-flex-nowrap">
<div class="input-group gl-flex-wrap-nowrap">
<gl-button-group class="gl-w-full">
<gl-button class="js-group-namespace-button gl-text-truncate gl-flex-grow-0!" label>
{{ basePath }}

View File

@ -20,7 +20,7 @@ export default {
</script>
<template>
<div class="d-flex-center gl-flex-nowrap text-nowrap js-ide-status-mr">
<div class="d-flex-center gl-flex-wrap-nowrap text-nowrap js-ide-status-mr">
<gl-icon name="merge-request" />
<span class="ml-1 d-none d-sm-block">{{ s__('WebIDE|Merge request') }}</span>
<gl-link class="ml-1" :href="url">{{ text }}</gl-link>

View File

@ -31,7 +31,7 @@ export default {
<template>
<dropdown-button class="gl-w-full!">
<span class="row gl-flex-nowrap">
<span class="row gl-flex-wrap-nowrap">
<span class="col-auto flex-fill text-truncate">
<gl-icon :size="16" :aria-label="__('Current Branch')" name="branch" /> {{ branchLabel }}
</span>

View File

@ -130,7 +130,7 @@ export default {
</div>
<div class="gl-w-full gl-display-flex">
<ul
class="merge-request-tabs nav-tabs nav nav-links gl-display-flex gl-flex-nowrap gl-m-0 gl-p-0 gl-border-b-0"
class="merge-request-tabs nav-tabs nav nav-links gl-display-flex gl-flex-wrap-nowrap gl-m-0 gl-p-0 gl-border-b-0"
>
<li
v-for="(tab, index) in tabs"

View File

@ -82,7 +82,7 @@ export default {
<status-icon :icon-name="data.icon.name" :size="12" class="gl-m-auto" />
</div>
<div class="gl-w-full">
<div class="gl-display-flex gl-flex-nowrap">
<div class="gl-display-flex gl-flex-wrap-nowrap">
<div class="gl-flex-wrap gl-display-flex gl-w-full">
<div class="gl-display-flex gl-align-items-center">
<p v-safe-html="generateText(data.text)" class="gl-m-0"></p>

View File

@ -1,56 +0,0 @@
<script>
import * as Sentry from '@sentry/browser';
import Vue from 'vue';
import Vuex from 'vuex';
Vue.use(Vuex);
export default {
props: {
dashboardUrl: {
type: String,
required: false,
default: '',
},
},
data() {
return {
metricEmbedComponent: null,
namespace: 'alertMetrics',
};
},
mounted() {
if (this.dashboardUrl) {
Promise.all([
import('~/monitoring/components/embeds/metric_embed.vue'),
import('~/monitoring/stores'),
])
.then(([{ default: MetricEmbed }, { monitoringDashboard }]) => {
this.$store = new Vuex.Store({
modules: {
[this.namespace]: monitoringDashboard,
},
});
this.metricEmbedComponent = MetricEmbed;
})
.catch((e) => Sentry.captureException(e));
}
},
};
</script>
<template>
<div class="gl-py-3">
<div v-if="dashboardUrl" ref="metricsChart">
<component
:is="metricEmbedComponent"
v-if="metricEmbedComponent"
:dashboard-url="dashboardUrl"
:namespace="namespace"
/>
</div>
<div v-else ref="emptyState">
{{ s__("AlertManagement|Metrics weren't available in the alerts payload.") }}
</div>
</div>
</template>

View File

@ -295,7 +295,7 @@ export default {
</script>
<template>
<div class="form-row gl-mb-5 work-item-assignees gl-relative gl-flex-nowrap">
<div class="form-row gl-mb-5 work-item-assignees gl-relative gl-flex-wrap-nowrap">
<span
:id="assigneesTitleId"
class="gl-font-weight-bold gl-mt-2 col-lg-2 col-3 gl-pt-2 min-w-fit-content gl-overflow-wrap-break"

View File

@ -272,7 +272,7 @@ export default {
</script>
<template>
<div class="form-row gl-mb-5 work-item-labels gl-relative gl-flex-nowrap">
<div class="form-row gl-mb-5 work-item-labels gl-relative gl-flex-wrap-nowrap">
<span
:id="labelsTitleId"
class="gl-font-weight-bold gl-mt-2 col-lg-2 col-3 gl-pt-2 min-w-fit-content gl-overflow-wrap-break"

View File

@ -217,7 +217,7 @@ export default {
<template>
<gl-form-group
class="work-item-dropdown gl-flex-nowrap"
class="work-item-dropdown gl-flex-wrap-nowrap"
:label="$options.i18n.MILESTONE"
label-for="milestone-value"
label-class="gl-pb-0! gl-mt-3 gl-overflow-wrap-break"

View File

@ -4,7 +4,7 @@
= text_field_tag :name, @name, placeholder: "My awesome project", class: "js-project-name form-control gl-form-input input-lg", autofocus: true, required: true, aria: { required: true }, data: { qa_selector: 'project_name_field' }
.form-group.col-12.col-sm-6.gl-pr-0
= label_tag :namespace_id, _('Project URL'), class: 'label-bold'
.input-group.gl-flex-nowrap
.input-group.gl-flex-wrap-nowrap
- if current_user.can_select_namespace?
- namespace_id = namespace_id_from(params)
.js-vue-new-project-url-select{ data: { namespace_full_path: GroupFinder.new(current_user).execute(id: namespace_id)&.full_path || current_user.namespace.full_path,

View File

@ -16,7 +16,7 @@
.form-group.project-path.col-sm-6.gl-pr-0
= f.label :namespace_id, class: 'label-bold' do
%span= _('Project URL')
.input-group.gl-flex-nowrap
.input-group.gl-flex-wrap-nowrap
- if current_user.can_select_namespace?
- namespace_id = namespace_id_from(params)
.js-vue-new-project-url-select{ data: { namespace_full_path: GroupFinder.new(current_user).execute(id: namespace_id)&.full_path || @current_user_group&.full_path,

View File

@ -1,7 +1,7 @@
- f ||= local_assigns[:f]
.project-templates-buttons
= gl_tabs_nav({ class: 'nav-links scrolling-tabs gl-display-flex gl-flex-grow-1 gl-flex-nowrap gl-border-0' }) do
= gl_tabs_nav({ class: 'nav-links scrolling-tabs gl-display-flex gl-flex-grow-1 gl-flex-wrap-nowrap gl-border-0' }) do
= gl_tab_link_to '#built-in', tab_class: 'built-in-tab', class: 'active', data: { toggle: 'tab' } do
= _('Built-in')
= gl_tab_counter_badge Gitlab::ProjectTemplate.all.count + Gitlab::SampleDataTemplate.all.count

View File

@ -27,7 +27,7 @@
= render "projects/merge_requests/mr_box"
.merge-request-tabs-holder{ class: "#{'js-tabs-affix' unless ENV['RAILS_ENV'] == 'test'} #{'gl-static' if moved_mr_sidebar_enabled?}" }
.merge-request-tabs-container.gl-display-flex.gl-justify-content-space-between{ class: "#{'is-merge-request' if Feature.enabled?(:moved_mr_sidebar, @project) && !fluid_layout}" }
%ul.merge-request-tabs.nav-tabs.nav.nav-links.gl-display-flex.gl-flex-nowrap.gl-m-0.gl-p-0{ class: "#{'gl-w-full gl-lg-w-auto!' if Feature.enabled?(:moved_mr_sidebar, @project)}" }
%ul.merge-request-tabs.nav-tabs.nav.nav-links.gl-display-flex.gl-flex-wrap-nowrap.gl-m-0.gl-p-0{ class: "#{'gl-w-full gl-lg-w-auto!' if Feature.enabled?(:moved_mr_sidebar, @project)}" }
= render "projects/merge_requests/tabs/tab", class: "notes-tab", qa_selector: "notes_tab" do
= tab_link_for @merge_request, :show, force_link: @commit.present? do
= _("Overview")

View File

@ -22,7 +22,7 @@
.scrolling-tabs-container.inner-page-scroll-tabs.is-smaller
.fade-left= sprite_icon('chevron-lg-left', size: 12)
.fade-right= sprite_icon('chevron-lg-right', size: 12)
%ul.merge-request-tabs.nav.nav-tabs.nav-links.no-top.no-bottom.gl-display-flex.gl-flex-nowrap.gl-m-0.gl-p-0.js-tabs-affix
%ul.merge-request-tabs.nav.nav-tabs.nav-links.no-top.no-bottom.gl-display-flex.gl-flex-wrap-nowrap.gl-m-0.gl-p-0.js-tabs-affix
%li.commits-tab.new-tab
= link_to url_for(safe_params), data: {target: 'div#commits', action: 'new', toggle: 'tabvue'} do
= _("Commits")

View File

@ -2,7 +2,7 @@
- include_private = local_assigns.fetch(:include_private, false)
- params[:scope] ||= []
= gl_tabs_nav({ class: 'js-snippets-nav-tabs gl-border-b-0 gl-overflow-x-auto gl-flex-grow-1 gl-flex-nowrap' }) do
= gl_tabs_nav({ class: 'js-snippets-nav-tabs gl-border-b-0 gl-overflow-x-auto gl-flex-grow-1 gl-flex-wrap-nowrap' }) do
= gl_tab_link_to subject_snippets_path(subject), { item_active: params[:scope].empty? } do
= _('All')
= gl_tab_counter_badge(include_private ? counts[:total] : counts[:are_public_or_internal])

View File

@ -8,11 +8,9 @@ class AddIndexToVulnerabilityFindingsOnUuid < Gitlab::Database::Migration[2.1]
# TODO: Index to be created synchronously in https://gitlab.com/gitlab-org/gitlab/-/issues/397740
def up
index_sql = <<-SQL
prepare_async_index_from_sql <<-SQL
CREATE UNIQUE INDEX CONCURRENTLY #{INDEX_NAME} ON vulnerability_occurrences (uuid) include (vulnerability_id);
SQL
prepare_async_index_from_sql(:vulnerability_occurrences, INDEX_NAME, index_sql)
end
def down

View File

@ -1,5 +1,5 @@
---
status: proposed
status: ongoing
creation-date: "2022-11-25"
authors: [ "@theoretick" ]
coach: "@DylanGriffith"
@ -123,6 +123,23 @@ Given our existing familiarity with the tool and its extensibility, it should
remain our engine of choice. Changes to the detection engine are out of scope
unless benchmarking unveils performance concerns.
Notable alternatives include high-performance regex engines such as [hyperscan](https://github.com/intel/hyperscan) or it's portable fork [vectorscan](https://github.com/VectorCamp/vectorscan).
### High-level architecture
The implementation of the secret scanning service is highly dependent on the outcomes of our benchmarking
and capacity planning against both GitLab.com and our
[Reference Architectures](../../../administration/reference_architectures/index.md).
As the scanning capability must be an on-by-default component of both our SaaS and self-managed
instances [the PoC](#iterations), the deployment characteristics must be considered to determine whether
this is a standalone component or executed as a subprocess of the existing Sidekiq worker fleet
(similar to the implementation of our Elasticsearch indexing service).
Similarly, the scan target volume will require a robust and scalable enqueueing system to limit resource consumption.
See [this thread](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/105142#note_1194863310)
for past discussion around scaling approaches.
### Push event detection flow
```mermaid
@ -151,17 +168,20 @@ sequenceDiagram
## Iterations
1. Requirements definition for detection coverage and actions
1. PoC of secret scanning service
1. gRPC commit retrieval from Gitaly
1. blob scanning
1. benchmarking of issuables, comments, job logs and blobs to gain confidence that the total costs will be viable
1. Implementation of secret scanning service MVC (targeting individual commits)
1. Security and readiness review
1. Deployment and monitoring
1. Implementation of secret scanning service MVC (targeting arbitrary text blobs)
1. Deployment and monitoring
1. High priority domain object rollout (priority `TBD`)
1. Issuable comments
1. Issuable bodies
1. Job logs
- [x] Define [requirements for detection coverage and actions](https://gitlab.com/gitlab-org/gitlab/-/issues/376716)
- [x] Implement [Clientside detection of GitLab tokens within comments/issues](https://gitlab.com/gitlab-org/gitlab/-/issues/368434)
- [ ] PoC of secret scanning service
- [ ] Benchmarking of issuables, comments, job logs and blobs to gain confidence that the total costs will be viable
- [ ] Capacity planning for addition of service component to Reference Architectures headroom
- [ ] Service capabilities
- [ ] gRPC commit retrieval from Gitaly
- [ ] blob scanning
- [ ] Implementation of secret scanning service MVC (targeting individual commits)
- [ ] Security and readiness review
- [ ] Deployment and monitoring
- [ ] Implementation of secret scanning service MVC (targeting arbitrary text blobs)
- [ ] Deployment and monitoring
- [ ] High priority domain object rollout (priority `TBD`)
- [ ] Issuable comments
- [ ] Issuable bodies
- [ ] Job logs

View File

@ -76,7 +76,7 @@ tab of the pipeline's details page.
### Project quality view
The project quality view displays an overview of the code quality findings.
The project quality view displays an overview of the code quality findings. The view can be found under **Analytics > CI/CD**, and requires [`project_quality_summary_page`](../../user/feature_flags.md) feature flag to be enabled for this particular project.
![Code Quality Summary](img/code_quality_summary_15_9.png)

View File

@ -15,6 +15,9 @@ We recommend that you use fuzz testing in addition to the other security scanner
and your own test processes. If you're using [GitLab CI/CD](../../../ci/index.md),
you can run your coverage-guided fuzz testing as part your CI/CD workflow.
<i class="fa fa-youtube-play youtube" aria-hidden="true"></i>
For an overview, see [Coverage Fuzzing](https://www.youtube.com/watch?v=bbIenVVcjW0).
## Coverage-guided fuzz testing process
The fuzz testing process:

View File

@ -15,6 +15,9 @@ visible from the source code.
Dynamic Application Security Testing (DAST) examines applications for
vulnerabilities like these in deployed environments.
<i class="fa fa-youtube-play youtube" aria-hidden="true"></i>
For an overview, see [Dynamic Application Security Testing (DAST)](https://www.youtube.com/watch?v=nbeDUoLZJTo).
NOTE:
To learn how four of the top six attacks were application-based and how
to protect your organization, download our

View File

@ -13,6 +13,9 @@ info: To determine the technical writer assigned to the Stage/Group associated w
Use the dependency list to review your project's dependencies and key
details about those dependencies, including their known vulnerabilities. It is a collection of dependencies in your project, including existing and new findings.
<i class="fa fa-youtube-play youtube" aria-hidden="true"></i>
For an overview, see [Project Dependency](https://www.youtube.com/watch?v=ckqkn9Tnbw4).
To see the dependency list, go to your project and select **Security and Compliance > Dependency list**.
This information is sometimes referred to as a Software Bill of Materials, SBOM, or BOM.

View File

@ -350,5 +350,17 @@ a minimum of the Guest role in the specified Dependency Proxy group:
```plaintext
ERROR: gitlab.example.com:443/group1/dependency_proxy/containers/alpine:latest: not found
failed to solve with frontend dockerfile.v0: failed to create LLB definition: gitlab.example.com:443/group1/dependency_proxy/containers/alpine:latest: not found
failed to solve with frontend dockerfile.v0: failed to create LLB definition:
gitlab.example.com:443/group1/dependency_proxy/containers/alpine:latest: not found
```
```plaintext
ERROR: Job failed: failed to pull image
"gitlab.example.com:443/group1/dependency_proxy/containers/alpine:latest" with specified policies [always]:
Error response from daemon: error parsing HTTP 404 response body:
unexpected end of JSON input: "" (manager.go:237:1s)
```
NOTE:
The work to correctly display `Access denied` errors in such cases is being tracked in the following issue:
[Dependency proxy error reporting confusing - "404/not found" should be "access denied" when not group member](https://gitlab.com/gitlab-org/gitlab/-/issues/354826).

View File

@ -3,14 +3,17 @@ stage: Manage
group: Integrations
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/product/ux/technical-writing/#assignments
---
<!--- start_remove The following content will be removed on remove_date: '2024-05-22' -->
# Slack notifications **(FREE)**
# Slack notifications (deprecated) **(FREE)**
WARNING:
This feature was [deprecated](https://gitlab.com/gitlab-org/gitlab/-/issues/372411) on GitLab.com
in GitLab 15.9 and is [planned for removal](https://gitlab.com/groups/gitlab-org/-/epics/8673).
For GitLab.com, use the [GitLab for Slack app](gitlab_slack_application.md) instead.
For self-managed GitLab instances, you can continue to use this feature.
This feature was [deprecated](https://gitlab.com/gitlab-org/gitlab/-/issues/372411) in GitLab 15.9
and is planned for removal in 17.0. Use the [GitLab for Slack app](gitlab_slack_application.md) instead.
This change is a breaking change.
For the planned support of the GitLab for Slack app for self-managed instances,
see [epic 1211](https://gitlab.com/groups/gitlab-org/-/epics/1211).
The Slack notifications integration enables your GitLab project to send events
(such as issue creation) to your existing Slack team as notifications. Setting up
@ -150,3 +153,5 @@ p.each do |project|
project.slack_integration.update!(:active, false)
end
```
<!--- end_remove -->

View File

@ -77,11 +77,13 @@ module Gitlab
async_index
end
def prepare_async_index_from_sql(table_name, index_name, definition)
def prepare_async_index_from_sql(definition)
Gitlab::Database::QueryAnalyzers::RestrictAllowedSchemas.require_ddl_mode!
return unless async_index_creation_available?
table_name, index_name = extract_table_and_index_names_from_concurrent_index!(definition)
if index_name_exists?(table_name, index_name)
Gitlab::AppLogger.warn(
message: 'Index not prepared because it already exists',
@ -137,7 +139,30 @@ module Gitlab
end
def async_index_creation_available?
connection.table_exists?(:postgres_async_indexes)
table_exists?(:postgres_async_indexes)
end
private
delegate :table_exists?, to: :connection, private: true
def extract_table_and_index_names_from_concurrent_index!(definition)
statement = index_statement_from!(definition)
raise 'Index statement not found!' unless statement
raise 'Index must be created concurrently!' unless statement.concurrent
raise 'Table does not exist!' unless table_exists?(statement.relation.relname)
[statement.relation.relname, statement.idxname]
end
# This raises `PgQuery::ParseError` if the given statement
# is syntactically incorrect, therefore, validates that the
# index definition is correct.
def index_statement_from!(definition)
parsed_query = PgQuery.parse(definition)
parsed_query.tree.stmts[0].stmt.index_stmt
end
end
end

View File

@ -3822,9 +3822,6 @@ msgstr ""
msgid "AlertManagement|Metrics"
msgstr ""
msgid "AlertManagement|Metrics weren't available in the alerts payload."
msgstr ""
msgid "AlertManagement|More information"
msgstr ""
@ -25468,6 +25465,9 @@ msgstr ""
msgid "Learn more."
msgstr ""
msgid "Learn more: %{url}"
msgstr ""
msgid "LearnGitLab|%{percentage}%{percentSymbol} completed"
msgstr ""
@ -28369,19 +28369,10 @@ msgstr ""
msgid "NamespaceStorageSize|push to your repository, create pipelines, create issues or add comments. To reduce storage capacity, delete unused repositories, artifacts, wikis, issues, and pipelines."
msgstr ""
msgid "NamespaceStorage|%{name_with_link} namespace has approximately %{percent} (%{size}) namespace storage space remaining."
msgid "NamespaceStorage|%{name_with_link} is now read-only. Projects under this namespace are locked and actions are restricted."
msgstr ""
msgid "NamespaceStorage|%{name_with_link} namespace has exceeded its namespace storage limit."
msgstr ""
msgid "NamespaceStorage|%{name}(%{url}) namespace has approximately %{percent} (%{size}) namespace storage space remaining."
msgstr ""
msgid "NamespaceStorage|%{name}(%{url}) namespace has exceeded its namespace storage limit."
msgstr ""
msgid "NamespaceStorage|Action required: Approximately %{percentage_of_available_storage}%% of namespace storage remains for %{namespace_name}"
msgid "NamespaceStorage|%{name} is now read-only. Projects under this namespace are locked and actions are restricted."
msgstr ""
msgid "NamespaceStorage|Action required: Storage has been exceeded for %{namespace_name}"
@ -28390,10 +28381,37 @@ msgstr ""
msgid "NamespaceStorage|Buy more storage"
msgstr ""
msgid "NamespaceStorage|We recommend that you buy additional storage to ensure your service is not interrupted."
msgid "NamespaceStorage|If %{name_with_link} exceeds the storage quota, all projects in the namespace will be locked and actions will be restricted."
msgstr ""
msgid "NamespaceStorage|We recommend that you buy additional storage to resume normal service."
msgid "NamespaceStorage|If %{name} exceeds the storage quota, all projects in the namespace will be locked and actions will be restricted."
msgstr ""
msgid "NamespaceStorage|Learn about which actions are restricted: %{url}"
msgstr ""
msgid "NamespaceStorage|Learn about which actions become restricted: %{url}"
msgstr ""
msgid "NamespaceStorage|Manage your storage usage or purchase additional storage."
msgstr ""
msgid "NamespaceStorage|See storage usage statistics: %{url}"
msgstr ""
msgid "NamespaceStorage|Which actions are restricted?"
msgstr ""
msgid "NamespaceStorage|Which actions become restricted?"
msgstr ""
msgid "NamespaceStorage|You have used %{used_storage_percentage} of the storage quota for %{name_with_link} (%{current_size} of %{limit})."
msgstr ""
msgid "NamespaceStorage|You have used %{used_storage_percentage} of the storage quota for %{name} (%{current_size} of %{limit})."
msgstr ""
msgid "NamespaceStorage|You have used %{used_storage_percentage}%% of the storage quota for %{namespace_name}"
msgstr ""
msgid "NamespaceUserCap|Pending users must be reviewed and approved by a group owner. Learn more about %{user_caps_link_start}user caps%{link_end} and %{users_pending_approval_link_start}users pending approval%{link_end}."

View File

@ -348,7 +348,8 @@ RSpec.describe 'Login', :clean_gitlab_redis_sessions, feature_category: :system_
expect(page).to have_content('Invalid two-factor code')
end
it 'allows login with invalid code, then valid code' do
it 'allows login with invalid code, then valid code',
quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/402322' do
expect(authentication_metrics)
.to increment(:user_authenticated_counter)
.and increment(:user_two_factor_authenticated_counter)
@ -362,7 +363,8 @@ RSpec.describe 'Login', :clean_gitlab_redis_sessions, feature_category: :system_
expect(page).to have_current_path root_path, ignore_query: true
end
it 'triggers ActiveSession.cleanup for the user', quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/402322' do
it 'triggers ActiveSession.cleanup for the user',
quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/402322' do
expect(authentication_metrics)
.to increment(:user_authenticated_counter)
.and increment(:user_two_factor_authenticated_counter)

View File

@ -1,63 +0,0 @@
import { shallowMount } from '@vue/test-utils';
import axios from 'axios';
import MockAdapter from 'axios-mock-adapter';
import { nextTick } from 'vue';
import waitForPromises from 'helpers/wait_for_promises';
import MetricEmbed from '~/monitoring/components/embeds/metric_embed.vue';
import AlertMetrics from '~/vue_shared/alert_details/components/alert_metrics.vue';
jest.mock('~/monitoring/stores', () => ({
monitoringDashboard: {},
}));
jest.mock('~/monitoring/components/embeds/metric_embed.vue', () => ({
render(h) {
return h('div');
},
}));
describe('Alert Metrics', () => {
let wrapper;
const mock = new MockAdapter(axios);
function mountComponent({ props } = {}) {
wrapper = shallowMount(AlertMetrics, {
propsData: {
...props,
},
});
}
const findChart = () => wrapper.findComponent(MetricEmbed);
const findEmptyState = () => wrapper.findComponent({ ref: 'emptyState' });
afterEach(() => {
if (wrapper) {
wrapper.destroy();
}
});
afterAll(() => {
mock.restore();
});
describe('Empty state', () => {
it('should display a message when metrics dashboard url is not provided', () => {
mountComponent();
expect(findChart().exists()).toBe(false);
expect(findEmptyState().text()).toBe("Metrics weren't available in the alerts payload.");
});
});
describe('Chart', () => {
it('should be rendered when dashboard url is provided', async () => {
mountComponent({ props: { dashboardUrl: 'metrics.url' } });
await waitForPromises();
await nextTick();
expect(findEmptyState().exists()).toBe(false);
expect(findChart().exists()).toBe(true);
});
});
});

View File

@ -144,10 +144,10 @@ RSpec.describe Gitlab::Database::AsyncIndexes::MigrationHelpers, feature_categor
end
describe '#prepare_async_index_from_sql' do
let(:index_definition) { "CREATE INDEX #{index_name} ON #{table_name} USING btree(id)" }
let(:index_definition) { "CREATE INDEX CONCURRENTLY #{index_name} ON #{table_name} USING btree(id)" }
subject(:prepare_async_index_from_sql) do
migration.prepare_async_index_from_sql(table_name, index_name, index_definition)
migration.prepare_async_index_from_sql(index_definition)
end
before do
@ -162,38 +162,68 @@ RSpec.describe Gitlab::Database::AsyncIndexes::MigrationHelpers, feature_categor
expect(Gitlab::Database::QueryAnalyzers::RestrictAllowedSchemas).to have_received(:require_ddl_mode!)
end
context 'when the async index creation is not available' do
before do
connection.drop_table(:postgres_async_indexes)
end
context 'when the given index is invalid' do
let(:index_definition) { "SELECT FROM users" }
it 'does not raise an error' do
expect { prepare_async_index_from_sql }.not_to raise_error
it 'raises a RuntimeError' do
expect { prepare_async_index_from_sql }.to raise_error(RuntimeError, 'Index statement not found!')
end
end
context 'when the async index creation is available' do
context 'when there is already an index with the given name' do
before do
connection.add_index(table_name, 'id', name: index_name)
end
context 'when the given index is valid' do
context 'when the index algorithm is not concurrent' do
let(:index_definition) { "CREATE INDEX #{index_name} ON #{table_name} USING btree(id)" }
it 'does not create the async index record' do
expect { prepare_async_index_from_sql }.not_to change { index_model.where(name: index_name).count }
it 'raises a RuntimeError' do
expect { prepare_async_index_from_sql }.to raise_error(RuntimeError, 'Index must be created concurrently!')
end
end
context 'when there is no index with the given name' do
let(:async_index) { index_model.find_by(name: index_name) }
context 'when the index algorithm is concurrent' do
context 'when the statement tries to create an index for non-existing table' do
let(:index_definition) { "CREATE INDEX CONCURRENTLY #{index_name} ON foo_table USING btree(id)" }
it 'creates the async index record' do
expect { prepare_async_index_from_sql }.to change { index_model.where(name: index_name).count }.by(1)
it 'raises a RuntimeError' do
expect { prepare_async_index_from_sql }.to raise_error(RuntimeError, 'Table does not exist!')
end
end
it 'sets the async index attributes correctly' do
prepare_async_index_from_sql
context 'when the statement tries to create an index for an existing table' do
context 'when the async index creation is not available' do
before do
connection.drop_table(:postgres_async_indexes)
end
expect(async_index).to have_attributes(table_name: table_name, definition: index_definition)
it 'does not raise an error' do
expect { prepare_async_index_from_sql }.not_to raise_error
end
end
context 'when the async index creation is available' do
context 'when there is already an index with the given name' do
before do
connection.add_index(table_name, 'id', name: index_name)
end
it 'does not create the async index record' do
expect { prepare_async_index_from_sql }.not_to change { index_model.where(name: index_name).count }
end
end
context 'when there is no index with the given name' do
let(:async_index) { index_model.find_by(name: index_name) }
it 'creates the async index record' do
expect { prepare_async_index_from_sql }.to change { index_model.where(name: index_name).count }.by(1)
end
it 'sets the async index attributes correctly' do
prepare_async_index_from_sql
expect(async_index).to have_attributes(table_name: table_name, definition: index_definition)
end
end
end
end
end
end

View File

@ -151,9 +151,27 @@ RSpec.describe Notify do
it 'has the correct subject and body' do
aggregate_failures do
is_expected.to have_referable_subject(issue, reply: true)
is_expected.to have_body_text(previous_assignee.name)
is_expected.to have_body_text(assignee.name)
is_expected.to have_body_text(project_issue_path(project, issue))
is_expected.to have_body_text("Assignee changed from <strong>#{previous_assignee.name}</strong> to <strong>#{assignee.name}</strong>")
is_expected.to have_body_text(%(<a href="#{project_issue_url(project, issue)}">view it on GitLab</a>))
is_expected.to have_body_text("You're receiving this email because of your account")
end
end
context 'without new assignee' do
before do
issue.update!(assignees: [])
end
it 'uses "Unassigned" placeholder' do
is_expected.to have_body_text("Assignee changed from <strong>#{previous_assignee.name}</strong> to <strong>Unassigned</strong>")
end
end
context 'without previous assignees' do
subject { described_class.reassigned_issue_email(recipient.id, issue.id, [], current_user.id) }
it 'uses short text' do
is_expected.to have_body_text("Assignee changed to <strong>#{assignee.name}</strong>")
end
end

View File

@ -102,7 +102,7 @@ RSpec.describe CreateTestFailureIssues, feature_category: :tooling do
### Test file path
[`#{failed_test['file']}`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/#{failed_test['file']})
[`#{failed_test['file']}`](https://gitlab.com/example/gitlab/-/blob/master/#{failed_test['file']})
<!-- Don't add anything after the report list since it's updated automatically -->
### Reports (1)
@ -131,8 +131,11 @@ RSpec.describe CreateTestFailureIssues, feature_category: :tooling do
end
it 'calls CreateIssue#execute(payload)' do
stub_const("#{described_class}::FILE_BASE_URL", 'https://gitlab.com/example/gitlab/-/blob/master/')
expect(CreateIssue).to receive(:new).with(project: project, api_token: api_token)
.and_return(create_issue_stub)
expect(create_issue_stub).to receive(:execute).with(expected_create_payload).and_return(issue_stub)
creator.upsert(failed_test)
@ -152,7 +155,7 @@ RSpec.describe CreateTestFailureIssues, feature_category: :tooling do
### Test file path
[`#{failed_test['file']}`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/#{failed_test['file']})
[`#{failed_test['file']}`](https://gitlab.com/example/gitlab/-/blob/master/#{failed_test['file']})
<!-- Don't add anything after the report list since it's updated automatically -->
### Reports (1)
@ -165,7 +168,9 @@ RSpec.describe CreateTestFailureIssues, feature_category: :tooling do
double('Issue', iid: 42, title: issue_title, description: issue_description, web_url: 'issue_web_url')
end
let(:update_issue_stub) { double('UpdateIssue') }
let(:update_issue_stub) do
double('UpdateIssue', description: latest_format_issue_description)
end
let(:expected_update_payload) do
{

View File

@ -1107,19 +1107,37 @@ RSpec.describe Issues::UpdateService, :mailer, feature_category: :team_planning
end
context 'updating asssignee_id' do
it 'changes assignee' do
expect_next_instance_of(NotificationService::Async) do |service|
expect(service).to receive(:reassigned_issue).with(issue, user, [user3])
end
update_issue(assignee_ids: [user2.id])
expect(issue.reload.assignees).to eq([user2])
end
it 'does not update assignee when assignee_id is invalid' do
expect(NotificationService).not_to receive(:new)
update_issue(assignee_ids: [-1])
expect(issue.reload.assignees).to eq([user3])
end
it 'unassigns assignee when user id is 0' do
expect_next_instance_of(NotificationService::Async) do |service|
expect(service).to receive(:reassigned_issue).with(issue, user, [user3])
end
update_issue(assignee_ids: [0])
expect(issue.reload.assignees).to be_empty
end
it 'does not update assignee_id when user cannot read issue' do
expect(NotificationService).not_to receive(:new)
update_issue(assignee_ids: [create(:user).id])
expect(issue.reload.assignees).to eq([user3])
@ -1130,6 +1148,8 @@ RSpec.describe Issues::UpdateService, :mailer, feature_category: :team_planning
levels.each do |level|
it "does not update with unauthorized assignee when project is #{Gitlab::VisibilityLevel.level_name(level)}" do
expect(NotificationService).not_to receive(:new)
assignee = create(:user)
project.update!(visibility_level: level)
feature_visibility_attr = :"#{issue.model_name.plural}_access_level"