Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
413a526be6
commit
6121eccf2b
13 changed files with 128 additions and 141 deletions
|
@ -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>
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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-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"
|
|
@ -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"
|
||||
|
|
|
@ -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
|
1
db/schema_migrations/20210622135221
Normal file
1
db/schema_migrations/20210622135221
Normal file
|
@ -0,0 +1 @@
|
|||
e43889baa57ea2cd0b87ba98819408115955f6a6586b3275cf0a08bd79909c71
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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`
|
||||
|
|
|
@ -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],
|
||||
|
|
|
@ -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');
|
||||
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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();
|
||||
|
|
@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
Loading…
Reference in a new issue