Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2021-11-26 18:12:26 +00:00
parent 74015980b5
commit a4aa229f76
14 changed files with 244 additions and 94 deletions

View file

@ -954,6 +954,17 @@ entry.
- [Cleanup bigint conversion for ci_builds](gitlab-org/gitlab@176992aa2b2e76b22637a07d5bafbd6541324a7d) ([merge request](gitlab-org/gitlab!70351))
- [Drop support for data-track-event](gitlab-org/gitlab@ac6027fbef6adf41643412a84945fda6f15c9666) ([merge request](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/70234))
## 14.3.5 (2021-11-26)
### Fixed (6 changes)
- [Allow SSO callbacks through maintenance mode](gitlab-org/gitlab@f9b250145ac3db5fb01698839f1b5f3d9d349945) ([merge request](gitlab-org/gitlab!75145)) **GitLab Enterprise Edition**
- [Geo - Fix no repo error message for group-level wikis](gitlab-org/gitlab@5560e012bd6f35431d4d1cea389807ca475c2ce5) ([merge request](gitlab-org/gitlab!75145)) **GitLab Enterprise Edition**
- [Prevent Git operations from checking replication lag on non-Geo-secondary sites](gitlab-org/gitlab@35344d81d5e07bfb4db997be6c4f99f39a16562e) ([merge request](gitlab-org/gitlab!75145)) **GitLab Enterprise Edition**
- [Fix error 500 loading branch with UTF-8 characters with performance bar](gitlab-org/gitlab@08b47c2870cc338021bb3f945ac6736e46abf376) ([merge request](gitlab-org/gitlab!75145))
- [Remove defaultAuthors from MR Analytics and VSA](gitlab-org/gitlab@be95c921623056d31e2cbc0a7bc96de3aa66ca65) ([merge request](gitlab-org/gitlab!75145))
- [Allow SSO callbacks through maintenance mode](gitlab-org/gitlab@0727751512d41537356b295d6b889e05c6a07480) ([merge request](gitlab-org/gitlab!74706)) **GitLab Enterprise Edition**
## 14.3.4 (2021-10-28)
### Security (13 changes)
@ -1520,6 +1531,21 @@ entry.
- [Remove the FF ci_reset_bridge_with_subsequent_jobs](gitlab-org/gitlab@a4a75095b9b0250d0b1bdadea90c8a4cd24449b2) ([merge request](gitlab-org/gitlab!68295))
- [Removes ci_same_stage_job_needs ff](gitlab-org/gitlab@5e509cf7aa90041a541b19dda563120a359f0bf9) ([merge request](gitlab-org/gitlab!68041))
## 14.2.7 (2021-11-26)
### Fixed (3 changes)
- [Prevent Git operations from checking replication lag on non-Geo-secondary sites](gitlab-org/gitlab@84734dab92e0bf9e304ee7bf1579346cc48d26c3) ([merge request](gitlab-org/gitlab!75119)) **GitLab Enterprise Edition**
- [Remove defaultAuthors from MR Analytics and VSA](gitlab-org/gitlab@1a15d4d1be939a9e38124827f563ed9ec2612a75) ([merge request](gitlab-org/gitlab!75119))
- [Let non-members set confidential flag when creating an issue in public project](gitlab-org/gitlab@d093cc62e6263629b36a449c9464d9b8644d4d74) ([merge request](gitlab-org/gitlab!75119))
### Changed (4 changes)
- [Geo: Alternate redownload and normal design sync attempts](gitlab-org/gitlab@d401e3ec94e6dba9ea76a9682893352f28d446cb) ([merge request](gitlab-org/gitlab!75119)) **GitLab Enterprise Edition**
- [Geo: Alternate redownload and normal SSF sync attempts](gitlab-org/gitlab@00eeff14a9bfeabe9107fc38ce9d7d2eee06384b) ([merge request](gitlab-org/gitlab!75119)) **GitLab Enterprise Edition**
- [Geo: Alternate redownload and normal project syncs](gitlab-org/gitlab@fac9bb8c11db13d34b311f4ebd18f84fa7a575d3) ([merge request](gitlab-org/gitlab!75119)) **GitLab Enterprise Edition**
- [Geo: Reduce frequency of redownload attempts](gitlab-org/gitlab@d18381e4788a8652d3e36cec5d4bce343c48209c) ([merge request](gitlab-org/gitlab!75119)) **GitLab Enterprise Edition**
## 14.2.6 (2021-10-28)
### Security (13 changes)

View file

@ -87,7 +87,7 @@ export default {
try {
const {
data: {
commitCreate: { errors },
commitCreate: { errors, commitPipelinePath: pipelineEtag },
},
} = await this.$apollo.mutate({
mutation: commitCIFile,
@ -101,14 +101,12 @@ export default {
content: this.ciFileContent,
lastCommitId: this.commitSha,
},
update(_, { data }) {
const pipelineEtag = data?.commitCreate?.commit?.commitPipelinePath;
if (pipelineEtag) {
this.$apollo.mutate({ mutation: updatePipelineEtag, variables: pipelineEtag });
}
},
});
if (pipelineEtag) {
this.updatePipelineEtag(pipelineEtag);
}
if (errors?.length) {
this.$emit('showError', { type: COMMIT_FAILURE, reasons: errors });
} else if (openMergeRequest) {
@ -139,6 +137,9 @@ export default {
variables: { lastCommitBranch },
});
},
updatePipelineEtag(pipelineEtag) {
this.$apollo.mutate({ mutation: updatePipelineEtag, variables: { pipelineEtag } });
},
},
};
</script>

View file

@ -19,7 +19,9 @@ mutation commitCIFile(
]
}
) {
__typename
commit {
__typename
sha
}
commitPipelinePath

View file

@ -1,6 +1,7 @@
<script>
import { GlTab, GlTabs, GlSprintf, GlLink, GlAlert } from '@gitlab/ui';
import { __, s__ } from '~/locale';
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import LocalStorageSync from '~/vue_shared/components/local_storage_sync.vue';
import UserCalloutDismisser from '~/vue_shared/components/user_callout_dismisser.vue';
import AutoDevOpsAlert from './auto_dev_ops_alert.vue';
@ -23,6 +24,8 @@ export const i18n = {
any subsequent feature branch you create will include the scan.`,
),
securityConfiguration: __('Security Configuration'),
vulnerabilityManagement: s__('SecurityConfiguration|Vulnerability Management'),
securityTraining: s__('SecurityConfiguration|Security training'),
};
export default {
@ -41,6 +44,7 @@ export default {
UpgradeBanner,
UserCalloutDismisser,
},
mixins: [glFeatureFlagsMixin()],
inject: ['projectPath'],
props: {
augmentedSecurityFeatures: {
@ -231,6 +235,13 @@ export default {
</template>
</section-layout>
</gl-tab>
<gl-tab
v-if="glFeatures.secureVulnerabilityTraining"
data-testid="vulnerability-management-tab"
:title="$options.i18n.vulnerabilityManagement"
>
<section-layout :heading="$options.i18n.securityTraining" />
</gl-tab>
</gl-tabs>
</article>
</template>

View file

@ -0,0 +1,8 @@
---
name: secure_vulnerability_training
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/issues/346074
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/346308
milestone: '14.6'
type: development
group: group::threat insights
default_enabled: false

View file

@ -0,0 +1,16 @@
# frozen_string_literal: true
class RemoveTestReportRequirementIssueConstraint < Gitlab::Database::Migration[1.0]
disable_ddl_transaction!
TARGET_TABLE = :requirements_management_test_reports
CONSTRAINT_NAME = 'requirements_test_reports_requirement_id_xor_issue_id'
def up
remove_check_constraint TARGET_TABLE, CONSTRAINT_NAME
end
def down
add_check_constraint(TARGET_TABLE, 'num_nonnulls(requirement_id, issue_id) = 1', CONSTRAINT_NAME)
end
end

View file

@ -0,0 +1 @@
adb95bc78104382fb1d3af2c2775b4b5bd23394b4260c3a97667b4bd7917e0da

View file

@ -18888,8 +18888,7 @@ CREATE TABLE requirements_management_test_reports (
author_id bigint,
state smallint NOT NULL,
build_id bigint,
issue_id bigint,
CONSTRAINT requirements_test_reports_requirement_id_xor_issue_id CHECK ((num_nonnulls(requirement_id, issue_id) = 1))
issue_id bigint
);
CREATE SEQUENCE requirements_management_test_reports_id_seq

View file

@ -30836,6 +30836,9 @@ msgstr ""
msgid "SecurityConfiguration|Security testing"
msgstr ""
msgid "SecurityConfiguration|Security training"
msgstr ""
msgid "SecurityConfiguration|The status of the tools only applies to the default branch and is based on the %{linkStart}latest pipeline%{linkEnd}."
msgstr ""
@ -30845,6 +30848,9 @@ msgstr ""
msgid "SecurityConfiguration|Using custom settings. You won't receive automatic updates on this variable. %{anchorStart}Restore to default%{anchorEnd}"
msgstr ""
msgid "SecurityConfiguration|Vulnerability Management"
msgstr ""
msgid "SecurityConfiguration|Vulnerability details and statistics in the merge request"
msgstr ""
@ -37493,6 +37499,9 @@ msgstr ""
msgid "UsageQuota|Packages"
msgstr ""
msgid "UsageQuota|Pending Members"
msgstr ""
msgid "UsageQuota|Pipeline artifacts and job artifacts, created with CI/CD."
msgstr ""

View file

@ -10,8 +10,11 @@ module Gitlab
link :buy_ci_minutes, text: 'Buy additional minutes'
link :buy_storage, text: /Purchase more storage/
strong :additional_minutes, text: 'Additional minutes'
div :purchased_usage, 'data-testid': 'purchased-usage'
div(:additional_minutes_usage) { additional_minutes_element.following_sibling.span }
div :purchase_successful_alert, text: /You have successfully purchased CI minutes/
div :ci_purchase_successful_alert, text: /You have successfully purchased CI minutes/
div :storage_purchase_successful_alert, text: /You have successfully purchased a storage/
h4 :storage_available_alert, text: /purchased storage is available/
def plan_minutes_limits
plan_minutes_usage[%r{([^/ ]+)$}]
@ -20,6 +23,23 @@ module Gitlab
def additional_limits
additional_minutes_usage[%r{([^/ ]+)$}]
end
# Waits and Checks if storage available alert presents on the page
#
# @return [Boolean] True if the alert presents, false if not after 5 second wait
def purchased_storage_available?
storage_available_alert_element.wait_until(timeout: 5, &:present?)
rescue Watir::Wait::TimeoutError
false
end
# Returns total purchased storage value once it's ready on page
#
# @return [Float] Total purchased storage value in GiB
def total_purchased_storage
storage_available_alert_element.wait_until(&:present?)
purchased_usage_element.p.spans[3].text.to_f
end
end
end
end

View file

@ -1,3 +1,4 @@
import { nextTick } from 'vue';
import { GlFormInput, GlFormTextarea } from '@gitlab/ui';
import { shallowMount, mount } from '@vue/test-utils';
@ -32,7 +33,6 @@ describe('Pipeline Editor | Commit Form', () => {
afterEach(() => {
wrapper.destroy();
wrapper = null;
});
describe('when the form is displayed', () => {
@ -121,7 +121,7 @@ describe('Pipeline Editor | Commit Form', () => {
beforeEach(async () => {
createComponent();
wrapper.setProps({ scrollToCommitForm: true });
await wrapper.vm.$nextTick();
await nextTick();
});
it('scrolls into view', () => {

View file

@ -1,5 +1,7 @@
import VueApollo from 'vue-apollo';
import { GlFormTextarea, GlFormInput, GlLoadingIcon } from '@gitlab/ui';
import { mount } from '@vue/test-utils';
import { createLocalVue, mount } from '@vue/test-utils';
import createMockApollo from 'helpers/mock_apollo_helper';
import waitForPromises from 'helpers/wait_for_promises';
import { objectToQuery, redirectTo } from '~/lib/utils/url_utility';
import CommitForm from '~/pipeline_editor/components/commit/commit_form.vue';
@ -10,18 +12,22 @@ import {
COMMIT_SUCCESS,
} from '~/pipeline_editor/constants';
import commitCreate from '~/pipeline_editor/graphql/mutations/commit_ci_file.mutation.graphql';
import updatePipelineEtag from '~/pipeline_editor/graphql/mutations/update_pipeline_etag.mutation.graphql';
import {
mockCiConfigPath,
mockCiYml,
mockCommitCreateResponse,
mockCommitCreateResponseNewEtag,
mockCommitSha,
mockCommitNextSha,
mockCommitMessage,
mockDefaultBranch,
mockProjectFullPath,
mockNewMergeRequestPath,
} from '../../mock_data';
const localVue = createLocalVue();
jest.mock('~/lib/utils/url_utility', () => ({
redirectTo: jest.fn(),
refreshCurrentPage: jest.fn(),
@ -47,7 +53,8 @@ const mockProvide = {
describe('Pipeline Editor | Commit section', () => {
let wrapper;
let mockMutate;
let mockApollo;
const mockMutateCommitData = jest.fn();
const defaultProps = {
ciFileContent: mockCiYml,
@ -55,18 +62,7 @@ describe('Pipeline Editor | Commit section', () => {
isNewCiConfigFile: false,
};
const createComponent = ({ props = {}, options = {}, provide = {} } = {}) => {
mockMutate = jest.fn().mockResolvedValue({
data: {
commitCreate: {
errors: [],
commit: {
sha: mockCommitNextSha,
},
},
},
});
const createComponent = ({ apolloConfig = {}, props = {}, options = {}, provide = {} } = {}) => {
wrapper = mount(CommitSection, {
propsData: { ...defaultProps, ...props },
provide: { ...mockProvide, ...provide },
@ -75,16 +71,25 @@ describe('Pipeline Editor | Commit section', () => {
currentBranch: mockDefaultBranch,
};
},
mocks: {
$apollo: {
mutate: mockMutate,
},
},
attachTo: document.body,
...apolloConfig,
...options,
});
};
const createComponentWithApollo = (options) => {
const handlers = [[commitCreate, mockMutateCommitData]];
localVue.use(VueApollo);
mockApollo = createMockApollo(handlers);
const apolloConfig = {
localVue,
apolloProvider: mockApollo,
};
createComponent({ ...options, apolloConfig });
};
const findCommitForm = () => wrapper.findComponent(CommitForm);
const findCommitBtnLoadingIcon = () =>
wrapper.find('[type="submit"]').findComponent(GlLoadingIcon);
@ -104,66 +109,53 @@ describe('Pipeline Editor | Commit section', () => {
};
afterEach(() => {
mockMutate.mockReset();
wrapper.destroy();
});
describe('when the user commits a new file', () => {
beforeEach(async () => {
createComponent({ props: { isNewCiConfigFile: true } });
mockMutateCommitData.mockResolvedValue(mockCommitCreateResponse);
createComponentWithApollo({ props: { isNewCiConfigFile: true } });
await submitCommit();
});
it('calls the mutation with the CREATE action', () => {
// the extra calls are for updating client queries (currentBranch and lastCommitBranch)
expect(mockMutate).toHaveBeenCalledTimes(3);
expect(mockMutate).toHaveBeenCalledWith({
mutation: commitCreate,
update: expect.any(Function),
variables: {
...mockVariables,
action: COMMIT_ACTION_CREATE,
branch: mockDefaultBranch,
},
expect(mockMutateCommitData).toHaveBeenCalledTimes(1);
expect(mockMutateCommitData).toHaveBeenCalledWith({
...mockVariables,
action: COMMIT_ACTION_CREATE,
branch: mockDefaultBranch,
});
});
});
describe('when the user commits an update to an existing file', () => {
beforeEach(async () => {
createComponent();
createComponentWithApollo();
await submitCommit();
});
it('calls the mutation with the UPDATE action', () => {
expect(mockMutate).toHaveBeenCalledTimes(3);
expect(mockMutate).toHaveBeenCalledWith({
mutation: commitCreate,
update: expect.any(Function),
variables: {
...mockVariables,
action: COMMIT_ACTION_UPDATE,
branch: mockDefaultBranch,
},
expect(mockMutateCommitData).toHaveBeenCalledTimes(1);
expect(mockMutateCommitData).toHaveBeenCalledWith({
...mockVariables,
action: COMMIT_ACTION_UPDATE,
branch: mockDefaultBranch,
});
});
});
describe('when the user commits changes to the current branch', () => {
beforeEach(async () => {
createComponent();
createComponentWithApollo();
await submitCommit();
});
it('calls the mutation with the current branch', () => {
expect(mockMutate).toHaveBeenCalledTimes(3);
expect(mockMutate).toHaveBeenCalledWith({
mutation: commitCreate,
update: expect.any(Function),
variables: {
...mockVariables,
branch: mockDefaultBranch,
},
expect(mockMutateCommitData).toHaveBeenCalledTimes(1);
expect(mockMutateCommitData).toHaveBeenCalledWith({
...mockVariables,
branch: mockDefaultBranch,
});
});
@ -183,14 +175,10 @@ describe('Pipeline Editor | Commit section', () => {
it('a second commit submits the latest sha, keeping the form updated', async () => {
await submitCommit();
expect(mockMutate).toHaveBeenCalledTimes(6);
expect(mockMutate).toHaveBeenCalledWith({
mutation: commitCreate,
update: expect.any(Function),
variables: {
...mockVariables,
branch: mockDefaultBranch,
},
expect(mockMutateCommitData).toHaveBeenCalledTimes(2);
expect(mockMutateCommitData).toHaveBeenCalledWith({
...mockVariables,
branch: mockDefaultBranch,
});
});
});
@ -199,20 +187,16 @@ describe('Pipeline Editor | Commit section', () => {
const newBranch = 'new-branch';
beforeEach(async () => {
createComponent();
createComponentWithApollo();
await submitCommit({
branch: newBranch,
});
});
it('calls the mutation with the new branch', () => {
expect(mockMutate).toHaveBeenCalledWith({
mutation: commitCreate,
update: expect.any(Function),
variables: {
...mockVariables,
branch: newBranch,
},
expect(mockMutateCommitData).toHaveBeenCalledWith({
...mockVariables,
branch: newBranch,
});
});
@ -225,7 +209,7 @@ describe('Pipeline Editor | Commit section', () => {
const newBranch = 'new-branch';
beforeEach(async () => {
createComponent();
createComponentWithApollo();
await submitCommit({
branch: newBranch,
openMergeRequest: true,
@ -244,11 +228,11 @@ describe('Pipeline Editor | Commit section', () => {
describe('when the commit is ocurring', () => {
beforeEach(() => {
createComponent();
createComponentWithApollo();
});
it('shows a saving state', async () => {
mockMutate.mockImplementationOnce(() => {
mockMutateCommitData.mockImplementationOnce(() => {
expect(findCommitBtnLoadingIcon().exists()).toBe(true);
return Promise.resolve();
});
@ -261,6 +245,26 @@ describe('Pipeline Editor | Commit section', () => {
});
});
describe('when the commit returns a different etag path', () => {
beforeEach(async () => {
createComponentWithApollo();
jest.spyOn(wrapper.vm.$apollo, 'mutate');
mockMutateCommitData.mockResolvedValue(mockCommitCreateResponseNewEtag);
await submitCommit();
});
it('calls the client mutation to update the etag', () => {
// 1:Commit submission, 2:etag update, 3:currentBranch update, 4:lastCommit update
expect(wrapper.vm.$apollo.mutate).toHaveBeenCalledTimes(4);
expect(wrapper.vm.$apollo.mutate).toHaveBeenNthCalledWith(2, {
mutation: updatePipelineEtag,
variables: {
pipelineEtag: mockCommitCreateResponseNewEtag.data.commitCreate.commitPipelinePath,
},
});
});
});
it('sets listeners on commit form', () => {
const handler = jest.fn();
createComponent({ options: { listeners: { event: handler } } });

View file

@ -453,3 +453,31 @@ export const mockErrors = [
export const mockWarnings = [
'"jobs:multi_project_job may allow multiple pipelines to run for a single action due to `rules:when` clause with no `workflow:rules` - read more: https://docs.gitlab.com/ee/ci/troubleshooting.html#pipeline-warnings"',
];
export const mockCommitCreateResponse = {
data: {
commitCreate: {
__typename: 'CommitCreatePayload',
errors: [],
commit: {
__typename: 'Commit',
sha: mockCommitNextSha,
},
commitPipelinePath: '',
},
},
};
export const mockCommitCreateResponseNewEtag = {
data: {
commitCreate: {
__typename: 'CommitCreatePayload',
errors: [],
commit: {
__typename: 'Commit',
sha: mockCommitNextSha,
},
commitPipelinePath: '/api/graphql:pipelines/sha/550ceace1acd373c84d02bd539cb9d4614f786db',
},
},
};

View file

@ -39,7 +39,11 @@ describe('App component', () => {
let wrapper;
let userCalloutDismissSpy;
const createComponent = ({ shouldShowCallout = true, ...propsData }) => {
const createComponent = ({
shouldShowCallout = true,
secureVulnerabilityTraining = true,
...propsData
}) => {
userCalloutDismissSpy = jest.fn();
wrapper = extendedWrapper(
@ -50,6 +54,9 @@ describe('App component', () => {
autoDevopsHelpPagePath,
autoDevopsPath,
projectPath,
glFeatures: {
secureVulnerabilityTraining,
},
},
stubs: {
...stubChildren(SecurityConfigurationApp),
@ -138,20 +145,20 @@ describe('App component', () => {
expect(mainHeading.text()).toContain('Security Configuration');
});
it('renders GlTab Component ', () => {
expect(findTab().exists()).toBe(true);
});
describe('tabs', () => {
const expectedTabs = ['security-testing', 'compliance-testing', 'vulnerability-management'];
it('renders right amount of tabs with correct title ', () => {
expect(findTabs()).toHaveLength(2);
});
it('renders GlTab Component', () => {
expect(findTab().exists()).toBe(true);
});
it('renders security-testing tab', () => {
expect(findByTestId('security-testing-tab').exists()).toBe(true);
});
it('renders correct amount of tabs', () => {
expect(findTabs()).toHaveLength(expectedTabs.length);
});
it('renders compliance-testing tab', () => {
expect(findByTestId('compliance-testing-tab').exists()).toBe(true);
it.each(expectedTabs)('renders the %s tab', (tabName) => {
expect(findByTestId(`${tabName}-tab`).exists()).toBe(true);
});
});
it('renders right amount of feature cards for given props with correct props', () => {
@ -418,4 +425,22 @@ describe('App component', () => {
expect(findSecurityViewHistoryLink().attributes('href')).toBe('test/historyPath');
});
});
describe('when secureVulnerabilityTraining feature flag is disabled', () => {
beforeEach(() => {
createComponent({
augmentedSecurityFeatures: securityFeaturesMock,
augmentedComplianceFeatures: complianceFeaturesMock,
secureVulnerabilityTraining: false,
});
});
it('renders correct amount of tabs', () => {
expect(findTabs()).toHaveLength(2);
});
it('does not render the vulnerability-management tab', () => {
expect(wrapper.findByTestId('vulnerability-management-tab').exists()).toBe(false);
});
});
});