Commit d175ad5e authored by Kushal Pandya's avatar Kushal Pandya

Merge branch 'eks-iam-role-dropdown' into 'master'

Create cluster dropdown component

See merge request gitlab-org/gitlab!16518
parents fca53a0b 8400ab25
<script>
import DropdownSearchInput from '~/vue_shared/components/dropdown/dropdown_search_input.vue';
import DropdownHiddenInput from '~/vue_shared/components/dropdown/dropdown_hidden_input.vue';
import DropdownButton from '~/vue_shared/components/dropdown/dropdown_button.vue';
export default {
components: {
DropdownButton,
DropdownSearchInput,
DropdownHiddenInput,
},
props: {
fieldName: {
type: String,
required: false,
default: '',
},
placeholder: {
type: String,
required: false,
default: '',
},
defaultValue: {
type: String,
required: false,
default: '',
},
value: {
type: Object,
required: false,
default: () => null,
},
labelProperty: {
type: String,
required: false,
default: 'name',
},
valueProperty: {
type: String,
required: false,
default: 'value',
},
items: {
type: Array,
required: false,
default: () => [],
},
loading: {
type: Boolean,
required: false,
default: false,
},
disabled: {
type: Boolean,
required: false,
default: false,
},
loadingText: {
type: String,
required: false,
default: '',
},
disabledText: {
type: String,
required: false,
default: '',
},
hasErrors: {
type: Boolean,
required: false,
default: false,
},
errorMessage: {
type: String,
required: false,
default: '',
},
searchFieldPlaceholder: {
type: String,
required: false,
default: '',
},
emptyText: {
type: String,
required: false,
default: '',
},
searchFn: {
type: Function,
required: false,
default: searchQuery => item => item.name.toLowerCase().indexOf(searchQuery) > -1,
},
},
data() {
return {
searchQuery: '',
selectedItem: null,
};
},
computed: {
toggleText() {
if (this.loading && this.loadingText) {
return this.loadingText;
}
if (this.disabled && this.disabledText) {
return this.disabledText;
}
if (!this.selectedItem) {
return this.placeholder;
}
return this.selectedItemLabel;
},
results() {
if (!this.items) {
return [];
}
return this.items.filter(this.searchFn(this.searchQuery));
},
selectedItemLabel() {
return this.selectedItem && this.selectedItem[this.labelProperty];
},
selectedItemValue() {
return (this.selectedItem && this.selectedItem[this.valueProperty]) || '';
},
},
methods: {
select(item) {
this.selectedItem = item;
this.$emit('input', item);
},
},
};
</script>
<template>
<div>
<div class="js-gcp-machine-type-dropdown dropdown">
<dropdown-hidden-input :name="fieldName" :value="selectedItemValue" />
<dropdown-button
:class="{ 'border-danger': hasErrors }"
:is-disabled="disabled"
:is-loading="loading"
:toggle-text="toggleText"
/>
<div class="dropdown-menu dropdown-select">
<dropdown-search-input v-model="searchQuery" :placeholder-text="searchFieldPlaceholder" />
<div class="dropdown-content">
<ul>
<li v-if="!results.length">
<span class="js-empty-text menu-item">
{{ emptyText }}
</span>
</li>
<li v-for="item in results" :key="item.id">
<button class="js-dropdown-item" type="button" @click.prevent="select(item)">
<slot name="item" :item="item">
{{ item.name }}
</slot>
</button>
</li>
</ul>
</div>
</div>
</div>
<span
v-if="hasErrors && errorMessage"
:class="[
'form-text js-eks-dropdown-error-message',
{
'text-danger': hasErrors,
'text-muted': !hasErrors,
},
]"
>
{{ errorMessage }}
</span>
</div>
</template>
......@@ -14,5 +14,12 @@ export default {
};
</script>
<template>
<form name="eks-cluster-configuration-form"></form>
<form name="eks-cluster-configuration-form">
<div class="form-group">
<label class="label-bold" name="role" for="eks-role">
{{ s__('ClusterIntegration|Role name') }}
</label>
<role-name-dropdown />
</div>
</form>
</template>
<script>
import { sprintf, s__ } from '~/locale';
import ClusterFormDropdown from './cluster_form_dropdown.vue';
export default {
components: {
ClusterFormDropdown,
},
props: {
roles: {
type: Array,
required: false,
default: () => [],
},
loading: {
type: Boolean,
required: false,
default: false,
},
},
computed: {
helpText() {
return sprintf(
s__(
'ClusterIntegration|Select the IAM Role to allow Amazon EKS and the Kubernetes control plane to manage AWS resources on your behalf. To use a new role name, first create one on %{startLink}Amazon Web Services%{endLink}.',
),
{
startLink:
'<a href="https://console.aws.amazon.com/iam/home?#roles" target="_blank" rel="noopener noreferrer">',
endLink: '</a>',
},
false,
);
},
},
};
</script>
<template>
<div>
<cluster-form-dropdown
field-id="eks-role-name"
field-name="eks-role-name"
:items="roles"
:loading="loading"
:loading-text="s__('ClusterIntegration|Loading IAM Roles')"
:placeholder="s__('ClusterIntergation|Select role name')"
:search-field-placeholder="s__('ClusterIntegration|Search IAM Roles')"
:empty-text="s__('ClusterIntegration|No IAM Roles found')"
/>
<p class="form-text text-muted" v-html="helpText"></p>
</div>
</template>
......@@ -3514,6 +3514,9 @@ msgstr ""
msgid "ClusterIntegration|Let's Encrypt"
msgstr ""
msgid "ClusterIntegration|Loading IAM Roles"
msgstr ""
msgid "ClusterIntegration|Machine type"
msgstr ""
......@@ -3523,6 +3526,9 @@ msgstr ""
msgid "ClusterIntegration|Manage your Kubernetes cluster by visiting %{link_gke}"
msgstr ""
msgid "ClusterIntegration|No IAM Roles found"
msgstr ""
msgid "ClusterIntegration|No machine types matched your search"
msgstr ""
......@@ -3589,9 +3595,15 @@ msgstr ""
msgid "ClusterIntegration|Request to begin uninstalling failed"
msgstr ""
msgid "ClusterIntegration|Role name"
msgstr ""
msgid "ClusterIntegration|Save changes"
msgstr ""
msgid "ClusterIntegration|Search IAM Roles"
msgstr ""
msgid "ClusterIntegration|Search machine types"
msgstr ""
......@@ -3616,6 +3628,9 @@ msgstr ""
msgid "ClusterIntegration|Select project to choose zone"
msgstr ""
msgid "ClusterIntegration|Select the IAM Role to allow Amazon EKS and the Kubernetes control plane to manage AWS resources on your behalf. To use a new role name, first create one on %{startLink}Amazon Web Services%{endLink}."
msgstr ""
msgid "ClusterIntegration|Select zone"
msgstr ""
......@@ -3739,6 +3754,9 @@ msgstr ""
msgid "ClusterIntegration|sign up"
msgstr ""
msgid "ClusterIntergation|Select role name"
msgstr ""
msgid "Code"
msgstr ""
......
import { shallowMount } from '@vue/test-utils';
import ClusterFormDropdown from '~/create_cluster/eks_cluster/components/cluster_form_dropdown.vue';
import DropdownButton from '~/vue_shared/components/dropdown/dropdown_button.vue';
import DropdownSearchInput from '~/vue_shared/components/dropdown/dropdown_search_input.vue';
import DropdownHiddenInput from '~/vue_shared/components/dropdown/dropdown_hidden_input.vue';
describe('ClusterFormDropdown', () => {
let vm;
beforeEach(() => {
vm = shallowMount(ClusterFormDropdown);
});
afterEach(() => vm.destroy());
describe('when no item is selected', () => {
it('displays placeholder text', () => {
const placeholder = 'placeholder';
vm.setProps({ placeholder });
expect(vm.find(DropdownButton).props('toggleText')).toEqual(placeholder);
});
});
describe('when an item is selected', () => {
const selectedItem = { name: 'Name', value: 'value' };
beforeEach(() => {
vm.setData({ selectedItem });
});
it('displays selected item label', () => {
expect(vm.find(DropdownButton).props('toggleText')).toEqual(selectedItem.name);
});
it('sets selected value to dropdown hidden input', () => {
expect(vm.find(DropdownHiddenInput).props('value')).toEqual(selectedItem.value);
});
});
describe('when an item is selected and has a custom label property', () => {
it('displays selected item custom label', () => {
const labelProperty = 'customLabel';
const selectedItem = { [labelProperty]: 'Name' };
vm.setProps({ labelProperty });
vm.setData({ selectedItem });
expect(vm.find(DropdownButton).props('toggleText')).toEqual(selectedItem[labelProperty]);
});
});
describe('when loading', () => {
it('dropdown button isLoading', () => {
vm.setProps({ loading: true });
expect(vm.find(DropdownButton).props('isLoading')).toBe(true);
});
});
describe('when loading and loadingText is provided', () => {
it('uses loading text as toggle button text', () => {
const loadingText = 'loading text';
vm.setProps({ loading: true, loadingText });
expect(vm.find(DropdownButton).props('toggleText')).toEqual(loadingText);
});
});
describe('when disabled', () => {
it('dropdown button isDisabled', () => {
vm.setProps({ disabled: true });
expect(vm.find(DropdownButton).props('isDisabled')).toBe(true);
});
});
describe('when disabled and disabledText is provided', () => {
it('uses disabled text as toggle button text', () => {
const disabledText = 'disabled text';
vm.setProps({ disabled: true, disabledText });
expect(vm.find(DropdownButton).props('toggleText')).toBe(disabledText);
});
});
describe('when has errors', () => {
it('sets border-danger class selector to dropdown toggle', () => {
vm.setProps({ hasErrors: true });
expect(vm.find(DropdownButton).classes('border-danger')).toBe(true);
});
});
describe('when has errors and an error message', () => {
it('displays error message', () => {
const errorMessage = 'error message';
vm.setProps({ hasErrors: true, errorMessage });
expect(vm.find('.js-eks-dropdown-error-message').text()).toEqual(errorMessage);
});
});
describe('when no results are available', () => {
it('displays empty text', () => {
const emptyText = 'error message';
vm.setProps({ items: [], emptyText });
expect(vm.find('.js-empty-text').text()).toEqual(emptyText);
});
});
it('displays search field placeholder', () => {
const searchFieldPlaceholder = 'Placeholder';
vm.setProps({ searchFieldPlaceholder });
expect(vm.find(DropdownSearchInput).props('placeholderText')).toEqual(searchFieldPlaceholder);
});
it('it filters results by search query', () => {
const secondItem = { name: 'second item' };
const items = [{ name: 'first item' }, secondItem];
const searchQuery = 'second';
vm.setProps({ items });
vm.setData({ searchQuery });
expect(vm.findAll('.js-dropdown-item').length).toEqual(1);
expect(vm.find('.js-dropdown-item').text()).toEqual(secondItem.name);
});
});
import { shallowMount } from '@vue/test-utils';
import ClusterFormDropdown from '~/create_cluster/eks_cluster/components/cluster_form_dropdown.vue';
import RoleNameDropdown from '~/create_cluster/eks_cluster/components/role_name_dropdown.vue';
describe('RoleNameDropdown', () => {
let vm;
beforeEach(() => {
vm = shallowMount(RoleNameDropdown);
});
afterEach(() => vm.destroy());
it('renders a cluster-form-dropdown', () => {
expect(vm.find(ClusterFormDropdown).exists()).toBe(true);
});
it('sets roles to cluster-form-dropdown items property', () => {
const roles = [{ name: 'basic' }];
vm.setProps({ roles });
expect(vm.find(ClusterFormDropdown).props('items')).toEqual(roles);
});
it('sets a loading text', () => {
expect(vm.find(ClusterFormDropdown).props('loadingText')).toEqual('Loading IAM Roles');
});
it('sets a placeholder', () => {
expect(vm.find(ClusterFormDropdown).props('placeholder')).toEqual('Select role name');
});
it('sets an empty results text', () => {
expect(vm.find(ClusterFormDropdown).props('emptyText')).toEqual('No IAM Roles found');
});
it('sets a search field placeholder', () => {
expect(vm.find(ClusterFormDropdown).props('searchFieldPlaceholder')).toEqual(
'Search IAM Roles',
);
});
});
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