Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2020-08-14 00:10:28 +00:00
parent 3825437c53
commit 968e01a6dd
21 changed files with 397 additions and 109 deletions

View file

@ -2,3 +2,4 @@
export const ESC_KEY = 'Escape';
export const ESC_KEY_IE11 = 'Esc'; // https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/key
export const ENTER_KEY = 'Enter';

View file

@ -85,7 +85,7 @@ export default {
saveButtonLabel() {
return this.isExistingRelease ? __('Save changes') : __('Create release');
},
isSaveChangesDisabled() {
isFormSubmissionDisabled() {
return this.isUpdatingRelease || !this.isValid;
},
milestoneComboboxExtraLinks() {
@ -116,13 +116,18 @@ export default {
'updateReleaseNotes',
'updateReleaseMilestones',
]),
submitForm() {
if (!this.isFormSubmissionDisabled) {
this.saveRelease();
}
},
},
};
</script>
<template>
<div class="d-flex flex-column">
<p class="pt-3 js-subtitle-text" v-html="subtitleText"></p>
<form v-if="showForm" @submit.prevent="saveRelease()">
<form v-if="showForm" class="js-quick-submit" @submit.prevent="submitForm">
<tag-field />
<gl-form-group>
<label for="release-title">{{ __('Release title') }}</label>
@ -134,7 +139,7 @@ export default {
class="form-control"
/>
</gl-form-group>
<gl-form-group class="w-50">
<gl-form-group class="w-50" @keydown.enter.prevent.capture>
<label>{{ __('Milestones') }}</label>
<div class="d-flex flex-column col-md-6 col-sm-10 pl-0">
<milestone-combobox
@ -163,8 +168,6 @@ export default {
data-supports-quick-actions="false"
:aria-label="__('Release notes')"
:placeholder="__('Write your release notes or drag your files here…')"
@keydown.meta.enter="saveRelease()"
@keydown.ctrl.enter="saveRelease()"
></textarea>
</template>
</markdown-field>
@ -179,7 +182,7 @@ export default {
category="primary"
variant="success"
type="submit"
:disabled="isSaveChangesDisabled"
:disabled="isFormSubmissionDisabled"
data-testid="submit-button"
>
{{ saveButtonLabel }}

View file

@ -49,6 +49,12 @@ export default {
this.removeAssetLink(linkId);
this.ensureAtLeastOneLink();
},
updateUrl(link, newUrl) {
this.updateAssetLinkUrl({ linkIdToUpdate: link.id, newUrl });
},
updateName(link, newName) {
this.updateAssetLinkName({ linkIdToUpdate: link.id, newName });
},
hasDuplicateUrl(link) {
return Boolean(this.getLinkErrors(link).isDuplicate);
},
@ -138,7 +144,9 @@ export default {
type="text"
class="form-control"
:state="isUrlValid(link)"
@change="updateAssetLinkUrl({ linkIdToUpdate: link.id, newUrl: $event })"
@change="updateUrl(link, $event)"
@keydown.ctrl.enter="updateUrl(link, $event.target.value)"
@keydown.meta.enter="updateUrl(link, $event.target.value)"
/>
<template #invalid-feedback>
<span v-if="hasEmptyUrl(link)" class="invalid-feedback d-inline">
@ -175,7 +183,9 @@ export default {
type="text"
class="form-control"
:state="isNameValid(link)"
@change="updateAssetLinkName({ linkIdToUpdate: link.id, newName: $event })"
@change="updateName(link, $event)"
@keydown.ctrl.enter="updateName(link, $event.target.value)"
@keydown.meta.enter="updateName(link, $event.target.value)"
/>
<template #invalid-feedback>
<span v-if="hasEmptyName(link)" class="invalid-feedback d-inline">

View file

@ -32,5 +32,8 @@ export default {
isMergeImmediatelyDangerous() {
return false;
},
shouldRenderMergeTrainHelperText() {
return false;
},
},
};

View file

@ -33,7 +33,7 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
push_frontend_feature_flag(:widget_visibility_polling, @project, default_enabled: true)
push_frontend_feature_flag(:merge_ref_head_comments, @project, default_enabled: true)
push_frontend_feature_flag(:mr_commit_neighbor_nav, @project, default_enabled: true)
push_frontend_feature_flag(:multiline_comments, @project)
push_frontend_feature_flag(:multiline_comments, @project, default_enabled: true)
push_frontend_feature_flag(:file_identifier_hash)
push_frontend_feature_flag(:batch_suggestions, @project, default_enabled: true)
push_frontend_feature_flag(:auto_expand_collapsed_diffs, @project, default_enabled: true)

View file

@ -0,0 +1,17 @@
# frozen_string_literal: true
require "json"
module Resolvers
module CiConfiguration
class SastResolver < BaseResolver
SAST_UI_SCHEMA_PATH = 'app/validators/json_schemas/security_ci_configuration_schemas/sast_ui_schema.json'
type ::Types::CiConfiguration::Sast::Type, null: true
def resolve(**args)
Gitlab::Json.parse(File.read(Rails.root.join(SAST_UI_SCHEMA_PATH)))
end
end
end
end

View file

@ -175,6 +175,10 @@ module Types
description: 'A single environment of the project',
resolver: Resolvers::EnvironmentsResolver.single
field :sast_ci_configuration, ::Types::CiConfiguration::Sast::Type, null: true,
description: 'SAST CI configuration for the project',
resolver: ::Resolvers::CiConfiguration::SastResolver
field :issue,
Types::IssueType,
null: true,

View file

@ -4,43 +4,83 @@
"field" : "SECURE_ANALYZERS_PREFIX",
"label" : "Image prefix",
"type": "string",
"default_value": "",
"value": "",
"description": "Analyzer image's registry prefix (or Name of the registry providing the analyzers' image)"
"default_value": "registry.gitlab.com/gitlab-org/security-products/analyzers",
"value": ""
},
{
"field" : "SAST_EXCLUDED_PATHS",
"label" : "Excluded Paths",
"type": "string",
"default_value": "",
"value": "",
"description": "Comma-separated list of paths to be excluded from analyzer output. Patterns can be globs, file paths, or folder paths."
"default_value": "spec, test, tests, tmp",
"value": ""
},
{
"field" : "SAST_ANALYZER_IMAGE_TAG",
"label" : "Image tag",
"type": "string",
"default_value": "",
"value": "",
"description": "Analyzer image's tag"
"options": [],
"default_value": "2",
"value": ""
},
{
"field" : "SAST_DISABLED",
"label" : "Disable SAST",
"type": "options",
"options": [
{
"value" :"true",
"label" : "true (disables SAST)"
},
{
"value":"false",
"label":"false (enables SAST)"
}
],
"default_value": "false",
"value": ""
}
],
"pipeline": [
{
"field" : "stage",
"label" : "Stage",
"type": "string",
"default_value": "",
"value": "",
"description": "Pipeline stage in which the scan jobs run"
"type": "dropdown",
"options": [
{
"value" :"test",
"label" : "test"
},
{
"value":"build",
"label":"build"
}
],
"default_value": "test",
"value": ""
},
{
"field" : "SEARCH_MAX_DEPTH",
"label" : "Search maximum depth",
"type": "string",
"field" : "allow_failure",
"label" : "Allow Failure",
"type": "options",
"options": [
{
"value" :"true",
"label" : "Allows pipeline failure"
},
{
"value": "false",
"label": "Does not allow pipeline failure"
}
],
"default_value": "true",
"value": ""
},
{
"field" : "rules",
"label" : "Rules",
"type": "multiline",
"default_value": "",
"value": "",
"description": "Maximum depth of language and framework detection"
"value": ""
}
],
"analyzers": [

View file

@ -0,0 +1,5 @@
---
title: Enable Multiline Comments by default
merge_request: 39370
author:
type: changed

View file

@ -0,0 +1,5 @@
---
title: Improve submission behavior of the New/Edit Release page
merge_request: 39145
author:
type: added

View file

@ -1,7 +1,7 @@
---
# Suggestion: gitlab.Contractions
# Suggestion: gitlab.ContractionsDiscard
#
# Checks for use of common and uncommon contractions.
# Suggests a list of agreed-upon contractions to discard.
#
# For a list of all options, see https://errata-ai.gitbook.io/vale/getting-started/styles
extends: substitution
@ -12,18 +12,6 @@ nonword: false
ignorecase: true
swap:
# Common contractions are ok
it is: it's
can not: can't
cannot: can't
do not: don't
have not: haven't
that is: that's
we are: we're
would not: wouldn't
you are: you're
you have: you've
# Uncommon contractions are not ok
aren't: are not
couldn't: could not

View file

@ -0,0 +1,25 @@
---
# Suggestion: gitlab.ContractionsKeep
#
# Suggests a list of agreed-upon contractions to keep.
#
# For a list of all options, see https://errata-ai.gitbook.io/vale/getting-started/styles
extends: substitution
message: 'Use "%s" instead of "%s", for a friendly, informal tone.'
link: https://docs.gitlab.com/ee/development/documentation/styleguide.html#language
level: suggestion
nonword: false
ignorecase: true
swap:
# Common contractions are ok
it is: it's
can not: can't
cannot: can't
do not: don't
have not: haven't
that is: that's
we are: we're
would not: wouldn't
you are: you're
you have: you've

View file

@ -80,6 +80,7 @@ Please note that the certificate [fingerprint algorithm](#additional-providers-a
With this option enabled, users must go through your group's GitLab single sign-on URL. They may also be added via SCIM, if configured. Users cannot be added manually, and may only access project/group resources via the UI by signing in through the SSO URL.
However, users will not be prompted to sign in through SSO on each visit. GitLab will check whether a user has authenticated through SSO, and will only prompt the user to sign in via SSO if the session has expired.
You can see more information about how long a session is valid in our [user profile documentation](../../profile/#why-do-i-keep-getting-signed-out).
We intend to add a similar SSO requirement for [Git and API activity](https://gitlab.com/gitlab-org/gitlab/-/issues/9152).

View file

@ -151,11 +151,12 @@ in a Merge Request. To do so, click the **{comment}** **comment** icon in the gu
### Commenting on multiple lines
> - [Introduced](https://gitlab.com/gitlab-org/ux-research/-/issues/870) in GitLab 13.2.
> - It's deployed behind a feature flag, disabled by default.
> - It's deployed behind a feature flag, enabled by default.
> - [Became enabled by default](https://gitlab.com/gitlab-org/gitlab/-/issues/221268) on GitLab 13.3.
> - It's enabled on GitLab.com.
> - It can be enabled or disabled per-project.
> - It can be disabled or enabled per-project.
> - It's recommended for production use.
> - For GitLab self-managed instances, GitLab administrators can opt to [enable it](#enable-or-disable-multiline-comments-core-only). **(CORE ONLY)**
> - For GitLab self-managed instances, GitLab administrators can opt to [disable it](#enable-or-disable-multiline-comments-core-only). **(CORE ONLY)**
GitLab provides a way to select which lines of code a comment refers to. After starting a comment
a dropdown selector is shown to select the first line that this comment refers to.
@ -178,18 +179,18 @@ It is deployed behind a feature flag that is **disabled by default**.
[GitLab administrators with access to the GitLab Rails console](../../../administration/feature_flags.md)
can opt to enable it for your instance.
To enable it:
```ruby
Feature.enable(:multiline_comments)
```
To disable it:
```ruby
Feature.disable(:multiline_comments)
```
To enable it:
```ruby
Feature.enable(:multiline_comments)
```
## Pipeline status in merge requests widgets
If you've set up [GitLab CI/CD](../../../ci/README.md) in your project,

View file

@ -36,7 +36,7 @@ RSpec.describe 'Global search' do
end
end
it 'closes the dropdown on blur', :js do
it 'closes the dropdown on blur', :js, quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/201841' do
fill_in 'search', with: "a"
dropdown = find('.js-dashboard-search-options')

View file

@ -83,11 +83,10 @@ describe('Release edit/new component', () => {
});
const findSubmitButton = () => wrapper.find('button[type=submit]');
const findForm = () => wrapper.find('form');
describe(`basic functionality tests: all tests unrelated to the "${BACK_URL_PARAM}" parameter`, () => {
beforeEach(() => {
factory();
});
beforeEach(factory);
it('calls initializeRelease when the component is created', () => {
expect(actions.initializeRelease).toHaveBeenCalledTimes(1);
@ -122,15 +121,14 @@ describe('Release edit/new component', () => {
});
it('calls saveRelease when the form is submitted', () => {
wrapper.find('form').trigger('submit');
findForm().trigger('submit');
expect(actions.saveRelease).toHaveBeenCalledTimes(1);
});
});
describe(`when the URL does not contain a "${BACK_URL_PARAM}" parameter`, () => {
beforeEach(() => {
factory();
});
beforeEach(factory);
it(`renders a "Cancel" button with an href pointing to "${BACK_URL_PARAM}"`, () => {
const cancelButton = wrapper.find('.js-cancel-button');
@ -246,6 +244,12 @@ describe('Release edit/new component', () => {
it('renders the submit button as disabled', () => {
expect(findSubmitButton().attributes('disabled')).toBe('disabled');
});
it('does not allow the form to be submitted', () => {
findForm().trigger('submit');
expect(actions.saveRelease).not.toHaveBeenCalled();
});
});
});
});

View file

@ -3,6 +3,7 @@ import { mount, createLocalVue } from '@vue/test-utils';
import AssetLinksForm from '~/releases/components/asset_links_form.vue';
import { release as originalRelease } from '../mock_data';
import * as commonUtils from '~/lib/utils/common_utils';
import { ENTER_KEY } from '~/lib/utils/keys';
import { ASSET_LINK_TYPE, DEFAULT_ASSET_LINK_TYPE } from '~/releases/constants';
const localVue = createLocalVue();
@ -91,42 +92,128 @@ describe('Release edit component', () => {
expect(actions.removeAssetLink).toHaveBeenCalledTimes(1);
});
it('calls the "updateAssetLinkUrl" store method when text is entered into the "URL" input field', () => {
const linkIdToUpdate = release.assets.links[0].id;
const newUrl = 'updated url';
describe('URL input field', () => {
let input;
let linkIdToUpdate;
let newUrl;
expect(actions.updateAssetLinkUrl).not.toHaveBeenCalled();
beforeEach(() => {
input = wrapper.find({ ref: 'urlInput' }).element;
linkIdToUpdate = release.assets.links[0].id;
newUrl = 'updated url';
});
wrapper.find({ ref: 'urlInput' }).vm.$emit('change', newUrl);
const expectStoreMethodNotToBeCalled = () => {
expect(actions.updateAssetLinkUrl).not.toHaveBeenCalled();
};
expect(actions.updateAssetLinkUrl).toHaveBeenCalledTimes(1);
expect(actions.updateAssetLinkUrl).toHaveBeenCalledWith(
expect.anything(),
{
linkIdToUpdate,
newUrl,
},
undefined,
);
const dispatchKeydowEvent = eventParams => {
const event = new KeyboardEvent('keydown', eventParams);
input.dispatchEvent(event);
};
const expectStoreMethodToBeCalled = () => {
expect(actions.updateAssetLinkUrl).toHaveBeenCalledTimes(1);
expect(actions.updateAssetLinkUrl).toHaveBeenCalledWith(
expect.anything(),
{
linkIdToUpdate,
newUrl,
},
undefined,
);
};
it('calls the "updateAssetLinkUrl" store method when text is entered into the "URL" input field', () => {
expectStoreMethodNotToBeCalled();
wrapper.find({ ref: 'urlInput' }).vm.$emit('change', newUrl);
expectStoreMethodToBeCalled();
});
it('calls the "updateAssetLinkUrl" store method when Ctrl+Enter is pressed inside the "URL" input field', () => {
expectStoreMethodNotToBeCalled();
input.value = newUrl;
dispatchKeydowEvent({ key: ENTER_KEY, ctrlKey: true });
expectStoreMethodToBeCalled();
});
it('calls the "updateAssetLinkUrl" store method when Cmd+Enter is pressed inside the "URL" input field', () => {
expectStoreMethodNotToBeCalled();
input.value = newUrl;
dispatchKeydowEvent({ key: ENTER_KEY, metaKey: true });
expectStoreMethodToBeCalled();
});
});
it('calls the "updateAssetLinkName" store method when text is entered into the "Link title" input field', () => {
const linkIdToUpdate = release.assets.links[0].id;
const newName = 'updated name';
describe('Link title field', () => {
let input;
let linkIdToUpdate;
let newName;
expect(actions.updateAssetLinkName).not.toHaveBeenCalled();
beforeEach(() => {
input = wrapper.find({ ref: 'nameInput' }).element;
linkIdToUpdate = release.assets.links[0].id;
newName = 'updated name';
});
wrapper.find({ ref: 'nameInput' }).vm.$emit('change', newName);
const expectStoreMethodNotToBeCalled = () => {
expect(actions.updateAssetLinkUrl).not.toHaveBeenCalled();
};
expect(actions.updateAssetLinkName).toHaveBeenCalledTimes(1);
expect(actions.updateAssetLinkName).toHaveBeenCalledWith(
expect.anything(),
{
linkIdToUpdate,
newName,
},
undefined,
);
const dispatchKeydowEvent = eventParams => {
const event = new KeyboardEvent('keydown', eventParams);
input.dispatchEvent(event);
};
const expectStoreMethodToBeCalled = () => {
expect(actions.updateAssetLinkName).toHaveBeenCalledTimes(1);
expect(actions.updateAssetLinkName).toHaveBeenCalledWith(
expect.anything(),
{
linkIdToUpdate,
newName,
},
undefined,
);
};
it('calls the "updateAssetLinkName" store method when text is entered into the "Link title" input field', () => {
expectStoreMethodNotToBeCalled();
wrapper.find({ ref: 'nameInput' }).vm.$emit('change', newName);
expectStoreMethodToBeCalled();
});
it('calls the "updateAssetLinkName" store method when Ctrl+Enter is pressed inside the "Link title" input field', () => {
expectStoreMethodNotToBeCalled();
input.value = newName;
dispatchKeydowEvent({ key: ENTER_KEY, ctrlKey: true });
expectStoreMethodToBeCalled();
});
it('calls the "updateAssetLinkName" store method when Cmd+Enter is pressed inside the "Link title" input field', () => {
expectStoreMethodNotToBeCalled();
input.value = newName;
dispatchKeydowEvent({ key: ENTER_KEY, metaKey: true });
expectStoreMethodToBeCalled();
});
});
it('calls the "updateAssetLinkType" store method when an option is selected from the "Type" dropdown', () => {

View file

@ -0,0 +1,28 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Resolvers::CiConfiguration::SastResolver do
include GraphqlHelpers
let_it_be(:user) { create(:user) }
let_it_be(:project) { create(:project) }
describe '#resolve' do
subject(:sast_config) { resolve(described_class, ctx: { current_user: user }, obj: project) }
it 'returns global variable informations related to SAST' do
expect(sast_config['global'].first['field']).to eql("SECURE_ANALYZERS_PREFIX")
expect(sast_config['global'].first['label']).to eql("Image prefix")
expect(sast_config['global'].first['type']).to eql("string")
expect(sast_config['pipeline'].first['field']).to eql("stage")
expect(sast_config['pipeline'].first['label']).to eql("Stage")
expect(sast_config['pipeline'].first['type']).to eql("dropdown")
expect(sast_config['analyzers'].first['name']).to eql("brakeman")
expect(sast_config['analyzers'].first['label']).to eql("Brakeman")
expect(sast_config['analyzers'].first['enabled']).to be true
end
end
end

View file

@ -26,7 +26,7 @@ RSpec.describe GitlabSchema.types['Project'] do
grafanaIntegration autocloseReferencedIssues suggestion_commit_message environments
environment boards jira_import_status jira_imports services releases release
alert_management_alerts alert_management_alert alert_management_alert_status_counts
container_expiration_policy service_desk_enabled service_desk_address
container_expiration_policy sast_ci_configuration service_desk_enabled service_desk_address
issue_status_counts
]
@ -150,5 +150,93 @@ RSpec.describe GitlabSchema.types['Project'] do
it { is_expected.to have_graphql_type(Types::ContainerExpirationPolicyType) }
end
describe 'sast_ci_configuration' do
let_it_be(:project) { create(:project) }
let_it_be(:user) { create(:user) }
let_it_be(:query) do
%(
query {
project(fullPath: "#{project.full_path}") {
sastCiConfiguration {
global {
nodes {
type
options {
nodes {
label
value
}
}
field
label
defaultValue
value
}
}
pipeline {
nodes {
type
options {
nodes {
label
value
}
}
field
label
defaultValue
value
}
}
analyzers {
nodes {
name
label
enabled
}
}
}
}
}
)
end
subject { GitlabSchema.execute(query, context: { current_user: user }).as_json }
before do
project.add_developer(user)
end
it "returns the project's sast configuration for global variables" do
query_result = subject.dig('data', 'project', 'sastCiConfiguration', 'global', 'nodes')
first_config = query_result.first
fourth_config = query_result[3]
expect(first_config['type']).to eq('string')
expect(first_config['field']).to eq('SECURE_ANALYZERS_PREFIX')
expect(first_config['label']).to eq('Image prefix')
expect(first_config['defaultValue']).to eq('registry.gitlab.com/gitlab-org/security-products/analyzers')
expect(first_config['value']).to eq('')
expect(first_config['options']).to be_nil
expect(fourth_config['options']['nodes']).to match([{ "value" => "true", "label" => "true (disables SAST)" },
{ "value" => "false", "label" => "false (enables SAST)" }])
end
it "returns the project's sast configuration for pipeline variables" do
configuration = subject.dig('data', 'project', 'sastCiConfiguration', 'pipeline', 'nodes').first
expect(configuration['type']).to eq('dropdown')
expect(configuration['field']).to eq('stage')
expect(configuration['label']).to eq('Stage')
expect(configuration['defaultValue']).to eq('test')
expect(configuration['value']).to eq('')
end
it "returns the project's sast configuration for analyzer variables" do
configuration = subject.dig('data', 'project', 'sastCiConfiguration', 'analyzers', 'nodes').first
expect(configuration['name']).to eq('brakeman')
expect(configuration['label']).to eq('Brakeman')
expect(configuration['enabled']).to eq(true)
end
end
it_behaves_like 'a GraphQL type with labels'
end

View file

@ -1,13 +0,0 @@
include:
- template: SAST.gitlab-ci.yml
variables:
SECURE_ANALYZERS_PREFIX: "registry.gitlab.com/gitlab-org/security-products/analyzers2"
SAST_EXCLUDED_PATHS: "spec, executables"
stages:
- our_custom_security_stage
sast:
stage: our_custom_security_stage
variables:
SEARCH_MAX_DEPTH: 8

View file

@ -1,9 +0,0 @@
# frozen_string_literal: true
RSpec.shared_context 'read ci configuration for sast enabled project' do
let_it_be(:gitlab_ci_yml_content) do
File.read(Rails.root.join('spec/support/gitlab_stubs/gitlab_ci_for_sast.yml'))
end
let_it_be(:project) { create(:project, :repository) }
end