Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2021-06-25 09:07:34 +00:00
parent 413a526be6
commit 6121eccf2b
13 changed files with 128 additions and 141 deletions

View file

@ -1,10 +1,8 @@
<script>
import { GlAlert } from '@gitlab/ui';
import { mapState, mapActions } from 'vuex';
import axios from '~/lib/utils/axios_utils';
import featureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import { NEW_VERSION_FLAG, ROLLOUT_STRATEGY_ALL_USERS } from '../constants';
import { createNewEnvironmentScope } from '../store/helpers';
import { ROLLOUT_STRATEGY_ALL_USERS } from '../constants';
import FeatureFlagForm from './form.vue';
export default {
@ -13,48 +11,14 @@ export default {
GlAlert,
},
mixins: [featureFlagsMixin()],
inject: {
showUserCallout: {},
userCalloutId: {
default: '',
},
userCalloutsPath: {
default: '',
},
},
data() {
return {
userShouldSeeNewFlagAlert: this.showUserCallout,
};
},
computed: {
...mapState(['error', 'path']),
scopes() {
return [
createNewEnvironmentScope(
{
environmentScope: '*',
active: true,
},
this.glFeatures.featureFlagsPermissions,
),
];
},
version() {
return NEW_VERSION_FLAG;
},
strategies() {
return [{ name: ROLLOUT_STRATEGY_ALL_USERS, parameters: {}, scopes: [] }];
},
},
methods: {
...mapActions(['createFeatureFlag']),
dismissNewVersionFlagAlert() {
this.userShouldSeeNewFlagAlert = false;
axios.post(this.userCalloutsPath, {
feature_name: this.userCalloutId,
});
},
},
};
</script>
@ -69,9 +33,7 @@ export default {
<feature-flag-form
:cancel-path="path"
:submit-text="s__('FeatureFlags|Create feature flag')"
:scopes="scopes"
:strategies="strategies"
:version="version"
@handleSubmit="(data) => createFeatureFlag(data)"
/>
</div>

View file

@ -1,7 +1,6 @@
import axios from '~/lib/utils/axios_utils';
import { visitUrl } from '~/lib/utils/url_utility';
import { NEW_VERSION_FLAG } from '../../constants';
import { mapFromScopesViewModel, mapStrategiesToRails } from '../helpers';
import { mapStrategiesToRails } from '../helpers';
import * as types from './mutation_types';
/**
@ -17,12 +16,7 @@ export const createFeatureFlag = ({ state, dispatch }, params) => {
dispatch('requestCreateFeatureFlag');
return axios
.post(
state.endpoint,
params.version === NEW_VERSION_FLAG
? mapStrategiesToRails(params)
: mapFromScopesViewModel(params),
)
.post(state.endpoint, mapStrategiesToRails(params))
.then(() => {
dispatch('receiveCreateFeatureFlagSuccess');
visitUrl(state.path);

View file

@ -1,5 +1,5 @@
<script>
import { GlButton, GlModalDirective } from '@gitlab/ui';
import { GlButtonGroup, GlButton, GlModalDirective } from '@gitlab/ui';
import { uniqueId } from 'lodash';
import { sprintf, __ } from '~/locale';
import getRefMixin from '../mixins/get_ref';
@ -9,8 +9,10 @@ export default {
i18n: {
replace: __('Replace'),
replacePrimaryBtnText: __('Replace file'),
delete: __('Delete'),
},
components: {
GlButtonGroup,
GlButton,
UploadBlobModal,
},
@ -48,7 +50,7 @@ export default {
replaceModalId() {
return uniqueId('replace-modal');
},
title() {
replaceModalTitle() {
return sprintf(__('Replace %{name}'), { name: this.name });
},
},
@ -57,13 +59,16 @@ export default {
<template>
<div class="gl-mr-3">
<gl-button v-gl-modal="replaceModalId">
{{ $options.i18n.replace }}
</gl-button>
<gl-button-group>
<gl-button v-gl-modal="replaceModalId">
{{ $options.i18n.replace }}
</gl-button>
<gl-button>{{ $options.i18n.delete }}</gl-button>
</gl-button-group>
<upload-blob-modal
:modal-id="replaceModalId"
:modal-title="title"
:commit-message="title"
:modal-title="replaceModalTitle"
:commit-message="replaceModalTitle"
:target-branch="targetBranch || ref"
:original-branch="originalBranch || ref"
:can-push-code="canPushCode"

View file

@ -7,14 +7,14 @@ import { SIMPLE_BLOB_VIEWER, RICH_BLOB_VIEWER } from '~/blob/components/constant
import createFlash from '~/flash';
import { __ } from '~/locale';
import blobInfoQuery from '../queries/blob_info.query.graphql';
import BlobButtonGroup from './blob_button_group.vue';
import BlobEdit from './blob_edit.vue';
import BlobReplace from './blob_replace.vue';
export default {
components: {
BlobHeader,
BlobEdit,
BlobReplace,
BlobButtonGroup,
BlobContent,
GlLoadingIcon,
},
@ -132,7 +132,7 @@ export default {
>
<template #actions>
<blob-edit :edit-path="blobInfo.editBlobPath" :web-ide-path="blobInfo.ideEditPath" />
<blob-replace
<blob-button-group
v-if="isLoggedIn"
:path="path"
:name="blobInfo.name"

View file

@ -0,0 +1,21 @@
# frozen_string_literal: true
class AddForeignKeyForEnvironmentIdToEnvironments < ActiveRecord::Migration[6.0]
include Gitlab::Database::MigrationHelpers
disable_ddl_transaction!
def up
# `validate: false` option is passed here, because validating the existing rows fails by the orphaned deployments,
# which will be cleaned up in https://gitlab.com/gitlab-org/gitlab/-/merge_requests/64588.
# The validation runs for only new records or updates, so that we can at least
# stop creating orphaned rows.
add_concurrent_foreign_key :deployments, :environments, column: :environment_id, on_delete: :cascade, validate: false
end
def down
with_lock_retries do
remove_foreign_key_if_exists :deployments, :environments
end
end
end

View file

@ -0,0 +1 @@
e43889baa57ea2cd0b87ba98819408115955f6a6586b3275cf0a08bd79909c71

View file

@ -25476,6 +25476,9 @@ CREATE TRIGGER trigger_has_external_wiki_on_update AFTER UPDATE ON services FOR
ALTER TABLE ONLY chat_names
ADD CONSTRAINT fk_00797a2bf9 FOREIGN KEY (service_id) REFERENCES services(id) ON DELETE CASCADE;
ALTER TABLE ONLY deployments
ADD CONSTRAINT fk_009fd21147 FOREIGN KEY (environment_id) REFERENCES environments(id) ON DELETE CASCADE NOT VALID;
ALTER TABLE ONLY epics
ADD CONSTRAINT fk_013c9f36ca FOREIGN KEY (due_date_sourcing_epic_id) REFERENCES epics(id) ON DELETE SET NULL;

View file

@ -3591,6 +3591,27 @@ Input type: `RunnersRegistrationTokenResetInput`
| <a id="mutationrunnersregistrationtokenreseterrors"></a>`errors` | [`[String!]!`](#string) | Errors encountered during execution of the mutation. |
| <a id="mutationrunnersregistrationtokenresettoken"></a>`token` | [`String`](#string) | The runner token after mutation. |
### `Mutation.scanExecutionPolicyCommit`
Input type: `ScanExecutionPolicyCommitInput`
#### Arguments
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="mutationscanexecutionpolicycommitclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. |
| <a id="mutationscanexecutionpolicycommitoperationmode"></a>`operationMode` | [`MutationOperationMode!`](#mutationoperationmode) | Changes the operation mode. |
| <a id="mutationscanexecutionpolicycommitpolicyyaml"></a>`policyYaml` | [`String!`](#string) | YAML snippet of the policy. |
| <a id="mutationscanexecutionpolicycommitprojectpath"></a>`projectPath` | [`ID!`](#id) | Full path of the project. |
#### Fields
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="mutationscanexecutionpolicycommitbranch"></a>`branch` | [`String`](#string) | Name of the branch to which the policy changes are committed. |
| <a id="mutationscanexecutionpolicycommitclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. |
| <a id="mutationscanexecutionpolicycommiterrors"></a>`errors` | [`[String!]!`](#string) | Errors encountered during execution of the mutation. |
### `Mutation.terraformStateDelete`
Input type: `TerraformStateDeleteInput`

View file

@ -34,7 +34,7 @@ RSpec.describe 'Database schema' do
compliance_management_frameworks: %w[group_id],
commit_user_mentions: %w[commit_id],
deploy_keys_projects: %w[deploy_key_id],
deployments: %w[deployable_id environment_id user_id],
deployments: %w[deployable_id user_id],
draft_notes: %w[discussion_id commit_id],
epics: %w[updated_by_id last_edited_by_id state_id],
events: %w[target_id],

View file

@ -4,7 +4,6 @@ import Vuex from 'vuex';
import { TEST_HOST } from 'spec/test_constants';
import Form from '~/feature_flags/components/form.vue';
import NewFeatureFlag from '~/feature_flags/components/new_feature_flag.vue';
import { ROLLOUT_STRATEGY_ALL_USERS, DEFAULT_PERCENT_ROLLOUT } from '~/feature_flags/constants';
import createStore from '~/feature_flags/store/new';
import { allUsersStrategy } from '../mock_data';
@ -71,18 +70,6 @@ describe('New feature flag form', () => {
expect(wrapper.find(Form).exists()).toEqual(true);
});
it('should render default * row', () => {
const defaultScope = {
id: expect.any(String),
environmentScope: '*',
active: true,
rolloutStrategy: ROLLOUT_STRATEGY_ALL_USERS,
rolloutPercentage: DEFAULT_PERCENT_ROLLOUT,
rolloutUserIds: '',
};
expect(wrapper.vm.scopes).toEqual([defaultScope]);
});
it('has an all users strategy by default', () => {
const strategies = wrapper.find(Form).props('strategies');

View file

@ -1,13 +1,8 @@
import MockAdapter from 'axios-mock-adapter';
import testAction from 'helpers/vuex_action_helper';
import { TEST_HOST } from 'spec/test_constants';
import {
ROLLOUT_STRATEGY_ALL_USERS,
ROLLOUT_STRATEGY_PERCENT_ROLLOUT,
LEGACY_FLAG,
NEW_VERSION_FLAG,
} from '~/feature_flags/constants';
import { mapFromScopesViewModel, mapStrategiesToRails } from '~/feature_flags/store/helpers';
import { ROLLOUT_STRATEGY_ALL_USERS, NEW_VERSION_FLAG } from '~/feature_flags/constants';
import { mapStrategiesToRails } from '~/feature_flags/store/helpers';
import {
createFeatureFlag,
requestCreateFeatureFlag,
@ -24,33 +19,13 @@ describe('Feature flags New Module Actions', () => {
let mockedState;
beforeEach(() => {
mockedState = state({ endpoint: 'feature_flags.json', path: '/feature_flags' });
mockedState = state({ endpoint: '/feature_flags.json', path: '/feature_flags' });
});
describe('createFeatureFlag', () => {
let mock;
const actionParams = {
name: 'name',
description: 'description',
active: true,
version: LEGACY_FLAG,
scopes: [
{
id: 1,
environmentScope: 'environmentScope',
active: true,
canUpdate: true,
protected: true,
shouldBeDestroyed: false,
rolloutStrategy: ROLLOUT_STRATEGY_ALL_USERS,
rolloutPercentage: ROLLOUT_STRATEGY_PERCENT_ROLLOUT,
},
],
};
beforeEach(() => {
mockedState.endpoint = `${TEST_HOST}/endpoint.json`;
mock = new MockAdapter(axios);
});
@ -60,9 +35,22 @@ describe('Feature flags New Module Actions', () => {
describe('success', () => {
it('dispatches requestCreateFeatureFlag and receiveCreateFeatureFlagSuccess ', (done) => {
const convertedActionParams = mapFromScopesViewModel(actionParams);
mock.onPost(`${TEST_HOST}/endpoint.json`, convertedActionParams).replyOnce(200);
const actionParams = {
name: 'name',
description: 'description',
active: true,
version: NEW_VERSION_FLAG,
strategies: [
{
name: ROLLOUT_STRATEGY_ALL_USERS,
parameters: {},
id: 1,
scopes: [{ id: 1, environmentScope: 'environmentScope', shouldBeDestroyed: false }],
shouldBeDestroyed: false,
},
],
};
mock.onPost(mockedState.endpoint, mapStrategiesToRails(actionParams)).replyOnce(200);
testAction(
createFeatureFlag,
@ -80,9 +68,11 @@ describe('Feature flags New Module Actions', () => {
done,
);
});
});
it('sends strategies for new style feature flags', (done) => {
const newVersionFlagParams = {
describe('error', () => {
it('dispatches requestCreateFeatureFlag and receiveCreateFeatureFlagError ', (done) => {
const actionParams = {
name: 'name',
description: 'description',
active: true,
@ -98,33 +88,7 @@ describe('Feature flags New Module Actions', () => {
],
};
mock
.onPost(`${TEST_HOST}/endpoint.json`, mapStrategiesToRails(newVersionFlagParams))
.replyOnce(200);
testAction(
createFeatureFlag,
newVersionFlagParams,
mockedState,
[],
[
{
type: 'requestCreateFeatureFlag',
},
{
type: 'receiveCreateFeatureFlagSuccess',
},
],
done,
);
});
});
describe('error', () => {
it('dispatches requestCreateFeatureFlag and receiveCreateFeatureFlagError ', (done) => {
const convertedActionParams = mapFromScopesViewModel(actionParams);
mock
.onPost(`${TEST_HOST}/endpoint.json`, convertedActionParams)
.onPost(mockedState.endpoint, mapStrategiesToRails(actionParams))
.replyOnce(500, { message: [] });
testAction(

View file

@ -1,5 +1,8 @@
import { GlButton } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import BlobReplace from '~/repository/components/blob_replace.vue';
import { createMockDirective, getBinding } from 'helpers/vue_mock_directive';
import BlobButtonGroup from '~/repository/components/blob_button_group.vue';
import UploadBlobModal from '~/repository/components/upload_blob_modal.vue';
const DEFAULT_PROPS = {
@ -14,11 +17,11 @@ const DEFAULT_INJECT = {
originalBranch: 'master',
};
describe('BlobReplace component', () => {
describe('BlobButtonGroup component', () => {
let wrapper;
const createComponent = (props = {}) => {
wrapper = shallowMount(BlobReplace, {
wrapper = shallowMount(BlobButtonGroup, {
propsData: {
...DEFAULT_PROPS,
...props,
@ -26,6 +29,9 @@ describe('BlobReplace component', () => {
provide: {
...DEFAULT_INJECT,
},
directives: {
GlModal: createMockDirective(),
},
});
};
@ -34,6 +40,7 @@ describe('BlobReplace component', () => {
});
const findUploadBlobModal = () => wrapper.findComponent(UploadBlobModal);
const findReplaceButton = () => wrapper.findAll(GlButton).at(0);
it('renders component', () => {
createComponent();
@ -46,6 +53,28 @@ describe('BlobReplace component', () => {
});
});
describe('buttons', () => {
beforeEach(() => {
createComponent();
});
it('renders both the replace and delete button', () => {
expect(wrapper.findAll(GlButton)).toHaveLength(2);
});
it('renders the buttons in the correct order', () => {
expect(wrapper.findAll(GlButton).at(0).text()).toBe('Replace');
expect(wrapper.findAll(GlButton).at(1).text()).toBe('Delete');
});
it('triggers the UploadBlobModal from the replace button', () => {
const { value } = getBinding(findReplaceButton().element, 'gl-modal');
const modalId = findUploadBlobModal().props('modalId');
expect(modalId).toEqual(value);
});
});
it('renders UploadBlobModal', () => {
createComponent();

View file

@ -3,9 +3,9 @@ import { shallowMount, mount } from '@vue/test-utils';
import { nextTick } from 'vue';
import BlobContent from '~/blob/components/blob_content.vue';
import BlobHeader from '~/blob/components/blob_header.vue';
import BlobButtonGroup from '~/repository/components/blob_button_group.vue';
import BlobContentViewer from '~/repository/components/blob_content_viewer.vue';
import BlobEdit from '~/repository/components/blob_edit.vue';
import BlobReplace from '~/repository/components/blob_replace.vue';
let wrapper;
const simpleMockData = {
@ -80,7 +80,7 @@ describe('Blob content viewer component', () => {
const findBlobHeader = () => wrapper.findComponent(BlobHeader);
const findBlobEdit = () => wrapper.findComponent(BlobEdit);
const findBlobContent = () => wrapper.findComponent(BlobContent);
const findBlobReplace = () => wrapper.findComponent(BlobReplace);
const findBlobButtonGroup = () => wrapper.findComponent(BlobButtonGroup);
afterEach(() => {
wrapper.destroy();
@ -200,7 +200,7 @@ describe('Blob content viewer component', () => {
});
});
describe('BlobReplace', () => {
describe('BlobButtonGroup', () => {
const { name, path } = simpleMockData;
it('renders component', async () => {
@ -210,13 +210,13 @@ describe('Blob content viewer component', () => {
mockData: { blobInfo: simpleMockData },
stubs: {
BlobContent: true,
BlobReplace: true,
BlobButtonGroup: true,
},
});
await nextTick();
expect(findBlobReplace().props()).toMatchObject({
expect(findBlobButtonGroup().props()).toMatchObject({
name,
path,
});
@ -235,7 +235,7 @@ describe('Blob content viewer component', () => {
await nextTick();
expect(findBlobReplace().exists()).toBe(false);
expect(findBlobButtonGroup().exists()).toBe(false);
});
});
});