Commit 7ebcead8 authored by GitLab Bot's avatar GitLab Bot

Add latest changes from gitlab-org/gitlab@master

parent f1a40d0d
...@@ -9,6 +9,7 @@ import GitignoreSelector from './template_selectors/gitignore_selector'; ...@@ -9,6 +9,7 @@ import GitignoreSelector from './template_selectors/gitignore_selector';
import LicenseSelector from './template_selectors/license_selector'; import LicenseSelector from './template_selectors/license_selector';
import toast from '~/vue_shared/plugins/global_toast'; import toast from '~/vue_shared/plugins/global_toast';
import { __ } from '~/locale'; import { __ } from '~/locale';
import initPopover from '~/blob/suggest_gitlab_ci_yml';
export default class FileTemplateMediator { export default class FileTemplateMediator {
constructor({ editor, currentAction, projectId }) { constructor({ editor, currentAction, projectId }) {
...@@ -128,6 +129,7 @@ export default class FileTemplateMediator { ...@@ -128,6 +129,7 @@ export default class FileTemplateMediator {
selectTemplateFile(selector, query, data) { selectTemplateFile(selector, query, data) {
const self = this; const self = this;
const { name } = selector.config; const { name } = selector.config;
const suggestCommitChanges = document.querySelector('.js-suggest-gitlab-ci-yml-commit-changes');
selector.renderLoading(); selector.renderLoading();
...@@ -146,6 +148,10 @@ export default class FileTemplateMediator { ...@@ -146,6 +148,10 @@ export default class FileTemplateMediator {
}, },
}, },
}); });
if (suggestCommitChanges) {
initPopover(suggestCommitChanges);
}
}) })
.catch(err => new Flash(`An error occurred while fetching the template: ${err}`)); .catch(err => new Flash(`An error occurred while fetching the template: ${err}`));
} }
......
<script> <script>
import { GlPopover, GlSprintf, GlButton, GlIcon } from '@gitlab/ui'; import { GlPopover, GlSprintf, GlButton, GlIcon } from '@gitlab/ui';
import Cookies from 'js-cookie'; import Cookies from 'js-cookie';
import { parseBoolean } from '~/lib/utils/common_utils'; import { parseBoolean, scrollToElement } from '~/lib/utils/common_utils';
import { s__ } from '~/locale'; import { s__ } from '~/locale';
import { glEmojiTag } from '~/emoji'; import { glEmojiTag } from '~/emoji';
const popoverStates = {
suggest_gitlab_ci_yml: {
title: s__(`suggestPipeline|1/2: Choose a template`),
content: s__(
`suggestPipeline|We recommend the %{boldStart}Code Quality%{boldEnd} template, which will add a report widget to your Merge Requests. This way you’ll learn about code quality degradations much sooner. %{footerStart} Goodbye technical debt! %{footerEnd}`,
),
emoji: glEmojiTag('wave'),
},
suggest_commit_first_project_gitlab_ci_yml: {
title: s__(`suggestPipeline|2/2: Commit your changes`),
content: s__(
`suggestPipeline|Commit the changes and your pipeline will automatically run for the first time.`,
),
},
};
export default { export default {
components: { components: {
GlPopover, GlPopover,
...@@ -17,7 +32,7 @@ export default { ...@@ -17,7 +32,7 @@ export default {
type: String, type: String,
required: true, required: true,
}, },
cssClass: { trackLabel: {
type: String, type: String,
required: true, required: true,
}, },
...@@ -33,17 +48,19 @@ export default { ...@@ -33,17 +48,19 @@ export default {
}, },
computed: { computed: {
suggestTitle() { suggestTitle() {
return s__(`suggestPipeline|1/2: Choose a template`); return popoverStates[this.trackLabel].title || '';
}, },
suggestContent() { suggestContent() {
return s__( return popoverStates[this.trackLabel].content || '';
`suggestPipeline|We recommend the %{boldStart}Code Quality%{boldEnd} template, which will add a report widget to your Merge Requests. This way you’ll learn about code quality degradations much sooner. %{footerStart} Goodbye technical debt! %{footerEnd}`,
);
}, },
emoji() { emoji() {
return glEmojiTag('wave'); return popoverStates[this.trackLabel].emoji || '';
}, },
}, },
mounted() {
if (this.trackLabel === 'suggest_commit_first_project_gitlab_ci_yml' && !this.popoverDismissed)
scrollToElement(document.querySelector(this.target));
},
methods: { methods: {
onDismiss() { onDismiss() {
this.popoverDismissed = true; this.popoverDismissed = true;
...@@ -61,13 +78,15 @@ export default { ...@@ -61,13 +78,15 @@ export default {
placement="rightbottom" placement="rightbottom"
trigger="manual" trigger="manual"
container="viewport" container="viewport"
:css-classes="[cssClass]" :css-classes="['suggest-gitlab-ci-yml', 'ml-4']"
> >
<template #title> <template #title>
<gl-button :aria-label="__('Close')" class="btn-blank float-right" @click="onDismiss"> <span v-html="suggestTitle"></span>
<gl-icon name="close" aria-hidden="true" /> <span class="ml-auto">
</gl-button> <gl-button :aria-label="__('Close')" class="btn-blank" @click="onDismiss">
{{ suggestTitle }} <gl-icon name="close" aria-hidden="true" />
</gl-button>
</span>
</template> </template>
<gl-sprintf :message="suggestContent"> <gl-sprintf :message="suggestContent">
......
...@@ -8,7 +8,7 @@ export default el => ...@@ -8,7 +8,7 @@ export default el =>
return createElement(Popover, { return createElement(Popover, {
props: { props: {
target: el.dataset.target, target: el.dataset.target,
cssClass: el.dataset.cssClass, trackLabel: el.dataset.trackLabel,
dismissKey: el.dataset.dismissKey, dismissKey: el.dataset.dismissKey,
}, },
}); });
......
...@@ -4,11 +4,13 @@ import $ from 'jquery'; ...@@ -4,11 +4,13 @@ import $ from 'jquery';
import NewCommitForm from '../new_commit_form'; import NewCommitForm from '../new_commit_form';
import EditBlob from './edit_blob'; import EditBlob from './edit_blob';
import BlobFileDropzone from '../blob/blob_file_dropzone'; import BlobFileDropzone from '../blob/blob_file_dropzone';
import initPopover from '~/blob/suggest_gitlab_ci_yml';
export default () => { export default () => {
const editBlobForm = $('.js-edit-blob-form'); const editBlobForm = $('.js-edit-blob-form');
const uploadBlobForm = $('.js-upload-blob-form'); const uploadBlobForm = $('.js-upload-blob-form');
const deleteBlobForm = $('.js-delete-blob-form'); const deleteBlobForm = $('.js-delete-blob-form');
const suggestEl = document.querySelector('.js-suggest-gitlab-ci-yml');
if (editBlobForm.length) { if (editBlobForm.length) {
const urlRoot = editBlobForm.data('relativeUrlRoot'); const urlRoot = editBlobForm.data('relativeUrlRoot');
...@@ -56,4 +58,8 @@ export default () => { ...@@ -56,4 +58,8 @@ export default () => {
if (deleteBlobForm.length) { if (deleteBlobForm.length) {
new NewCommitForm(deleteBlobForm); new NewCommitForm(deleteBlobForm);
} }
if (suggestEl) {
initPopover(suggestEl);
}
}; };
...@@ -95,7 +95,7 @@ export default { ...@@ -95,7 +95,7 @@ export default {
<li :id="groupDomId" :class="rowClass" class="group-row" @click.stop="onClickRowGroup"> <li :id="groupDomId" :class="rowClass" class="group-row" @click.stop="onClickRowGroup">
<div <div
:class="{ 'project-row-contents': !isGroup }" :class="{ 'project-row-contents': !isGroup }"
class="group-row-contents d-flex align-items-center py-2" class="group-row-contents d-flex align-items-center py-2 pr-3"
> >
<div class="folder-toggle-wrap append-right-4 d-flex align-items-center"> <div class="folder-toggle-wrap append-right-4 d-flex align-items-center">
<item-caret :is-group-open="group.isOpen" /> <item-caret :is-group-open="group.isOpen" />
...@@ -103,8 +103,8 @@ export default { ...@@ -103,8 +103,8 @@ export default {
</div> </div>
<gl-loading-icon <gl-loading-icon
v-if="group.isChildrenLoading" v-if="group.isChildrenLoading"
size="md" size="lg"
class="d-none d-sm-inline-flex flex-shrink-0 append-right-10" class="d-none d-sm-inline-flex flex-shrink-0 append-right-8"
/> />
<div <div
:class="{ 'd-sm-flex': !group.isChildrenLoading }" :class="{ 'd-sm-flex': !group.isChildrenLoading }"
......
...@@ -68,7 +68,7 @@ export default { ...@@ -68,7 +68,7 @@ export default {
data-placement="bottom" data-placement="bottom"
class="edit-group btn btn-xs no-expand" class="edit-group btn btn-xs no-expand"
> >
<icon name="settings" class="position-top-0" /> <icon name="settings" class="position-top-0 align-middle" />
</a> </a>
</div> </div>
</template> </template>
...@@ -144,9 +144,7 @@ ...@@ -144,9 +144,7 @@
.popover-header { .popover-header {
padding: $gl-padding; padding: $gl-padding;
display: flex;
.ic-close { align-items: center;
margin-top: -1em;
}
} }
} }
...@@ -50,7 +50,7 @@ module Clusters ...@@ -50,7 +50,7 @@ module Clusters
end end
def helm_api def helm_api
@helm_api ||= Gitlab::Kubernetes::Helm::Api.new(kubeclient) @helm_api ||= Gitlab::Kubernetes::Helm::API.new(kubeclient)
end end
def install_command def install_command
......
.form-actions .form-actions
= button_tag 'Commit changes', class: 'btn commit-btn js-commit-button btn-success qa-commit-button' = button_tag 'Commit changes', id: 'commit-changes', class: 'btn commit-btn js-commit-button btn-success qa-commit-button'
= link_to 'Cancel', cancel_path, = link_to 'Cancel', cancel_path,
class: 'btn btn-cancel', data: {confirm: leave_edit_message} class: 'btn btn-cancel', data: {confirm: leave_edit_message}
......
...@@ -20,7 +20,10 @@ ...@@ -20,7 +20,10 @@
required: true, class: 'form-control new-file-name js-file-path-name-input', value: params[:file_name] || (should_suggest_gitlab_ci_yml? ? '.gitlab-ci.yml' : '') required: true, class: 'form-control new-file-name js-file-path-name-input', value: params[:file_name] || (should_suggest_gitlab_ci_yml? ? '.gitlab-ci.yml' : '')
= render 'template_selectors' = render 'template_selectors'
- if should_suggest_gitlab_ci_yml? - if should_suggest_gitlab_ci_yml?
= render partial: 'suggest_gitlab_ci_yml', locals: { target: '#gitlab-ci-yml-selector', dismiss_key: "suggest_gitlab_ci_yml_#{@project.id}" } .js-suggest-gitlab-ci-yml{ data: { toggle: 'popover',
target: '#gitlab-ci-yml-selector',
track_label: 'suggest_gitlab_ci_yml',
dismiss_key: "suggest_gitlab_ci_yml_#{@project.id}" } }
.file-buttons .file-buttons
- if is_markdown - if is_markdown
......
.js-suggest-gitlab-ci-yml{ data: { toggle: 'popover',
target: target,
css_class: 'suggest-gitlab-ci-yml ml-4',
dismiss_key: dismiss_key } }
...@@ -13,3 +13,8 @@ ...@@ -13,3 +13,8 @@
= hidden_field_tag 'content', '', id: 'file-content' = hidden_field_tag 'content', '', id: 'file-content'
= render 'projects/commit_button', ref: @ref, = render 'projects/commit_button', ref: @ref,
cancel_path: project_tree_path(@project, @id) cancel_path: project_tree_path(@project, @id)
- if should_suggest_gitlab_ci_yml?
.js-suggest-gitlab-ci-yml-commit-changes{ data: { toggle: 'popover',
target: '#commit-changes',
track_label: 'suggest_commit_first_project_gitlab_ci_yml',
dismiss_key: "suggest_commit_first_project_gitlab_ci_yml_#{@project.id}" } }
---
title: Added a padding-right to items in subgroup list
merge_request: 26791
author:
type: fixed
# frozen_string_literal: true
class AddIndexServicesOnTemplate < ActiveRecord::Migration[6.0]
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
disable_ddl_transaction!
# This migration is a corrective action to add the missing
# index_services_on_template index on staging.
def up
add_concurrent_index(:services, :template) unless index_exists?(:services, :template)
end
def down
# No reverse action as this is a corrective migration.
end
end
...@@ -10,7 +10,7 @@ ...@@ -10,7 +10,7 @@
# #
# It's strongly recommended that you check this file into your version control system. # It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(version: 2020_03_06_170531) do ActiveRecord::Schema.define(version: 2020_03_09_105539) do
# These are extensions that must be enabled in order to support this database # These are extensions that must be enabled in order to support this database
enable_extension "pg_trgm" enable_extension "pg_trgm"
......
...@@ -91,7 +91,8 @@ and are as follows: ...@@ -91,7 +91,8 @@ and are as follows:
1. For `master` branch, create a pipeline (this includes on schedules, pushes, merges, etc.). 1. For `master` branch, create a pipeline (this includes on schedules, pushes, merges, etc.).
1. For tags, create a pipeline. 1. For tags, create a pipeline.
1. If `$GITLAB_INTERNAL` isn't set, don't create a pipeline. 1. If `$GITLAB_INTERNAL` isn't set, don't create a pipeline.
1. For stable, auto-deploy, and security branches, create a pipeline. 1. For stable, auto-deploy, and security branches, create a pipeline.
1. For any other cases (e.g. when pushing a branch with no MR for it), no pipeline is created.
## `rules`, `if:` conditions and `changes:` patterns ## `rules`, `if:` conditions and `changes:` patterns
......
...@@ -183,6 +183,7 @@ Continued reading: ...@@ -183,6 +183,7 @@ Continued reading:
- [Best Practices](best_practices.md) - [Best Practices](best_practices.md)
- [Testing with feature flags](feature_flags.md) - [Testing with feature flags](feature_flags.md)
- [Flows](flows.md) - [Flows](flows.md)
- [RSpec metadata/tags](rspec_metadata_tests.md)
## Where can I ask for help? ## Where can I ask for help?
......
# RSpec metadata for end-to-end tests
This is a partial list of the [RSpec metadata](https://relishapp.com/rspec/rspec-core/docs/metadata/user-defined-metadata)
(a.k.a. tags) that are used in our end-to-end tests.
<!-- Please keep the tags in alphabetical order -->
| Tag | Description |
|-|-|
| `:elasticsearch` | The test requires an Elasticsearch service. It is used by the [instance-level scenario](https://gitlab.com/gitlab-org/gitlab-qa#definitions) [`Test::Integration::Elasticsearch`](https://gitlab.com/gitlab-org/gitlab/-/blob/72b62b51bdf513e2936301cb6c7c91ec27c35b4d/qa/qa/ee/scenario/test/integration/elasticsearch.rb) to include only tests that require Elasticsearch. |
| `:orchestrated` | The GitLab instance under test may be [configured by `gitlab-qa`](https://gitlab.com/gitlab-org/gitlab-qa/-/blob/master/docs/what_tests_can_be_run.md#orchestrated-tests) to be different to the default GitLab configuration, or `gitlab-qa` may launch additional services in separate docker containers, or both. Tests tagged with `:orchestrated` are excluded when testing environments where we can't dynamically modify GitLab's configuration (for example, Staging). |
| `:quarantine` | The test has been [quarantined](https://about.gitlab.com/handbook/engineering/quality/guidelines/debugging-qa-test-failures/#quarantining-tests), will run in a separate job that only includes quarantined tests, and is allowed to fail. The test will be skipped in its regular job so that if it fails it will not hold up the pipeline. |
| `:requires_admin` | The test requires an admin account. Tests with the tag are excluded when run against Canary and Production environments. |
...@@ -4,7 +4,7 @@ type: howto ...@@ -4,7 +4,7 @@ type: howto
# How to unlock a locked user from the command line # How to unlock a locked user from the command line
After six failed login attempts a user gets in a locked state. After ten failed login attempts a user gets in a locked state.
To unlock a locked user: To unlock a locked user:
......
...@@ -744,9 +744,9 @@ workers: ...@@ -744,9 +744,9 @@ workers:
By default, all Kubernetes pods are By default, all Kubernetes pods are
[non-isolated](https://kubernetes.io/docs/concepts/services-networking/network-policies/#isolated-and-non-isolated-pods), [non-isolated](https://kubernetes.io/docs/concepts/services-networking/network-policies/#isolated-and-non-isolated-pods),
and accept traffic from any source. You can use meaning that they will accept traffic to and from any source. You can use
[NetworkPolicy](https://kubernetes.io/docs/concepts/services-networking/network-policies/) [NetworkPolicy](https://kubernetes.io/docs/concepts/services-networking/network-policies/)
to restrict connections to selected pods or namespaces. to restrict connections to and from selected pods, namespaces, and the Internet.
NOTE: **Note:** NOTE: **Note:**
You must use a Kubernetes network plugin that implements support for You must use a Kubernetes network plugin that implements support for
...@@ -767,7 +767,7 @@ networkPolicy: ...@@ -767,7 +767,7 @@ networkPolicy:
The default policy deployed by the auto deploy pipeline will allow The default policy deployed by the auto deploy pipeline will allow
traffic within a local namespace and from the `gitlab-managed-apps` traffic within a local namespace and from the `gitlab-managed-apps`
namespace. All other inbound connection will be blocked. Outbound namespace. All other inbound connection will be blocked. Outbound
traffic is not affected by the default policy. traffic (for example, to the Internet) is not affected by the default policy.
You can also provide a custom [policy specification](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.16/#networkpolicyspec-v1-networking-k8s-io) You can also provide a custom [policy specification](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.16/#networkpolicyspec-v1-networking-k8s-io)
via the `.gitlab/auto-deploy-values.yaml` file, for example: via the `.gitlab/auto-deploy-values.yaml` file, for example:
...@@ -788,6 +788,9 @@ networkPolicy: ...@@ -788,6 +788,9 @@ networkPolicy:
app.gitlab.com/managed_by: gitlab app.gitlab.com/managed_by: gitlab
``` ```
For more information on how to install Network Policies, see
[Install Cilium using GitLab CI](../../user/clusters/applications.md#install-cilium-using-gitlab-ci).
#### Web Application Firewall (ModSecurity) customization #### Web Application Firewall (ModSecurity) customization
> [Introduced](https://gitlab.com/gitlab-org/charts/auto-deploy-app/-/merge_requests/44) in GitLab 12.8. > [Introduced](https://gitlab.com/gitlab-org/charts/auto-deploy-app/-/merge_requests/44) in GitLab 12.8.
......
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
module Gitlab module Gitlab
module Kubernetes module Kubernetes
module Helm module Helm
class Api class API
def initialize(kubeclient) def initialize(kubeclient)
@kubeclient = kubeclient @kubeclient = kubeclient
@namespace = Gitlab::Kubernetes::Namespace.new( @namespace = Gitlab::Kubernetes::Namespace.new(
......
# frozen_string_literal: true
module Gitlab
module SidekiqLogging
class DeduplicationLogger
include Singleton
include LogsJobs
def log(job, deduplication_type)
payload = parse_job(job)
payload['job_status'] = 'deduplicated'
payload['message'] = "#{base_message(payload)}: deduplicated: #{deduplication_type}"
payload['deduplication_type'] = deduplication_type
Sidekiq.logger.info payload
end
end
end
end
# frozen_string_literal: true
module Gitlab
module SidekiqLogging
module LogsJobs
def base_message(payload)
"#{payload['class']} JID-#{payload['jid']}"
end
def parse_job(job)
# Error information from the previous try is in the payload for
# displaying in the Sidekiq UI, but is very confusing in logs!
job = job.except('error_backtrace', 'error_class', 'error_message')
# Add process id params
job['pid'] = ::Process.pid
job.delete('args') unless ENV['SIDEKIQ_LOG_ARGUMENTS']
job['args'] = Gitlab::Utils::LogLimitedArray.log_limited_array(job['args'].map(&:to_s)) if job['args']
job
end
end
end
end
...@@ -6,6 +6,8 @@ require 'active_record/log_subscriber' ...@@ -6,6 +6,8 @@ require 'active_record/log_subscriber'
module Gitlab module Gitlab
module SidekiqLogging module SidekiqLogging
class StructuredLogger class StructuredLogger
include LogsJobs
def call(job, queue) def call(job, queue)
started_time = get_time started_time = get_time
base_payload = parse_job(job) base_payload = parse_job(job)
...@@ -24,10 +26,6 @@ module Gitlab ...@@ -24,10 +26,6 @@ module Gitlab
private private
def base_message(payload)
"#{payload['class']} JID-#{payload['jid']}"
end
def add_instrumentation_keys!(job, output_payload) def add_instrumentation_keys!(job, output_payload)
output_payload.merge!(job.slice(*::Gitlab::InstrumentationHelper::KEYS)) output_payload.merge!(job.slice(*::Gitlab::InstrumentationHelper::KEYS))
end end
...@@ -76,20 +74,6 @@ module Gitlab ...@@ -76,20 +74,6 @@ module Gitlab
payload['completed_at'] = Time.now.utc.to_f payload['completed_at'] = Time.now.utc.to_f
end end
def parse_job(job)
# Error information from the previous try is in the payload for
# displaying in the Sidekiq UI, but is very confusing in logs!
job = job.except('error_backtrace', 'error_class', 'error_message')
# Add process id params
job['pid'] = ::Process.pid
job.delete('args') unless ENV['SIDEKIQ_LOG_ARGUMENTS']
job['args'] = Gitlab::Utils::LogLimitedArray.log_limited_array(job['args'].map(&:to_s)) if job['args']
job
end
def elapsed(t0) def elapsed(t0)
t1 = get_time t1 = get_time
{ {
......
# frozen_string_literal: true
require 'digest'
module Gitlab
module SidekiqMiddleware
module DuplicateJobs
def self.drop_duplicates?
Feature.enabled?(:drop_duplicate_sidekiq_jobs)
end
end
end
end
...@@ -66,6 +66,10 @@ module Gitlab ...@@ -66,6 +66,10 @@ module Gitlab
jid != existing_jid jid != existing_jid
end end
def droppable?
idempotent? && duplicate? && DuplicateJobs.drop_duplicates?
end
private private
attr_reader :queue_name, :strategy, :job attr_reader :queue_name, :strategy, :job
...@@ -98,6 +102,14 @@ module Gitlab ...@@ -98,6 +102,14 @@ module Gitlab
def idempotency_string def idempotency_string
"#{worker_class_name}:#{arguments.join('-')}" "#{worker_class_name}:#{arguments.join('-')}"
end end
def idempotent?
worker_class = worker_class_name.to_s.safe_constantize
return false unless worker_class
return false unless worker_class.respond_to?(:idempotent?)
worker_class.idempotent?
end
end end
end end
end end
......
...@@ -17,6 +17,11 @@ module Gitlab ...@@ -17,6 +17,11 @@ module Gitlab
job['duplicate-of'] = duplicate_job.existing_jid job['duplicate-of'] = duplicate_job.existing_jid
end end
if duplicate_job.droppable?
Gitlab::SidekiqLogging::DeduplicationLogger.instance.log(job, "dropped until executing")
return false
end
yield yield
end end
......
...@@ -24211,6 +24211,12 @@ msgstr "" ...@@ -24211,6 +24211,12 @@ msgstr ""
msgid "suggestPipeline|1/2: Choose a template" msgid "suggestPipeline|1/2: Choose a template"
msgstr "" msgstr ""
msgid "suggestPipeline|2/2: Commit your changes"
msgstr ""
msgid "suggestPipeline|Commit the changes and your pipeline will automatically run for the first time."
msgstr ""
msgid "suggestPipeline|We recommend the %{boldStart}Code Quality%{boldEnd} template, which will add a report widget to your Merge Requests. This way you’ll learn about code quality degradations much sooner. %{footerStart} Goodbye technical debt! %{footerEnd}" msgid "suggestPipeline|We recommend the %{boldStart}Code Quality%{boldEnd} template, which will add a report widget to your Merge Requests. This way you’ll learn about code quality degradations much sooner. %{footerStart} Goodbye technical debt! %{footerEnd}"
msgstr "" msgstr ""
......
import { shallowMount } from '@vue/test-utils'; import { shallowMount } from '@vue/test-utils';
import Popover from '~/blob/suggest_gitlab_ci_yml/components/popover.vue'; import Popover from '~/blob/suggest_gitlab_ci_yml/components/popover.vue';
import Cookies from 'js-cookie'; import Cookies from 'js-cookie';
import * as utils from '~/lib/utils/common_utils';
const popoverTarget = 'gitlab-ci-yml-selector'; jest.mock('~/lib/utils/common_utils', () => ({
...jest.requireActual('~/lib/utils/common_utils'),
scrollToElement: jest.fn(),
}));
const target = 'gitlab-ci-yml-selector';
const dismissKey = 'suggest_gitlab_ci_yml_99'; const dismissKey = 'suggest_gitlab_ci_yml_99';
const defaultTrackLabel = 'suggest_gitlab_ci_yml';
describe('Suggest gitlab-ci.yml Popover', () => { describe('Suggest gitlab-ci.yml Popover', () => {
let wrapper; let wrapper;
function createWrapper() { function createWrapper(trackLabel) {
wrapper = shallowMount(Popover, { wrapper = shallowMount(Popover, {
propsData: { propsData: {
target: popoverTarget, target,
cssClass: 'js-class', trackLabel,
dismissKey, dismissKey,
}, },
}); });
...@@ -25,7 +32,7 @@ describe('Suggest gitlab-ci.yml Popover', () => { ...@@ -25,7 +32,7 @@ describe('Suggest gitlab-ci.yml Popover', () => {
describe('when no dismiss cookie is set', () => { describe('when no dismiss cookie is set', () => {
beforeEach(() => { beforeEach(() => {
createWrapper(); createWrapper(defaultTrackLabel);
}); });
it('sets popoverDismissed to false', () => { it('sets popoverDismissed to false', () => {
...@@ -36,11 +43,26 @@ describe('Suggest gitlab-ci.yml Popover', () => { ...@@ -36,11 +43,26 @@ describe('Suggest gitlab-ci.yml Popover', () => {
describe('when the dismiss cookie is set', () => { describe('when the dismiss cookie is set', () => {
beforeEach(() => { beforeEach(() => {
Cookies.set(dismissKey, true); Cookies.set(dismissKey, true);
createWrapper(); createWrapper(defaultTrackLabel);
}); });
it('sets popoverDismissed to true', () => { it('sets popoverDismissed to true', () => {
expect(wrapper.vm.popoverDismissed).toEqual(true); expect(wrapper.vm.popoverDismissed).toEqual(true);
}); });
beforeEach(() => {
Cookies.remove(dismissKey);
});
});
describe('when the popover is mounted with the trackLabel of the Confirm button popover at the bottom of the page', () => {
it('calls scrollToElement so that the Confirm button and popover will be in sight', () => {
const scrollToElementSpy = jest.spyOn(utils, 'scrollToElement');
const commitTrackLabel = 'suggest_commit_first_project_gitlab_ci_yml';
createWrapper(commitTrackLabel);
expect(scrollToElementSpy).toHaveBeenCalled();
});
}); });
}); });
import Vue from 'vue';
import Vuex from 'vuex'; import Vuex from 'vuex';
import { mountComponentWithStore } from 'spec/helpers/vue_mount_component_helper'; import { mount, createLocalVue } from '@vue/test-utils';
import TreeList from '~/diffs/components/tree_list.vue'; import TreeList from '~/diffs/components/tree_list.vue';
import createStore from '~/diffs/store/modules'; import createStore from '~/diffs/store/modules';
describe('Diffs tree list component', () => { describe('Diffs tree list component', () => {
let Component; let wrapper;
let vm; const getFileRows = () => wrapper.findAll('.file-row');
const localVue = createLocalVue();
beforeAll(() => { localVue.use(Vuex);
Component = Vue.extend(TreeList);
});
beforeEach(() => {
Vue.use(Vuex);
const createComponent = state => {
const store = new Vuex.Store({ const store = new Vuex.Store({
modules: { modules: {
diffs: createStore(), diffs: createStore(),
...@@ -22,26 +17,38 @@ describe('Diffs tree list component', () => { ...@@ -22,26 +17,38 @@ describe('Diffs tree list component', () => {
}); });
// Setup initial state // Setup initial state
store.state.diffs.addedLines = 10;
store.state.diffs.removedLines = 20;
store.state.diffs.diffFiles.push('test'); store.state.diffs.diffFiles.push('test');
store.state.diffs = {
addedLines: 10,
removedLines: 20,
...store.state.diffs,
...state,
};
wrapper = mount(TreeList, {
store,
localVue,
propsData: { hideFileStats: false },
});
};
beforeEach(() => {
localStorage.removeItem('mr_diff_tree_list'); localStorage.removeItem('mr_diff_tree_list');
vm = mountComponentWithStore(Component, { store, props: { hideFileStats: false } }); createComponent();
}); });
afterEach(() => { afterEach(() => {
vm.$destroy(); wrapper.destroy();
}); });
it('renders empty text', () => { it('renders empty text', () => {
expect(vm.$el.textContent).toContain('No files found'); expect(wrapper.text()).toContain('No files found');
}); });
describe('with files', () => { describe('with files', () => {
beforeEach(done => { beforeEach(() => {
Object.assign(vm.$store.state.diffs.treeEntries, { const treeEntries = {
'index.js': { 'index.js': {
addedLines: 0, addedLines: 0,
changed: true, changed: true,
...@@ -62,64 +69,69 @@ describe('Diffs tree list component', () => { ...@@ -62,64 +69,69 @@ describe('Diffs tree list component', () => {
type: 'tree', type: 'tree',
tree: [], tree: [],
}, },
};
createComponent({
treeEntries,
tree: [treeEntries['index.js'], treeEntries.app],
}); });
vm.$store.state.diffs.tree = [
vm.$store.state.diffs.treeEntries['index.js'],
vm.$store.state.diffs.treeEntries.app,
];
vm.$nextTick(done); return wrapper.vm.$nextTick();
}); });
it('renders tree', () => { it('renders tree', () => {
expect(vm.$el.querySelectorAll('.file-row').length).toBe(2); expect(getFileRows()).toHaveLength(2);
expect(vm.$el.querySelectorAll('.file-row')[0].textContent).toContain('index.js'); expect(
expect(vm.$el.querySelectorAll('.file-row')[1].textContent).toContain('app'); getFileRows()
.at(0)
.text(),
).toContain('index.js');
expect(
getFileRows()
.at(1)
.text(),
).toContain('app');
}); });
it('hides file stats', done => { it('hides file stats', () => {
vm.hideFileStats = true; wrapper.setProps({ hideFileStats: true });
vm.$nextTick(() => {
expect(vm.$el.querySelector('.file-row-stats')).toBe(null);
done(); return wrapper.vm.$nextTick().then(() => {
expect(wrapper.find('.file-row-stats').exists()).toBe(false);
}); });
}); });
it('calls toggleTreeOpen when clicking folder', () => { it('calls toggleTreeOpen when clicking folder', () => {
spyOn(vm.$store, 'dispatch').and.stub(); jest.spyOn(wrapper.vm.$store, 'dispatch').mockReturnValue(undefined);
vm.$el.querySelectorAll('.file-row')[1].click(); getFileRows()
.at(1)
.trigger('click');
expect(vm.$store.dispatch).toHaveBeenCalledWith('diffs/toggleTreeOpen', 'app'); expect(wrapper.vm.$store.dispatch).toHaveBeenCalledWith('diffs/toggleTreeOpen', 'app');
}); });
it('calls scrollToFile when clicking blob', () => { it('calls scrollToFile when clicking blob', () => {
spyOn(vm.$store, 'dispatch').and.stub(); jest.spyOn(wrapper.vm.$store, 'dispatch').mockReturnValue(undefined);
vm.$el.querySelector('.file-row').click(); wrapper.find('.file-row').trigger('click');
expect(vm.$store.dispatch).toHaveBeenCalledWith('diffs/scrollToFile', 'app/index.js'); expect(wrapper.vm.$store.dispatch).toHaveBeenCalledWith('diffs/scrollToFile', 'app/index.js');
}); });
it('renders as file list when renderTreeList is false', done => { it('renders as file list when renderTreeList is false', () => {
vm.$store.state.diffs.renderTreeList = false; wrapper.vm.$store.state.diffs.renderTreeList = false;
vm.$nextTick(() => { return wrapper.vm.$nextTick().then(() => {
expect(vm.$el.querySelectorAll('.file-row').length).toBe(1); expect(getFileRows()).toHaveLength(1);
done();
}); });
}); });
it('renders file paths when renderTreeList is false', done => { it('renders file paths when renderTreeList is false', () => {
vm.$store.state.diffs.renderTreeList = false; wrapper.vm.$store.state.diffs.renderTreeList = false;
vm.$nextTick(() => {
expect(vm.$el.querySelector('.file-row').textContent).toContain('index.js');
done(); return wrapper.vm.$nextTick().then(() => {
expect(wrapper.find('.file-row').text()).toContain('index.js');
}); });
}); });
}); });
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
require 'spec_helper' require 'spec_helper'
describe Gitlab::Kubernetes::Helm::Api do describe Gitlab::Kubernetes::Helm::API do
let(:client) { double('kubernetes client') } let(:client) { double('kubernetes client') }
let(:helm) { described_class.new(client) } let(:helm) { described_class.new(client) }
let(:gitlab_namespace) { Gitlab::Kubernetes::Helm::NAMESPACE } let(:gitlab_namespace) { Gitlab::Kubernetes::Helm::NAMESPACE }
......
# frozen_string_literal: true
require 'spec_helper'
describe Gitlab::SidekiqLogging::DeduplicationLogger do
describe '#log_deduplication' do
let(:job) do
{
'class' => 'TestWorker',
'args' => [1234, 'hello', { 'key' => 'value' }],
'jid' => 'da883554ee4fe414012f5f42',
'correlation_id' => 'cid',
'duplicate-of' => 'other_jid'
}
end
it 'logs a deduplication message to the sidekiq logger' do
expected_payload = {
'job_status' => 'deduplicated',
'message' => "#{job['class']} JID-#{job['jid']}: deduplicated: a fancy strategy",
'deduplication_type' => 'a fancy strategy'
}
expect(Sidekiq.logger).to receive(:info).with(a_hash_including(expected_payload)).and_call_original
described_class.instance.log(job, "a fancy strategy")
end
it "does not modify the job" do
expect { described_class.instance.log(job, "a fancy strategy") }
.not_to change { job }
end
end
end
...@@ -3,6 +3,8 @@ ...@@ -3,6 +3,8 @@
require 'spec_helper' require 'spec_helper'
describe Gitlab::SidekiqMiddleware::DuplicateJobs::DuplicateJob, :clean_gitlab_redis_queues do describe Gitlab::SidekiqMiddleware::DuplicateJobs::DuplicateJob, :clean_gitlab_redis_queues do
using RSpec::Parameterized::TableSyntax
subject(:duplicate_job) do subject(:duplicate_job) do
described_class.new(job, queue) described_class.new(job, queue)
end end
...@@ -110,6 +112,36 @@ describe Gitlab::SidekiqMiddleware::DuplicateJobs::DuplicateJob, :clean_gitlab_r ...@@ -110,6 +112,36 @@ describe Gitlab::SidekiqMiddleware::DuplicateJobs::DuplicateJob, :clean_gitlab_r
end end
end end
describe 'droppable?' do
where(:idempotent, :duplicate, :feature_enabled) do
# [true, false].repeated_permutation(3)
[[true, true, true],
[true, true, false],
[true, false, true],
[true, false, false],
[false, true, true],
[false, true, false],
[false, false, true],
[false, false, false]]
end
with_them do
before do
allow(AuthorizedProjectsWorker).to receive(:idempotent?).and_return(idempotent)
allow(duplicate_job).to receive(:duplicate?).and_return(duplicate)
stub_feature_flags(drop_duplicate_sidekiq_jobs: feature_enabled)
end
it 'is droppable when all conditions are met' do
if idempotent && duplicate && feature_enabled
expect(duplicate_job).to be_droppable
else
expect(duplicate_job).not_to be_droppable
end
end
end
end
def set_idempotency_key(key, value = '1') def set_idempotency_key(key, value = '1')
Sidekiq.redis { |r| r.set(key, value) } Sidekiq.redis { |r| r.set(key, value) }
end end
......
...@@ -10,14 +10,21 @@ describe Gitlab::SidekiqMiddleware::DuplicateJobs::Strategies::UntilExecuting do ...@@ -10,14 +10,21 @@ describe Gitlab::SidekiqMiddleware::DuplicateJobs::Strategies::UntilExecuting do
subject(:strategy) { described_class.new(fake_duplicate_job) } subject(:strategy) { described_class.new(fake_duplicate_job) }
describe '#schedule' do describe '#schedule' do
before do
allow(Gitlab::SidekiqLogging::DeduplicationLogger.instance).to receive(:log)
end
it 'checks for duplicates before yielding' do it 'checks for duplicates before yielding' do
expect(fake_duplicate_job).to receive(:check!).ordered.and_return('a jid') expect(fake_duplicate_job).to receive(:check!).ordered.and_return('a jid')
expect(fake_duplicate_job).to receive(:duplicate?).ordered.and_return(false) expect(fake_duplicate_job).to receive(:duplicate?).ordered.and_return(false)
expect(fake_duplicate_job).to receive(:droppable?).ordered.and_return(false)
expect { |b| strategy.schedule({}, &b) }.to yield_control expect { |b| strategy.schedule({}, &b) }.to yield_control
end end
it 'adds the jid of the existing job to the job hash' do it 'adds the jid of the existing job to the job hash' do
allow(fake_duplicate_job).to receive(:check!).and_return('the jid') allow(fake_duplicate_job).to receive(:check!).and_return('the jid')
allow(fake_duplicate_job).to receive(:droppable?).and_return(true)
job_hash = {} job_hash = {}
expect(fake_duplicate_job).to receive(:duplicate?).and_return(true) expect(fake_duplicate_job).to receive(:duplicate?).and_return(true)
...@@ -27,6 +34,33 @@ describe Gitlab::SidekiqMiddleware::DuplicateJobs::Strategies::UntilExecuting do ...@@ -27,6 +34,33 @@ describe Gitlab::SidekiqMiddleware::DuplicateJobs::Strategies::UntilExecuting do
expect(job_hash).to include('duplicate-of' => 'the jid') expect(job_hash).to include('duplicate-of' => 'the jid')
end end
context "when the job is droppable" do
before do
allow(fake_duplicate_job).to receive(:check!).and_return('the jid')
allow(fake_duplicate_job).to receive(:duplicate?).and_return(true)
allow(fake_duplicate_job).to receive(:existing_jid).and_return('the jid')
allow(fake_duplicate_job).to receive(:droppable?).and_return(true)
end
it 'drops the job' do
schedule_result = nil
expect(fake_duplicate_job).to receive(:droppable?).and_return(true)
expect { |b| schedule_result = strategy.schedule({}, &b) }.not_to yield_control
expect(schedule_result).to be(false)
end
it 'logs that the job wass dropped' do
fake_logger = instance_double(Gitlab::SidekiqLogging::DeduplicationLogger)
expect(Gitlab::SidekiqLogging::DeduplicationLogger).to receive(:instance).and_return(fake_logger)
expect(fake_logger).to receive(:log).with(a_hash_including({ 'jid' => 'new jid' }), 'dropped until executing')
strategy.schedule({ 'jid' => 'new jid' }) {}
end
end
end end
describe '#perform' do describe '#perform' do
......
...@@ -501,64 +501,20 @@ describe Gitlab::UrlBlocker, :stub_invalid_dns_only do ...@@ -501,64 +501,20 @@ describe Gitlab::UrlBlocker, :stub_invalid_dns_only do
it_behaves_like 'dns rebinding checks' it_behaves_like 'dns rebinding checks'
end end
end end
context 'with ip ranges in whitelist' do
let(:ipv4_range) { '127.0.0.0/28' }
let(:ipv6_range) { 'fd84:6d02:f6d8:c89e::/124' }
let(:whitelist) do
[
ipv4_range,
ipv6_range
]
end
it 'blocks ipv4 range when not in whitelist' do
stub_application_setting(outbound_local_requests_whitelist: [])
IPAddr.new(ipv4_range).to_range.to_a.each do |ip|
expect(described_class).to be_blocked_url("http://#{ip}",
url_blocker_attributes)
end
end
it 'allows all ipv4s in the range when in whitelist' do
IPAddr.new(ipv4_range).to_range.to_a.each do |ip|
expect(described_class).not_to be_blocked_url("http://#{ip}",
url_blocker_attributes)
end
end
it 'blocks ipv6 range when not in whitelist' do
stub_application_setting(outbound_local_requests_whitelist: [])
IPAddr.new(ipv6_range).to_range.to_a.each do |ip|
expect(described_class).to be_blocked_url("http://[#{ip}]",
url_blocker_attributes)
end
end
it 'allows all ipv6s in the range when in whitelist' do
IPAddr.new(ipv6_range).to_range.to_a.each do |ip|
expect(described_class).not_to be_blocked_url("http://[#{ip}]",
url_blocker_attributes)
end
end
it 'blocks IPs outside the range' do
expect(described_class).to be_blocked_url("http://[fd84:6d02:f6d8:c89e:0:0:1:f]",
url_blocker_attributes)
expect(described_class).to be_blocked_url("http://127.0.1.15",
url_blocker_attributes)
end
end
end end
end end
def stub_domain_resolv(domain, ip, &block) def stub_domain_resolv(domain, ip, port = 80, &block)
address = double(ip_address: ip, ipv4_private?: true, ipv6_link_local?: false, ipv4_loopback?: false, ipv6_loopback?: false, ipv4?: false) address = instance_double(Addrinfo,
allow(Addrinfo).to receive(:getaddrinfo).with(domain, any_args).and_return([address]) ip_address: ip,
ipv4_private?: true,
ipv6_linklocal?: false,
ipv4_loopback?: false,
ipv6_loopback?: false,
ipv4?: false,
ip_port: port
)
allow(Addrinfo).to receive(:getaddrinfo).with(domain, port, any_args).and_return([address])
allow(address).to receive(:ipv6_v4mapped?).and_return(false) allow(address).to receive(:ipv6_v4mapped?).and_return(false)
yield yield
......
...@@ -68,5 +68,51 @@ describe Gitlab::UrlBlockers::UrlWhitelist do ...@@ -68,5 +68,51 @@ describe Gitlab::UrlBlockers::UrlWhitelist do
it 'returns false when ip is blank' do it 'returns false when ip is blank' do
expect(described_class).not_to be_ip_whitelisted(nil) expect(described_class).not_to be_ip_whitelisted(nil)
end end
context 'with ip ranges in whitelist' do
let(:ipv4_range) { '127.0.0.0/28' }
let(:ipv6_range) { 'fd84:6d02:f6d8:c89e::/124' }
let(:whitelist) do
[
ipv4_range,
ipv6_range
]
end
it 'does not whitelist ipv4 range when not in whitelist' do
stub_application_setting(outbound_local_requests_whitelist: [])
IPAddr.new(ipv4_range).to_range.to_a.each do |ip|
expect(described_class).not_to be_ip_whitelisted(ip.to_s)
end
end
it 'whitelists all ipv4s in the range when in whitelist' do
IPAddr.new(ipv4_range).to_range.to_a.each do |ip|
expect(described_class).to be_ip_whitelisted(ip.to_s)
end
end
it 'does not whitelist ipv6 range when not in whitelist' do
stub_application_setting(outbound_local_requests_whitelist: [])
IPAddr.new(ipv6_range).to_range.to_a.each do |ip|
expect(described_class).not_to be_ip_whitelisted(ip.to_s)
end
end
it 'whitelists all ipv6s in the range when in whitelist' do
IPAddr.new(ipv6_range).to_range.to_a.each do |ip|
expect(described_class).to be_ip_whitelisted(ip.to_s)
end
end
it 'does not whitelist IPs outside the range' do
expect(described_class).not_to be_ip_whitelisted("fd84:6d02:f6d8:c89e:0:0:1:f")
expect(described_class).not_to be_ip_whitelisted("127.0.1.15")
end
end
end end
end end
...@@ -144,7 +144,7 @@ describe Clusters::Applications::CheckInstallationProgressService, '#execute' do ...@@ -144,7 +144,7 @@ describe Clusters::Applications::CheckInstallationProgressService, '#execute' do
end end
it 'removes the installation POD' do it 'removes the installation POD' do
expect_next_instance_of(Gitlab::Kubernetes::Helm::Api) do |instance| expect_next_instance_of(Gitlab::Kubernetes::Helm::API) do |instance|
expect(instance).to receive(:delete_pod!).with(kind_of(String)).once expect(instance).to receive(:delete_pod!).with(kind_of(String)).once
end end
expect(service).to receive(:remove_installation_pod).and_call_original expect(service).to receive(:remove_installation_pod).and_call_original
......
...@@ -48,7 +48,7 @@ describe Clusters::Applications::CheckUninstallProgressService do ...@@ -48,7 +48,7 @@ describe Clusters::Applications::CheckUninstallProgressService do
let(:phase) { Gitlab::Kubernetes::Pod::SUCCEEDED } let(:phase) { Gitlab::Kubernetes::Pod::SUCCEEDED }
before do before do
expect_next_instance_of(Gitlab::Kubernetes::Helm::Api) do |instance| expect_next_instance_of(Gitlab::Kubernetes::Helm::API) do |instance|
expect(instance).to receive(:delete_pod!).with(kind_of(String)).once expect(instance).to receive(:delete_pod!).with(kind_of(String)).once
end end
expect(service).to receive(:pod_phase).once.and_return(phase) expect(service).to receive(:pod_phase).once.and_return(phase)
......
...@@ -7,7 +7,7 @@ describe Clusters::Applications::InstallService do ...@@ -7,7 +7,7 @@ describe Clusters::Applications::InstallService do
let(:application) { create(:clusters_applications_helm, :scheduled) } let(:application) { create(:clusters_applications_helm, :scheduled) }
let!(:install_command) { application.install_command } let!(:install_command) { application.install_command }
let(:service) { described_class.new(application) } let(:service) { described_class.new(application) }
let(:helm_client) { instance_double(Gitlab::Kubernetes::Helm::Api) } let(:helm_client) { instance_double(Gitlab::Kubernetes::Helm::API) }
before do before do
allow(service).to receive(:install_command).and_return(install_command) allow(service).to receive(:install_command).and_return(install_command)
......
...@@ -7,7 +7,7 @@ describe Clusters::Applications::PatchService do ...@@ -7,7 +7,7 @@ describe Clusters::Applications::PatchService do
let(:application) { create(:clusters_applications_knative, :scheduled) } let(:application) { create(:clusters_applications_knative, :scheduled) }
let!(:update_command) { application.update_command } let!(:update_command) { application.update_command }
let(:service) { described_class.new(application) } let(:service) { described_class.new(application) }
let(:helm_client) { instance_double(Gitlab::Kubernetes::Helm::Api) } let(:helm_client) { instance_double(Gitlab::Kubernetes::Helm::API) }
before do before do
allow(service).to receive(:update_command).and_return(update_command) allow(service).to receive(:update_command).and_return(update_command)
......
...@@ -5,7 +5,7 @@ require 'spec_helper' ...@@ -5,7 +5,7 @@ require 'spec_helper'
describe Clusters::Applications::UninstallService, '#execute' do describe Clusters::Applications::UninstallService, '#execute' do
let(:application) { create(:clusters_applications_prometheus, :scheduled) } let(:application) { create(:clusters_applications_prometheus, :scheduled) }
let(:service) { described_class.new(application) } let(:service) { described_class.new(application) }
let(:helm_client) { instance_double(Gitlab::Kubernetes::Helm::Api) } let(:helm_client) { instance_double(Gitlab::Kubernetes::Helm::API) }
let(:worker_class) { Clusters::Applications::WaitForUninstallAppWorker } let(:worker_class) { Clusters::Applications::WaitForUninstallAppWorker }
before do before do
......
...@@ -7,7 +7,7 @@ describe Clusters::Applications::UpgradeService do ...@@ -7,7 +7,7 @@ describe Clusters::Applications::UpgradeService do
let(:application) { create(:clusters_applications_helm, :scheduled) } let(:application) { create(:clusters_applications_helm, :scheduled) }
let!(:install_command) { application.install_command } let!(:install_command) { application.install_command }
let(:service) { described_class.new(application) } let(:service) { described_class.new(application) }
let(:helm_client) { instance_double(Gitlab::Kubernetes::Helm::Api) } let(:helm_client) { instance_double(Gitlab::Kubernetes::Helm::API) }
before do before do
allow(service).to receive(:install_command).and_return(install_command) allow(service).to receive(:install_command).and_return(install_command)
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment