Commit 4319f961 authored by GitLab Bot's avatar GitLab Bot

Automatic merge of gitlab-org/gitlab master

parents 4f6c3fea fb32853a
...@@ -46,7 +46,7 @@ export default { ...@@ -46,7 +46,7 @@ export default {
blobGlobalId: this.fileGlobalId, blobGlobalId: this.fileGlobalId,
}); });
this.editor.onChangeContent(debounce(this.onFileChange.bind(this), 250)); this.editor.onDidChangeModelContent(debounce(this.onFileChange.bind(this), 250));
window.requestAnimationFrame(() => { window.requestAnimationFrame(() => {
if (!performance.getEntriesByName(SNIPPET_MARK_BLOBS_CONTENT).length) { if (!performance.getEntriesByName(SNIPPET_MARK_BLOBS_CONTENT).length) {
......
import $ from 'jquery'; import $ from 'jquery';
import Api from '~/api'; import Api from '~/api';
import toast from '~/vue_shared/plugins/global_toast';
import { __ } from '~/locale';
import initPopover from '~/blob/suggest_gitlab_ci_yml';
import { deprecatedCreateFlash as Flash } from '../flash'; import { deprecatedCreateFlash as Flash } from '../flash';
import FileTemplateTypeSelector from './template_selectors/type_selector'; import FileTemplateTypeSelector from './template_selectors/type_selector';
import BlobCiYamlSelector from './template_selectors/ci_yaml_selector'; import BlobCiYamlSelector from './template_selectors/ci_yaml_selector';
import DockerfileSelector from './template_selectors/dockerfile_selector'; import DockerfileSelector from './template_selectors/dockerfile_selector';
import GitignoreSelector from './template_selectors/gitignore_selector'; import GitignoreSelector from './template_selectors/gitignore_selector';
import LicenseSelector from './template_selectors/license_selector'; import LicenseSelector from './template_selectors/license_selector';
import MetricsDashboardSelector from './template_selectors/metrics_dashboard_selector'; import MetricsDashboardSelector from './template_selectors/metrics_dashboard_selector';
import toast from '~/vue_shared/plugins/global_toast';
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 }) {
......
...@@ -6,12 +6,11 @@ export function initEditorLite({ el, ...args }) { ...@@ -6,12 +6,11 @@ export function initEditorLite({ el, ...args }) {
alwaysConsumeMouseWheel: false, alwaysConsumeMouseWheel: false,
}, },
}); });
editor.createInstance({
return editor.createInstance({
el, el,
...args, ...args,
}); });
return editor;
} }
export default () => ({}); export default () => ({});
...@@ -40,9 +40,10 @@ export default class EditBlob { ...@@ -40,9 +40,10 @@ export default class EditBlob {
const MarkdownExtensionPromise = this.options.isMarkdown const MarkdownExtensionPromise = this.options.isMarkdown
? import('~/editor/editor_markdown_ext') ? import('~/editor/editor_markdown_ext')
: Promise.resolve(false); : Promise.resolve(false);
const FileTemplateExtensionPromise = import('~/editor/editor_file_template_ext');
return Promise.all([EditorPromise, MarkdownExtensionPromise]) return Promise.all([EditorPromise, MarkdownExtensionPromise, FileTemplateExtensionPromise])
.then(([EditorModule, MarkdownExtension]) => { .then(([EditorModule, MarkdownExtension, FileTemplateExtension]) => {
const EditorLite = EditorModule.default; const EditorLite = EditorModule.default;
const editorEl = document.getElementById('editor'); const editorEl = document.getElementById('editor');
const fileNameEl = const fileNameEl =
...@@ -50,18 +51,16 @@ export default class EditBlob { ...@@ -50,18 +51,16 @@ export default class EditBlob {
const fileContentEl = document.getElementById('file-content'); const fileContentEl = document.getElementById('file-content');
const form = document.querySelector('.js-edit-blob-form'); const form = document.querySelector('.js-edit-blob-form');
this.editor = new EditorLite(); const rootEditor = new EditorLite();
if (MarkdownExtension) { this.editor = rootEditor.createInstance({
this.editor.use(MarkdownExtension.default);
}
this.editor.createInstance({
el: editorEl, el: editorEl,
blobPath: fileNameEl.value, blobPath: fileNameEl.value,
blobContent: editorEl.innerText, blobContent: editorEl.innerText,
}); });
rootEditor.use([MarkdownExtension.default, FileTemplateExtension.default], this.editor);
fileNameEl.addEventListener('change', () => { fileNameEl.addEventListener('change', () => {
this.editor.updateModelLanguage(fileNameEl.value); this.editor.updateModelLanguage(fileNameEl.value);
}); });
......
import { Position } from 'monaco-editor';
export default {
navigateFileStart() {
this.setPosition(new Position(1, 1));
},
};
import { editor as monacoEditor, languages as monacoLanguages, Position, Uri } from 'monaco-editor'; import { editor as monacoEditor, languages as monacoLanguages, Uri } from 'monaco-editor';
import { DEFAULT_THEME, themes } from '~/ide/lib/themes'; import { DEFAULT_THEME, themes } from '~/ide/lib/themes';
import languages from '~/ide/lib/languages'; import languages from '~/ide/lib/languages';
import { defaultEditorOptions } from '~/ide/lib/editor_options'; import { defaultEditorOptions } from '~/ide/lib/editor_options';
...@@ -73,6 +73,7 @@ export default class Editor { ...@@ -73,6 +73,7 @@ export default class Editor {
this.instances.splice(index, 1); this.instances.splice(index, 1);
model.dispose(); model.dispose();
}); });
instance.updateModelLanguage = path => this.updateModelLanguage(path);
// Reference to the model on the editor level will go away in // Reference to the model on the editor level will go away in
// https://gitlab.com/gitlab-org/gitlab/-/issues/241023 // https://gitlab.com/gitlab-org/gitlab/-/issues/241023
...@@ -92,10 +93,6 @@ export default class Editor { ...@@ -92,10 +93,6 @@ export default class Editor {
delete this.editorEl.dataset.editorLoading; delete this.editorEl.dataset.editorLoading;
} }
onChangeContent(fn) {
return this.model.onDidChangeContent(fn);
}
updateModelLanguage(path) { updateModelLanguage(path) {
if (path === this.blobPath) return; if (path === this.blobPath) return;
this.blobPath = path; this.blobPath = path;
...@@ -107,46 +104,6 @@ export default class Editor { ...@@ -107,46 +104,6 @@ export default class Editor {
monacoEditor.setModelLanguage(this.model, id); monacoEditor.setModelLanguage(this.model, id);
} }
/**
* @deprecated do not use .getValue() directly on the editor.
* This proxy-method will be removed in https://gitlab.com/gitlab-org/gitlab/-/issues/241025
* Rather use it on the exact instance
*/
getValue() {
return this.instances[0].getValue();
}
/**
* @deprecated do not use .setValue() directly on the editor.
* This proxy-method will be removed in https://gitlab.com/gitlab-org/gitlab/-/issues/241025
* Rather use it on the exact instance
*/
setValue(val) {
this.instances[0].setValue(val);
}
/**
* @deprecated do not use .focus() directly on the editor.
* This proxy-method will be removed in https://gitlab.com/gitlab-org/gitlab/-/issues/241025
* Rather use it on the exact instance
*/
focus() {
this.instances[0].focus();
}
/**
* @deprecated do not use .updateOptions() directly on the editor.
* This proxy-method will be removed in https://gitlab.com/gitlab-org/gitlab/-/issues/241025
* Rather use it on the exact instance
*/
updateOptions(options = {}) {
this.instances[0].updateOptions(options);
}
navigateFileStart() {
this.instances[0].setPosition(new Position(1, 1));
}
use(exts = [], instance = null) { use(exts = [], instance = null) {
const extensions = Array.isArray(exts) ? exts : [exts]; const extensions = Array.isArray(exts) ? exts : [exts];
if (instance) { if (instance) {
......
...@@ -53,8 +53,10 @@ module SendFileUpload ...@@ -53,8 +53,10 @@ module SendFileUpload
private private
def image_scaling_request?(file_upload) def image_scaling_request?(file_upload)
Feature.enabled?(:dynamic_image_resizing, current_user) && avatar_safe_for_scaling?(file_upload) &&
avatar_safe_for_scaling?(file_upload) && valid_image_scaling_width? && current_user scaling_allowed_by_feature_flags?(file_upload) &&
valid_image_scaling_width? &&
current_user
end end
def avatar_safe_for_scaling?(file_upload) def avatar_safe_for_scaling?(file_upload)
...@@ -68,4 +70,17 @@ module SendFileUpload ...@@ -68,4 +70,17 @@ module SendFileUpload
def valid_image_scaling_width? def valid_image_scaling_width?
Avatarable::ALLOWED_IMAGE_SCALER_WIDTHS.include?(params[:width]&.to_i) Avatarable::ALLOWED_IMAGE_SCALER_WIDTHS.include?(params[:width]&.to_i)
end end
# We use two separate feature gates to allow image resizing.
# The first, `:dynamic_image_resizing_requester`, based on the content requester.
# Enabling it for the user would allow that user to send resizing requests for any avatar.
# The second, `:dynamic_image_resizing_owner`, based on the content owner.
# Enabling it for the user would allow anyone to send resizing requests against the mentioned user avatar only.
# This flag allows us to operate on trusted data only, more in https://gitlab.com/gitlab-org/gitlab/-/issues/241533.
# Because of this, you need to enable BOTH to serve resized image,
# as you would need at least one allowed requester and at least one allowed avatar.
def scaling_allowed_by_feature_flags?(file_upload)
Feature.enabled?(:dynamic_image_resizing_requester, current_user) &&
Feature.enabled?(:dynamic_image_resizing_owner, file_upload.model)
end
end end
...@@ -12,7 +12,7 @@ module MergedAtFilter ...@@ -12,7 +12,7 @@ module MergedAtFilter
mr_metrics_scope = mr_metrics_scope.merged_before(merged_before) if merged_before.present? mr_metrics_scope = mr_metrics_scope.merged_before(merged_before) if merged_before.present?
scope = items.joins(:metrics).merge(mr_metrics_scope) scope = items.joins(:metrics).merge(mr_metrics_scope)
scope = target_project_id_filter_on_metrics(scope) if Feature.enabled?(:improved_mr_merged_at_queries) scope = target_project_id_filter_on_metrics(scope) if Feature.enabled?(:improved_mr_merged_at_queries, default_enabled: true)
scope scope
end end
# rubocop: enable CodeReuse/ActiveRecord # rubocop: enable CodeReuse/ActiveRecord
......
---
title: Format Conan package manager in Dependency List
merge_request: 40811
author:
type: added
---
title: Retrieve security dashboard URL used on Project Severity status report from
backend
merge_request: 40801
author: Kev @KevSlashNull
type: changed
---
title: Improve Productivity Analytics and Merge Request Analytics database queries
merge_request: 40838
author:
type: performance
---
name: dynamic_image_resizing_owner
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/40606
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/241533
group: group::memory
type: development
default_enabled: false
--- ---
name: dynamic_image_resizing name: dynamic_image_resizing_requester
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/37342 introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/37342
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/233704 rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/233704
group: group::memory group: group::memory
type: development type: development
default_enabled: false default_enabled: false
\ No newline at end of file
...@@ -4,4 +4,4 @@ introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/39329 ...@@ -4,4 +4,4 @@ introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/39329
rollout_issue_url: rollout_issue_url:
group: group::analytics group: group::analytics
type: development type: development
default_enabled: false default_enabled: true
...@@ -142,6 +142,8 @@ The chosen OmniAuth provider is now active and can be used to sign in to GitLab ...@@ -142,6 +142,8 @@ The chosen OmniAuth provider is now active and can be used to sign in to GitLab
## Automatically Link Existing Users to OmniAuth Users ## Automatically Link Existing Users to OmniAuth Users
> [Introduced in GitLab 13.4.](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/36664)
You can automatically link OmniAuth users with existing GitLab users if their email addresses match by adding the following setting: You can automatically link OmniAuth users with existing GitLab users if their email addresses match by adding the following setting:
**For Omnibus installations** **For Omnibus installations**
......
...@@ -61,6 +61,7 @@ The following languages and dependency managers are supported: ...@@ -61,6 +61,7 @@ The following languages and dependency managers are supported:
| Language (package managers) | Supported files | Scan tool(s) | | Language (package managers) | Supported files | Scan tool(s) |
|----------------------------- | --------------- | ------------ | |----------------------------- | --------------- | ------------ |
| C# .NET ([NuGet](https://www.nuget.org/) 4.9+) | [`packages.lock.json`](https://docs.microsoft.com/en-us/nuget/consume-packages/package-references-in-project-files#enabling-lock-file) | [Gemnasium](https://gitlab.com/gitlab-org/security-products/gemnasium) | | C# .NET ([NuGet](https://www.nuget.org/) 4.9+) | [`packages.lock.json`](https://docs.microsoft.com/en-us/nuget/consume-packages/package-references-in-project-files#enabling-lock-file) | [Gemnasium](https://gitlab.com/gitlab-org/security-products/gemnasium) |
| C/C++ ([Conan](https://conan.io/)) | [`conan.lock`](https://docs.conan.io/en/latest/versioning/lockfiles.html) | [Gemnasium](https://gitlab.com/gitlab-org/security-products/gemnasium) |
| Java ([Gradle](https://gradle.org/), [Maven](https://maven.apache.org/)) | `build.gradle`, `build.gradle.kts`, `pom.xml` | [Gemnasium](https://gitlab.com/gitlab-org/security-products/gemnasium) | | Java ([Gradle](https://gradle.org/), [Maven](https://maven.apache.org/)) | `build.gradle`, `build.gradle.kts`, `pom.xml` | [Gemnasium](https://gitlab.com/gitlab-org/security-products/gemnasium) |
| JavaScript ([npm](https://www.npmjs.com/), [yarn](https://classic.yarnpkg.com/en/)) | `package-lock.json`, `npm-shrinkwrap.json`, `yarn.lock` | [Gemnasium](https://gitlab.com/gitlab-org/security-products/gemnasium), [Retire.js](https://retirejs.github.io/retire.js/) | | JavaScript ([npm](https://www.npmjs.com/), [yarn](https://classic.yarnpkg.com/en/)) | `package-lock.json`, `npm-shrinkwrap.json`, `yarn.lock` | [Gemnasium](https://gitlab.com/gitlab-org/security-products/gemnasium), [Retire.js](https://retirejs.github.io/retire.js/) |
| Go ([Golang](https://golang.org/)) | `go.sum` | [Gemnasium](https://gitlab.com/gitlab-org/security-products/gemnasium) | | Go ([Golang](https://golang.org/)) | `go.sum` | [Gemnasium](https://gitlab.com/gitlab-org/security-products/gemnasium) |
......
...@@ -25,7 +25,7 @@ export const computeMonthRangeData = (startDate, endDate, format = dateFormats.i ...@@ -25,7 +25,7 @@ export const computeMonthRangeData = (startDate, endDate, format = dateFormats.i
for ( for (
let dateCursor = new Date(endDate); let dateCursor = new Date(endDate);
dateCursor >= startDate; dateCursor >= startDate;
dateCursor.setMonth(dateCursor.getMonth() - 1) dateCursor.setMonth(dateCursor.getMonth(), 0)
) { ) {
const monthIndex = dateCursor.getMonth(); const monthIndex = dateCursor.getMonth();
const year = dateCursor.getFullYear(); const year = dateCursor.getFullYear();
......
...@@ -194,7 +194,7 @@ export default { ...@@ -194,7 +194,7 @@ export default {
<li v-for="project in severityGroup.projects" :key="project.id" class="gl-py-3"> <li v-for="project in severityGroup.projects" :key="project.id" class="gl-py-3">
<gl-link <gl-link
target="_blank" target="_blank"
:href="`/${project.fullPath}/-/security/dashboard`" :href="project.securityDashboardPath"
data-qa-selector="project_name_text" data-qa-selector="project_name_text"
>{{ project.nameWithNamespace }}</gl-link >{{ project.nameWithNamespace }}</gl-link
> >
......
...@@ -2,6 +2,7 @@ fragment Project on Project { ...@@ -2,6 +2,7 @@ fragment Project on Project {
id id
name name
nameWithNamespace nameWithNamespace
securityDashboardPath
fullPath fullPath
avatarUrl avatarUrl
path path
......
...@@ -62,6 +62,8 @@ module Gitlab ...@@ -62,6 +62,8 @@ module Gitlab
'Java (Maven)' 'Java (Maven)'
when 'composer' when 'composer'
'PHP (Composer)' 'PHP (Composer)'
when 'conan'
'C/C++ (Conan)'
else else
package_manager package_manager
end end
......
...@@ -2,10 +2,13 @@ import * as utils from 'ee/analytics/merge_request_analytics/utils'; ...@@ -2,10 +2,13 @@ import * as utils from 'ee/analytics/merge_request_analytics/utils';
import { expectedMonthData } from './mock_data'; import { expectedMonthData } from './mock_data';
describe('computeMonthRangeData', () => { describe('computeMonthRangeData', () => {
it('returns the data as expected', () => { const start = new Date('2020-05-17T00:00:00.000Z');
const startDate = new Date('2020-05-17T00:00:00.000Z');
const endDate = new Date('2020-07-17T00:00:00.000Z');
it.each`
startDate | endDate
${start} | ${new Date('2020-07-17T00:00:00.000Z')}
${start} | ${new Date('2020-07-31T00:00:00.000Z')}
`('returns the data as expected', ({ startDate, endDate }) => {
const monthData = utils.computeMonthRangeData(startDate, endDate); const monthData = utils.computeMonthRangeData(startDate, endDate);
expect(monthData).toStrictEqual(expectedMonthData); expect(monthData).toStrictEqual(expectedMonthData);
......
...@@ -136,7 +136,7 @@ describe('Vulnerability Severity component', () => { ...@@ -136,7 +136,7 @@ describe('Vulnerability Severity component', () => {
relatedProjects.forEach((project, i) => { relatedProjects.forEach((project, i) => {
const projectLink = findProjectName(accordion).at(i); const projectLink = findProjectName(accordion).at(i);
expect(projectLink.text()).toBe(project.nameWithNamespace); expect(projectLink.text()).toBe(project.nameWithNamespace);
expect(projectLink.attributes('href')).toBe(`/${project.fullPath}/-/security/dashboard`); expect(projectLink.attributes('href')).toBe(project.securityDashboardPath);
}); });
}); });
......
...@@ -100,6 +100,7 @@ export const generateProjectsWithSeverityCounts = () => [ ...@@ -100,6 +100,7 @@ export const generateProjectsWithSeverityCounts = () => [
name: 'Gitlab Test 1', name: 'Gitlab Test 1',
nameWithNamespace: 'Gitlab Org / Gitlab Test 1', nameWithNamespace: 'Gitlab Org / Gitlab Test 1',
fullPath: 'gitlab-org/gitlab-test-1', fullPath: 'gitlab-org/gitlab-test-1',
securityDashboardPath: '/gitlab-org/gitlab-test-1/-/security/dashboard',
vulnerabilitySeveritiesCount: { vulnerabilitySeveritiesCount: {
critical: 2, critical: 2,
high: 0, high: 0,
...@@ -114,6 +115,7 @@ export const generateProjectsWithSeverityCounts = () => [ ...@@ -114,6 +115,7 @@ export const generateProjectsWithSeverityCounts = () => [
name: 'Gitlab Test 2', name: 'Gitlab Test 2',
nameWithNamespace: 'Gitlab Org / Gitlab Test 2', nameWithNamespace: 'Gitlab Org / Gitlab Test 2',
fullPath: 'gitlab-org/gitlab-test-2', fullPath: 'gitlab-org/gitlab-test-2',
securityDashboardPath: '/gitlab-org/gitlab-test-2/-/security/dashboard',
vulnerabilitySeveritiesCount: { vulnerabilitySeveritiesCount: {
critical: 0, critical: 0,
high: 1, high: 1,
...@@ -128,6 +130,7 @@ export const generateProjectsWithSeverityCounts = () => [ ...@@ -128,6 +130,7 @@ export const generateProjectsWithSeverityCounts = () => [
name: 'Gitlab Test 3', name: 'Gitlab Test 3',
nameWithNamespace: 'Gitlab Org / Gitlab Test 3', nameWithNamespace: 'Gitlab Org / Gitlab Test 3',
fullPath: 'gitlab-org/gitlab-test-3', fullPath: 'gitlab-org/gitlab-test-3',
securityDashboardPath: '/gitlab-org/gitlab-test-3/-/security/dashboard',
vulnerabilitySeveritiesCount: { vulnerabilitySeveritiesCount: {
critical: 0, critical: 0,
high: 0, high: 0,
...@@ -142,6 +145,7 @@ export const generateProjectsWithSeverityCounts = () => [ ...@@ -142,6 +145,7 @@ export const generateProjectsWithSeverityCounts = () => [
name: 'Gitlab Test 4', name: 'Gitlab Test 4',
nameWithNamespace: 'Gitlab Org / Gitlab Test 4', nameWithNamespace: 'Gitlab Org / Gitlab Test 4',
fullPath: 'gitlab-org/gitlab-test-4', fullPath: 'gitlab-org/gitlab-test-4',
securityDashboardPath: '/gitlab-org/gitlab-test-4/-/security/dashboard',
vulnerabilitySeveritiesCount: { vulnerabilitySeveritiesCount: {
critical: 0, critical: 0,
high: 0, high: 0,
...@@ -156,6 +160,7 @@ export const generateProjectsWithSeverityCounts = () => [ ...@@ -156,6 +160,7 @@ export const generateProjectsWithSeverityCounts = () => [
name: 'Gitlab Test 5', name: 'Gitlab Test 5',
nameWithNamespace: 'Gitlab Org / Gitlab Test 5', nameWithNamespace: 'Gitlab Org / Gitlab Test 5',
fullPath: 'gitlab-org/gitlab-test-5', fullPath: 'gitlab-org/gitlab-test-5',
securityDashboardPath: '/gitlab-org/gitlab-test-5/-/security/dashboard',
vulnerabilitySeveritiesCount: { vulnerabilitySeveritiesCount: {
critical: 0, critical: 0,
high: 0, high: 0,
...@@ -170,6 +175,7 @@ export const generateProjectsWithSeverityCounts = () => [ ...@@ -170,6 +175,7 @@ export const generateProjectsWithSeverityCounts = () => [
name: 'Gitlab Test 6', name: 'Gitlab Test 6',
nameWithNamespace: 'Gitlab Org / Gitlab Test 6', nameWithNamespace: 'Gitlab Org / Gitlab Test 6',
fullPath: 'gitlab-org/gitlab-test-6', fullPath: 'gitlab-org/gitlab-test-6',
securityDashboardPath: '/gitlab-org/gitlab-test-6/-/security/dashboard',
vulnerabilitySeveritiesCount: { vulnerabilitySeveritiesCount: {
critical: 0, critical: 0,
high: 0, high: 0,
...@@ -182,8 +188,9 @@ export const generateProjectsWithSeverityCounts = () => [ ...@@ -182,8 +188,9 @@ export const generateProjectsWithSeverityCounts = () => [
{ {
id: 'gid://gitlab/Project/7', id: 'gid://gitlab/Project/7',
name: 'Gitlab Test 7', name: 'Gitlab Test 7',
nameWithNamespace: 'Gitlab Org / Gitlab Test 7', nameWithNamespace: 'Gitlab Org / Subgroup / Gitlab Test 7',
fullPath: 'gitlab-org/gitlab-test-7', fullPath: 'gitlab-org/subgroup/gitlab-test-7',
securityDashboardPath: '/gitlab-org/subgroup/gitlab-test-7/-/security/dashboard',
vulnerabilitySeveritiesCount: { vulnerabilitySeveritiesCount: {
critical: 0, critical: 0,
high: 0, high: 0,
......
...@@ -60,6 +60,7 @@ RSpec.describe Gitlab::Ci::Parsers::Security::Formatters::DependencyList do ...@@ -60,6 +60,7 @@ RSpec.describe Gitlab::Ci::Parsers::Security::Formatters::DependencyList do
'pip' | 'Python (pip)' 'pip' | 'Python (pip)'
'maven' | 'Java (Maven)' 'maven' | 'Java (Maven)'
'composer' | 'PHP (Composer)' 'composer' | 'PHP (Composer)'
'conan' | 'C/C++ (Conan)'
'' | '' '' | ''
end end
......
...@@ -71,7 +71,7 @@ module Backup ...@@ -71,7 +71,7 @@ module Backup
end end
report_success(success) report_success(success)
abort Backup::Error, 'Restore failed' unless success abort 'Restore failed' unless success
end end
protected protected
......
...@@ -49,12 +49,13 @@ RSpec.describe SendFileUpload do ...@@ -49,12 +49,13 @@ RSpec.describe SendFileUpload do
end end
shared_examples 'handles image resize requests' do shared_examples 'handles image resize requests' do
let(:headers) { double }
let(:image_requester) { build(:user) }
let(:image_owner) { build(:user) }
let(:params) do let(:params) do
{ attachment: 'avatar.png' } { attachment: 'avatar.png' }
end end
let(:headers) { double }
before do before do
allow(uploader).to receive(:image_safe_for_scaling?).and_return(true) allow(uploader).to receive(:image_safe_for_scaling?).and_return(true)
allow(uploader).to receive(:mounted_as).and_return(:avatar) allow(uploader).to receive(:mounted_as).and_return(:avatar)
...@@ -64,81 +65,110 @@ RSpec.describe SendFileUpload do ...@@ -64,81 +65,110 @@ RSpec.describe SendFileUpload do
# local or remote files # local or remote files
allow(controller).to receive(:send_file) allow(controller).to receive(:send_file)
allow(controller).to receive(:redirect_to) allow(controller).to receive(:redirect_to)
allow(controller).to receive(:current_user).and_return(image_requester)
allow(uploader).to receive(:model).and_return(image_owner)
end end
context 'when feature is enabled for current user' do context 'when boths FFs are enabled' do
let(:user) { build(:user) } before do
stub_feature_flags(dynamic_image_resizing_requester: image_requester)
stub_feature_flags(dynamic_image_resizing_owner: image_owner)
end
it_behaves_like 'handles image resize requests allowed by FFs'
end
context 'when only FF based on content requester is enabled for current user' do
before do before do
stub_feature_flags(dynamic_image_resizing: user) stub_feature_flags(dynamic_image_resizing_requester: image_requester)
allow(controller).to receive(:current_user).and_return(user) stub_feature_flags(dynamic_image_resizing_owner: false)
end end
context 'with valid width parameter' do it_behaves_like 'bypasses image resize requests not allowed by FFs'
it 'renders OK with workhorse command header' do end
expect(controller).not_to receive(:send_file)
expect(controller).to receive(:params).at_least(:once).and_return(width: '64') context 'when only FF based on content owner is enabled for requested avatar owner' do
expect(controller).to receive(:head).with(:ok) before do
expect(Gitlab::Workhorse).to receive(:send_scaled_image).with(a_string_matching('^(/.+|https://.+)'), 64, 'image/png').and_return([ stub_feature_flags(dynamic_image_resizing_requester: false)
Gitlab::Workhorse::SEND_DATA_HEADER, "send-scaled-img:faux" stub_feature_flags(dynamic_image_resizing_owner: image_owner)
])
expect(headers).to receive(:store).with(Gitlab::Workhorse::SEND_DATA_HEADER, "send-scaled-img:faux")
subject
end
end end
context 'with missing width parameter' do it_behaves_like 'bypasses image resize requests not allowed by FFs'
it 'does not write workhorse command header' do end
expect(headers).not_to receive(:store).with(Gitlab::Workhorse::SEND_DATA_HEADER, /^send-scaled-img:/)
subject context 'when both FFs are disabled' do
end before do
stub_feature_flags(dynamic_image_resizing_requester: false)
stub_feature_flags(dynamic_image_resizing_owner: false)
end end
context 'with invalid width parameter' do it_behaves_like 'bypasses image resize requests not allowed by FFs'
it 'does not write workhorse command header' do end
expect(controller).to receive(:params).at_least(:once).and_return(width: 'not a number') end
expect(headers).not_to receive(:store).with(Gitlab::Workhorse::SEND_DATA_HEADER, /^send-scaled-img:/)
shared_examples 'bypasses image resize requests not allowed by FFs' do
it 'does not write workhorse command header' do
expect(headers).not_to receive(:store).with(Gitlab::Workhorse::SEND_DATA_HEADER, /^send-scaled-img:/)
subject subject
end end
end
shared_examples 'handles image resize requests allowed by FFs' do
context 'with valid width parameter' do
it 'renders OK with workhorse command header' do
expect(controller).not_to receive(:send_file)
expect(controller).to receive(:params).at_least(:once).and_return(width: '64')
expect(controller).to receive(:head).with(:ok)
expect(Gitlab::Workhorse).to receive(:send_scaled_image).with(a_string_matching('^(/.+|https://.+)'), 64, 'image/png').and_return([
Gitlab::Workhorse::SEND_DATA_HEADER, "send-scaled-img:faux"
])
expect(headers).to receive(:store).with(Gitlab::Workhorse::SEND_DATA_HEADER, "send-scaled-img:faux")
subject
end end
end
context 'with width that is not allowed' do context 'with missing width parameter' do
it 'does not write workhorse command header' do it 'does not write workhorse command header' do
expect(controller).to receive(:params).at_least(:once).and_return(width: '63') expect(headers).not_to receive(:store).with(Gitlab::Workhorse::SEND_DATA_HEADER, /^send-scaled-img:/)
expect(headers).not_to receive(:store).with(Gitlab::Workhorse::SEND_DATA_HEADER, /^send-scaled-img:/)
subject subject
end
end end
end
context 'when image file is not an avatar' do context 'with invalid width parameter' do
it 'does not write workhorse command header' do it 'does not write workhorse command header' do
expect(uploader).to receive(:mounted_as).and_return(nil) # FileUploader is not mounted expect(controller).to receive(:params).at_least(:once).and_return(width: 'not a number')
expect(headers).not_to receive(:store).with(Gitlab::Workhorse::SEND_DATA_HEADER, /^send-scaled-img:/) expect(headers).not_to receive(:store).with(Gitlab::Workhorse::SEND_DATA_HEADER, /^send-scaled-img:/)
subject subject
end
end end
end
context 'when image file type is not considered safe for scaling' do context 'with width that is not allowed' do
it 'does not write workhorse command header' do it 'does not write workhorse command header' do
expect(uploader).to receive(:image_safe_for_scaling?).and_return(false) expect(controller).to receive(:params).at_least(:once).and_return(width: '63')
expect(headers).not_to receive(:store).with(Gitlab::Workhorse::SEND_DATA_HEADER, /^send-scaled-img:/) expect(headers).not_to receive(:store).with(Gitlab::Workhorse::SEND_DATA_HEADER, /^send-scaled-img:/)
subject subject
end
end end
end end
context 'when feature is disabled' do context 'when image file is not an avatar' do
before do it 'does not write workhorse command header' do
stub_feature_flags(dynamic_image_resizing: false) expect(uploader).to receive(:mounted_as).and_return(nil) # FileUploader is not mounted
expect(headers).not_to receive(:store).with(Gitlab::Workhorse::SEND_DATA_HEADER, /^send-scaled-img:/)
subject
end end
end
context 'when image file type is not considered safe for scaling' do
it 'does not write workhorse command header' do it 'does not write workhorse command header' do
expect(uploader).to receive(:image_safe_for_scaling?).and_return(false)
expect(headers).not_to receive(:store).with(Gitlab::Workhorse::SEND_DATA_HEADER, /^send-scaled-img:/) expect(headers).not_to receive(:store).with(Gitlab::Workhorse::SEND_DATA_HEADER, /^send-scaled-img:/)
subject subject
......
...@@ -2,12 +2,14 @@ import { shallowMount } from '@vue/test-utils'; ...@@ -2,12 +2,14 @@ import { shallowMount } from '@vue/test-utils';
import { nextTick } from 'vue'; import { nextTick } from 'vue';
import BlobEditContent from '~/blob/components/blob_edit_content.vue'; import BlobEditContent from '~/blob/components/blob_edit_content.vue';
import * as utils from '~/blob/utils'; import * as utils from '~/blob/utils';
import Editor from '~/editor/editor_lite';
jest.mock('~/editor/editor_lite'); jest.mock('~/editor/editor_lite');
describe('Blob Header Editing', () => { describe('Blob Header Editing', () => {
let wrapper; let wrapper;
const onDidChangeModelContent = jest.fn();
const updateModelLanguage = jest.fn();
const getValue = jest.fn();
const value = 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.'; const value = 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.';
const fileName = 'lorem.txt'; const fileName = 'lorem.txt';
const fileGlobalId = 'snippet_777'; const fileGlobalId = 'snippet_777';
...@@ -24,7 +26,12 @@ describe('Blob Header Editing', () => { ...@@ -24,7 +26,12 @@ describe('Blob Header Editing', () => {
} }
beforeEach(() => { beforeEach(() => {
jest.spyOn(utils, 'initEditorLite'); jest.spyOn(utils, 'initEditorLite').mockImplementation(() => ({
onDidChangeModelContent,
updateModelLanguage,
getValue,
dispose: jest.fn(),
}));
createComponent(); createComponent();
}); });
...@@ -34,8 +41,8 @@ describe('Blob Header Editing', () => { ...@@ -34,8 +41,8 @@ describe('Blob Header Editing', () => {
}); });
const triggerChangeContent = val => { const triggerChangeContent = val => {
jest.spyOn(Editor.prototype, 'getValue').mockReturnValue(val); getValue.mockReturnValue(val);
const [cb] = Editor.prototype.onChangeContent.mock.calls[0]; const [cb] = onDidChangeModelContent.mock.calls[0];
cb(); cb();
...@@ -79,12 +86,12 @@ describe('Blob Header Editing', () => { ...@@ -79,12 +86,12 @@ describe('Blob Header Editing', () => {
}); });
return nextTick().then(() => { return nextTick().then(() => {
expect(Editor.prototype.updateModelLanguage).toHaveBeenCalledWith(newFileName); expect(updateModelLanguage).toHaveBeenCalledWith(newFileName);
}); });
}); });
it('registers callback with editor onChangeContent', () => { it('registers callback with editor onChangeContent', () => {
expect(Editor.prototype.onChangeContent).toHaveBeenCalledWith(expect.any(Function)); expect(onDidChangeModelContent).toHaveBeenCalledWith(expect.any(Function));
}); });
it('emits input event when the blob content is changed', () => { it('emits input event when the blob content is changed', () => {
......
import EditBlob from '~/blob_edit/edit_blob'; import EditBlob from '~/blob_edit/edit_blob';
import EditorLite from '~/editor/editor_lite'; import EditorLite from '~/editor/editor_lite';
import MarkdownExtension from '~/editor/editor_markdown_ext'; import MarkdownExtension from '~/editor/editor_markdown_ext';
import FileTemplateExtension from '~/editor/editor_file_template_ext';
jest.mock('~/editor/editor_lite'); jest.mock('~/editor/editor_lite');
jest.mock('~/editor/editor_markdown_ext'); jest.mock('~/editor/editor_markdown_ext');
describe('Blob Editing', () => { describe('Blob Editing', () => {
const mockInstance = 'foo';
beforeEach(() => { beforeEach(() => {
setFixtures( setFixtures(
`<div class="js-edit-blob-form"><div id="file_path"></div><div id="iditor"></div><input id="file-content"></div>`, `<div class="js-edit-blob-form"><div id="file_path"></div><div id="editor"></div><input id="file-content"></div>`,
); );
jest.spyOn(EditorLite.prototype, 'createInstance').mockReturnValue(mockInstance);
}); });
const initEditor = (isMarkdown = false) => { const initEditor = (isMarkdown = false) => {
...@@ -19,13 +22,29 @@ describe('Blob Editing', () => { ...@@ -19,13 +22,29 @@ describe('Blob Editing', () => {
}); });
}; };
it('does not load MarkdownExtension by default', async () => { it('loads FileTemplateExtension by default', async () => {
await initEditor(); await initEditor();
expect(EditorLite.prototype.use).not.toHaveBeenCalled(); expect(EditorLite.prototype.use).toHaveBeenCalledWith(
expect.arrayContaining([FileTemplateExtension]),
mockInstance,
);
}); });
it('loads MarkdownExtension only for the markdown files', async () => { describe('Markdown', () => {
await initEditor(true); it('does not load MarkdownExtension by default', async () => {
expect(EditorLite.prototype.use).toHaveBeenCalledWith(MarkdownExtension); await initEditor();
expect(EditorLite.prototype.use).not.toHaveBeenCalledWith(
expect.arrayContaining([MarkdownExtension]),
mockInstance,
);
});
it('loads MarkdownExtension only for the markdown files', async () => {
await initEditor(true);
expect(EditorLite.prototype.use).toHaveBeenCalledWith(
[MarkdownExtension, FileTemplateExtension],
mockInstance,
);
});
}); });
}); });
...@@ -78,18 +78,18 @@ describe('Markdown Extension for Editor Lite', () => { ...@@ -78,18 +78,18 @@ describe('Markdown Extension for Editor Lite', () => {
selectSecondString(); selectSecondString();
instance.replaceSelectedText(expectedStr); instance.replaceSelectedText(expectedStr);
expect(editor.getValue()).toBe(`${firstLine}\n${expectedStr}\n${thirdLine}`); expect(instance.getValue()).toBe(`${firstLine}\n${expectedStr}\n${thirdLine}`);
}); });
it('prepends the supplied text if no text is selected', () => { it('prepends the supplied text if no text is selected', () => {
instance.replaceSelectedText(expectedStr); instance.replaceSelectedText(expectedStr);
expect(editor.getValue()).toBe(`${expectedStr}${firstLine}\n${secondLine}\n${thirdLine}`); expect(instance.getValue()).toBe(`${expectedStr}${firstLine}\n${secondLine}\n${thirdLine}`);
}); });
it('replaces selection with empty string if no text is supplied', () => { it('replaces selection with empty string if no text is supplied', () => {
selectSecondString(); selectSecondString();
instance.replaceSelectedText(); instance.replaceSelectedText();
expect(editor.getValue()).toBe(`${firstLine}\n\n${thirdLine}`); expect(instance.getValue()).toBe(`${firstLine}\n\n${thirdLine}`);
}); });
it('puts cursor at the end of the new string and collapses selection by default', () => { it('puts cursor at the end of the new string and collapses selection by default', () => {
......
...@@ -15,11 +15,13 @@ describe('Snippet editor', () => { ...@@ -15,11 +15,13 @@ describe('Snippet editor', () => {
const updatedMockContent = 'New Foo Bar'; const updatedMockContent = 'New Foo Bar';
const mockEditor = { const mockEditor = {
createInstance: jest.fn(),
updateModelLanguage: jest.fn(), updateModelLanguage: jest.fn(),
getValue: jest.fn().mockReturnValueOnce(updatedMockContent), getValue: jest.fn().mockReturnValueOnce(updatedMockContent),
}; };
Editor.mockImplementation(() => mockEditor); const createInstance = jest.fn().mockImplementation(() => ({ ...mockEditor }));
Editor.mockImplementation(() => ({
createInstance,
}));
function setUpFixture(name, content) { function setUpFixture(name, content) {
setHTMLFixture(` setHTMLFixture(`
...@@ -56,7 +58,7 @@ describe('Snippet editor', () => { ...@@ -56,7 +58,7 @@ describe('Snippet editor', () => {
}); });
it('correctly initializes Editor', () => { it('correctly initializes Editor', () => {
expect(mockEditor.createInstance).toHaveBeenCalledWith({ expect(createInstance).toHaveBeenCalledWith({
el: editorEl, el: editorEl,
blobPath: mockName, blobPath: mockName,
blobContent: mockContent, blobContent: mockContent,
......
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