Commit 8d4d2033 authored by Zack Cuddy's avatar Zack Cuddy Committed by Denys Mishunov

Geo Node Form - Redesign Form

This MR overhauls the Geo Node Form
Layout based on the design mocks.

Most signifigant changes are the re-ordering of
some fields, along with a complete rewrite/removal
of the help texts.

This overhaul also includes a few new More
information links to help users along.
parent a6b6aa45
......@@ -29,7 +29,7 @@ export default {
return this.node && this.node.primary;
},
pageTitle() {
return this.node ? __('Edit Geo Node') : __('New Geo Node');
return this.node ? __('Edit Geo Node') : __('Add New Node');
},
pillDetails() {
return {
......@@ -44,7 +44,7 @@ export default {
<template>
<article class="geo-node-form-container">
<div class="gl-display-flex gl-align-items-center">
<h3 class="page-title">{{ pageTitle }}</h3>
<h2 class="gl-font-size-h2 gl-my-5">{{ pageTitle }}</h2>
<gl-badge
class="rounded-pill gl-font-sm gl-px-3 gl-py-2 gl-ml-3"
:variant="pillDetails.variant"
......
<script>
import { mapActions, mapGetters } from 'vuex';
import { GlFormGroup, GlFormInput, GlFormCheckbox, GlButton } from '@gitlab/ui';
import { __ } from '~/locale';
import { GlButton } from '@gitlab/ui';
import { visitUrl } from '~/lib/utils/url_utility';
import GeoNodeFormCore from './geo_node_form_core.vue';
import GeoNodeFormSelectiveSync from './geo_node_form_selective_sync.vue';
......@@ -10,9 +9,6 @@ import GeoNodeFormCapacities from './geo_node_form_capacities.vue';
export default {
name: 'GeoNodeForm',
components: {
GlFormGroup,
GlFormInput,
GlFormCheckbox,
GlButton,
GeoNodeFormCore,
GeoNodeFormSelectiveSync,
......@@ -54,9 +50,6 @@ export default {
},
computed: {
...mapGetters(['formHasError']),
saveButtonTitle() {
return this.node ? __('Update') : __('Save');
},
},
created() {
if (this.node) {
......@@ -80,52 +73,30 @@ export default {
<template>
<form>
<geo-node-form-core :node-data="nodeData" />
<section class="mt-3 pl-0 col-sm-6">
<gl-form-group
v-if="nodeData.primary"
:label="__('Internal URL (optional)')"
label-for="node-internal-url-field"
:description="
__(
'The URL defined on the primary node that secondary nodes should use to contact it. Defaults to URL',
)
"
>
<gl-form-input id="node-internal-url-field" v-model="nodeData.internalUrl" type="text" />
</gl-form-group>
<geo-node-form-selective-sync
v-if="!nodeData.primary"
:node-data="nodeData"
:selective-sync-types="selectiveSyncTypes"
:sync-shards-options="syncShardsOptions"
@addSyncOption="addSyncOption"
@removeSyncOption="removeSyncOption"
/>
<geo-node-form-capacities :node-data="nodeData" />
<gl-form-group
v-if="!nodeData.primary"
:label="__('Object Storage replication')"
label-for="node-object-storage-field"
:description="
__(
'If enabled, and if object storage is enabled, GitLab will handle Object Storage replication using Geo',
)
"
>
<gl-form-checkbox id="node-object-storage-field" v-model="nodeData.syncObjectStorage">{{
__('Allow this secondary node to replicate content on Object Storage')
}}</gl-form-checkbox>
</gl-form-group>
</section>
<section class="d-flex align-items-center mt-4">
<geo-node-form-core
:node-data="nodeData"
class="gl-pb-4 gl-border-b-1 gl-border-b-gray-100 gl-border-b-solid"
/>
<geo-node-form-selective-sync
v-if="!nodeData.primary"
class="gl-pb-4 gl-border-b-1 gl-border-b-gray-100 gl-border-b-solid"
:node-data="nodeData"
:selective-sync-types="selectiveSyncTypes"
:sync-shards-options="syncShardsOptions"
@addSyncOption="addSyncOption"
@removeSyncOption="removeSyncOption"
/>
<geo-node-form-capacities :node-data="nodeData" />
<section
class="gl-display-flex gl-align-items-center gl-p-5 gl-mt-6 gl-bg-gray-10 gl-border-t-solid gl-border-b-solid gl-border-t-1 gl-border-b-1 gl-border-gray-200"
>
<gl-button
id="node-save-button"
data-qa-selector="add_node_button"
variant="success"
:disabled="formHasError"
@click="saveGeoNode(nodeData)"
>{{ saveButtonTitle }}</gl-button
>{{ __('Save changes') }}</gl-button
>
<gl-button id="node-cancel-button" class="gl-ml-auto" @click="redirect">{{
__('Cancel')
......
<script>
import { GlFormGroup, GlFormInput } from '@gitlab/ui';
import { GlFormGroup, GlFormInput, GlLink } from '@gitlab/ui';
import { mapActions, mapState } from 'vuex';
import { __ } from '~/locale';
import { validateCapacity } from '../validations';
import { VALIDATION_FIELD_KEYS } from '../constants';
import { VALIDATION_FIELD_KEYS, REVERIFICATION_MORE_INFO, BACKFILL_MORE_INFO } from '../constants';
export default {
name: 'GeoNodeFormCapacities',
components: {
GlFormGroup,
GlFormInput,
GlLink,
},
props: {
nodeData: {
......@@ -23,44 +24,30 @@ export default {
{
id: 'node-repository-capacity-field',
label: __('Repository sync capacity'),
description: __(
'Control the maximum concurrency of repository backfill for this secondary node',
),
key: VALIDATION_FIELD_KEYS.REPOS_MAX_CAPACITY,
conditional: 'secondary',
},
{
id: 'node-file-capacity-field',
label: __('File sync capacity'),
description: __(
'Control the maximum concurrency of LFS/attachment backfill for this secondary node',
),
key: VALIDATION_FIELD_KEYS.FILES_MAX_CAPACITY,
conditional: 'secondary',
},
{
id: 'node-container-repository-capacity-field',
label: __('Container repositories sync capacity'),
description: __(
'Control the maximum concurrency of container repository operations for this Geo node',
),
key: VALIDATION_FIELD_KEYS.CONTAINER_REPOSITORIES_MAX_CAPACITY,
conditional: 'secondary',
},
{
id: 'node-verification-capacity-field',
label: __('Verification capacity'),
description: __(
'Control the maximum concurrency of verification operations for this Geo node',
),
key: VALIDATION_FIELD_KEYS.VERIFICATION_MAX_CAPACITY,
},
{
id: 'node-reverification-interval-field',
label: __('Re-verification interval'),
description: __(
'Control the minimum interval in days that a repository should be reverified for this primary node',
),
description: __('Minimum interval in days'),
key: VALIDATION_FIELD_KEYS.MINIMUM_REVERIFICATION_INTERVAL,
conditional: 'primary',
},
......@@ -79,6 +66,16 @@ export default {
return true;
});
},
sectionDescription() {
return this.nodeData.primary
? __('Set the synchronization and verification capacity for the secondary node.')
: __(
'Set the number of concurrent requests this secondary node will make to the primary node while backfilling.',
);
},
sectionLink() {
return this.nodeData.primary ? REVERIFICATION_MORE_INFO : BACKFILL_MORE_INFO;
},
},
methods: {
...mapActions(['setError']),
......@@ -94,6 +91,11 @@ export default {
<template>
<div>
<h2 class="gl-font-size-h2 gl-my-5">{{ __('Performance and resource management') }}</h2>
<p class="gl-mb-5">
{{ sectionDescription }}
<gl-link :href="sectionLink" target="_blank">{{ __('More information') }}</gl-link>
</p>
<gl-form-group
v-for="formGroup in visibleFormGroups"
:key="formGroup.id"
......
<script>
import { GlFormGroup, GlFormInput, GlSprintf } from '@gitlab/ui';
import { GlFormGroup, GlFormInput, GlSprintf, GlLink } from '@gitlab/ui';
import { mapActions, mapState } from 'vuex';
import { validateName, validateUrl } from '../validations';
import { VALIDATION_FIELD_KEYS } from '../constants';
import { VALIDATION_FIELD_KEYS, NODE_NAME_MORE_INFO } from '../constants';
export default {
name: 'GeoNodeFormCore',
......@@ -10,6 +10,7 @@ export default {
GlFormGroup,
GlFormInput,
GlSprintf,
GlLink,
},
props: {
nodeData: {
......@@ -29,13 +30,13 @@ export default {
this.setError({ key: VALIDATION_FIELD_KEYS.URL, error: validateUrl(this.nodeData.url) });
},
},
NODE_NAME_MORE_INFO,
};
</script>
<template>
<section class="form-row">
<section>
<gl-form-group
class="col-sm-6"
:label="__('Name')"
label-for="node-name-field"
:state="Boolean(formErrors.name)"
......@@ -45,43 +46,92 @@ export default {
<gl-sprintf
:message="
__(
'The unique identifier for the Geo node. Must match %{geoNodeName} if it is set in gitlab.rb, otherwise it must match %{externalUrl} with a trailing slash',
'Must match with the %{codeStart}geo_node_name%{codeEnd} in %{codeStart}/etc/gitlab/gitlab.rb%{codeEnd}. %{linkStart}More information%{linkEnd}',
)
"
>
<template #geoNodeName>
<code>{{ __('geo_node_name') }}</code>
<template #code="{ content }">
<code>{{ content }}</code>
</template>
<template #externalUrl>
<code>{{ __('external_url') }}</code>
<template #link="{ content }">
<gl-link :href="$options.NODE_NAME_MORE_INFO" target="_blank">{{ content }}</gl-link>
</template>
</gl-sprintf>
</template>
<gl-form-input
id="node-name-field"
v-model="nodeData.name"
<div
:class="{ 'is-invalid': Boolean(formErrors.name) }"
data-qa-selector="node_name_field"
type="text"
@input="checkName"
/>
</gl-form-group>
<gl-form-group
class="col-sm-6"
:label="__('URL')"
label-for="node-url-field"
:description="__('The user-facing URL of the Geo node')"
:state="Boolean(formErrors.url)"
:invalid-feedback="formErrors.url"
>
<gl-form-input
id="node-url-field"
v-model="nodeData.url"
:class="{ 'is-invalid': Boolean(formErrors.url) }"
data-qa-selector="node_url_field"
type="text"
@input="checkUrl"
/>
class="gl-display-flex gl-align-items-center"
>
<gl-form-input
id="node-name-field"
v-model="nodeData.name"
class="col-sm-6 gl-pr-8!"
:class="{ 'is-invalid': Boolean(formErrors.name) }"
data-qa-selector="node_name_field"
type="text"
@input="checkName"
/>
<span class="gl-text-gray-700 m-n5 gl-z-index-2">{{ 255 - nodeData.name.length }}</span>
</div>
</gl-form-group>
<section class="form-row">
<gl-form-group
class="col-12 col-sm-6"
:label="__('URL')"
label-for="node-url-field"
:state="Boolean(formErrors.url)"
:invalid-feedback="formErrors.url"
>
<template #description>
<gl-sprintf
:message="
__(
'Must match with the %{codeStart}external_url%{codeEnd} in %{codeStart}/etc/gitlab/gitlab.rb%{codeEnd}.',
)
"
>
<template #code="{ content }">
<code>{{ content }}</code>
</template>
</gl-sprintf>
</template>
<div
:class="{ 'is-invalid': Boolean(formErrors.url) }"
class="gl-display-flex gl-align-items-center"
>
<gl-form-input
id="node-url-field"
v-model="nodeData.url"
class="gl-pr-8!"
:class="{ 'is-invalid': Boolean(formErrors.url) }"
data-qa-selector="node_url_field"
type="text"
@input="checkUrl"
/>
<span class="gl-text-gray-700 m-n5 gl-z-index-2">{{ 255 - nodeData.url.length }}</span>
</div>
</gl-form-group>
<gl-form-group
v-if="nodeData.primary"
class="col-12 col-sm-6"
:label="__('Internal URL (optional)')"
label-for="node-internal-url-field"
:description="
__('The URL defined on the primary node that secondary nodes should use to contact it.')
"
>
<div class="gl-display-flex gl-align-items-center">
<gl-form-input
id="node-internal-url-field"
v-model="nodeData.internalUrl"
class="gl-pr-8!"
type="text"
/>
<span class="gl-text-gray-700 m-n5 gl-z-index-2">{{
255 - nodeData.internalUrl.length
}}</span>
</div>
</gl-form-group>
</section>
</section>
</template>
<script>
import { GlFormGroup, GlFormSelect } from '@gitlab/ui';
import { GlFormGroup, GlFormSelect, GlFormCheckbox, GlSprintf, GlLink } from '@gitlab/ui';
import GeoNodeFormNamespaces from './geo_node_form_namespaces.vue';
import GeoNodeFormShards from './geo_node_form_shards.vue';
import { SELECTIVE_SYNC_MORE_INFO, OBJECT_STORAGE_MORE_INFO } from '../constants';
export default {
name: 'GeoNodeFormSelectiveSync',
......@@ -10,6 +11,9 @@ export default {
GlFormSelect,
GeoNodeFormNamespaces,
GeoNodeFormShards,
GlFormCheckbox,
GlSprintf,
GlLink,
},
props: {
nodeData: {
......@@ -41,11 +45,27 @@ export default {
this.$emit('removeSyncOption', { key, index });
},
},
SELECTIVE_SYNC_MORE_INFO,
OBJECT_STORAGE_MORE_INFO,
};
</script>
<template>
<div ref="geoNodeFormSelectiveSyncContainer">
<h2 class="gl-font-size-h2 gl-my-5">{{ __('Selective synchronization') }}</h2>
<p class="gl-mb-5">
{{
__(
'Set what should be replicated by choosing specific projects or groups by the secondary node.',
)
}}
<gl-link
:href="$options.SELECTIVE_SYNC_MORE_INFO"
target="_blank"
data-testid="selectiveSyncMoreInfo"
>{{ __('More information') }}</gl-link
>
</p>
<gl-form-group
:label="__('Selective synchronization')"
label-for="node-selective-synchronization-field"
......@@ -56,14 +76,13 @@ export default {
:options="selectiveSyncTypes"
value-field="value"
text-field="label"
class="col-sm-6"
class="col-sm-3"
/>
</gl-form-group>
<gl-form-group
v-if="selectiveSyncNamespaces"
:label="__('Groups to synchronize')"
label-for="node-synchronization-namespaces-field"
:description="__('Choose which groups you wish to synchronize to this secondary node')"
>
<geo-node-form-namespaces
id="node-synchronization-namespaces-field"
......@@ -76,7 +95,6 @@ export default {
v-if="selectiveSyncShards"
:label="__('Shards to synchronize')"
label-for="node-synchronization-shards-field"
:description="__('Choose which shards you wish to synchronize to this secondary node')"
>
<geo-node-form-shards
id="node-synchronization-shards-field"
......@@ -86,5 +104,28 @@ export default {
@removeSyncOption="removeSyncOption"
/>
</gl-form-group>
<gl-form-group :label="__('Object Storage replication')" label-for="node-object-storage-field">
<template #description>
<gl-sprintf
:message="
__(
'If enabled, GitLab will handle Object Storage replication using Geo. %{linkStart}More information%{linkEnd}',
)
"
>
<template #link="{ content }">
<gl-link
:href="$options.OBJECT_STORAGE_MORE_INFO"
data-testid="objectStorageMoreInfo"
target="_blank"
>{{ content }}</gl-link
>
</template>
</gl-sprintf>
</template>
<gl-form-checkbox id="node-object-storage-field" v-model="nodeData.syncObjectStorage">{{
__('Allow this secondary node to replicate content on Object Storage')
}}</gl-form-checkbox>
</gl-form-group>
</div>
</template>
......@@ -9,3 +9,18 @@ export const VALIDATION_FIELD_KEYS = {
VERIFICATION_MAX_CAPACITY: 'verificationMaxCapacity',
MINIMUM_REVERIFICATION_INTERVAL: 'minimumReverificationInterval',
};
export const NODE_NAME_MORE_INFO =
'https://docs.gitlab.com/ee/administration/geo/replication/configuration.html#step-3-add-the-secondary-node';
export const SELECTIVE_SYNC_MORE_INFO =
'https://docs.gitlab.com/ee/administration/geo/replication/configuration.html#selective-synchronization';
export const OBJECT_STORAGE_MORE_INFO =
'https://docs.gitlab.com/ee/administration/geo/replication/object_storage.html';
export const REVERIFICATION_MORE_INFO =
'https://docs.gitlab.com/ee/administration/geo/disaster_recovery/background_verification.html#repository-re-verification';
export const BACKFILL_MORE_INFO =
'https://docs.gitlab.com/ee/user/admin_area/geo_nodes.html#geo-backfill';
......@@ -29,7 +29,7 @@ class Admin::Geo::NodesController < Admin::Geo::ApplicationController
end
def new
@form_title = _('New Geo Node')
@form_title = _('Add New Node')
render :form
end
......
---
title: Geo Node Form - Redesign
merge_request: 34561
author:
type: changed
......@@ -132,7 +132,7 @@ RSpec.describe 'admin Geo Nodes', :js, :geo do
fill_in 'node-url-field', with: 'http://newsite.com'
fill_in 'node-internal-url-field', with: 'http://internal-url.com'
click_button 'Update'
click_button 'Save changes'
wait_for_requests
expect(current_path).to eq admin_geo_nodes_path
......
......@@ -23,7 +23,7 @@ describe('GeoNodeFormApp', () => {
wrapper.destroy();
});
const findGeoNodeFormTitle = () => wrapper.find('.page-title');
const findGeoNodeFormTitle = () => wrapper.find('h2');
const findGeoNodeFormBadge = () => wrapper.find(GlBadge);
const findGeoForm = () => wrapper.find(GeoNodeForm);
......@@ -34,7 +34,7 @@ describe('GeoNodeFormApp', () => {
describe.each`
formType | node | title | pillTitle | variant
${'create a secondary node'} | ${null} | ${'New Geo Node'} | ${'Secondary'} | ${'light'}
${'create a secondary node'} | ${null} | ${'Add New Node'} | ${'Secondary'} | ${'light'}
${'update a secondary node'} | ${{ primary: false }} | ${'Edit Geo Node'} | ${'Secondary'} | ${'light'}
${'update a primary node'} | ${{ primary: true }} | ${'Edit Geo Node'} | ${'Primary'} | ${'primary'}
`(`form header`, ({ formType, node, title, pillTitle, variant }) => {
......
import Vuex from 'vuex';
import { createLocalVue, mount } from '@vue/test-utils';
import { GlLink } from '@gitlab/ui';
import GeoNodeFormCapacities from 'ee/geo_node_form/components/geo_node_form_capacities.vue';
import { VALIDATION_FIELD_KEYS } from 'ee/geo_node_form/constants';
import {
VALIDATION_FIELD_KEYS,
REVERIFICATION_MORE_INFO,
BACKFILL_MORE_INFO,
} from 'ee/geo_node_form/constants';
import { MOCK_NODE } from '../mock_data';
const localVue = createLocalVue();
......@@ -44,6 +49,8 @@ describe('GeoNodeFormCapacities', () => {
wrapper.destroy();
});
const findGeoNodeFormCapcitiesSectionDescription = () => wrapper.find('p');
const findGeoNodeFormCapacitiesMoreInfoLink = () => wrapper.find(GlLink);
const findGeoNodeFormRepositoryCapacityField = () =>
wrapper.find('#node-repository-capacity-field');
const findGeoNodeFormFileCapacityField = () => wrapper.find('#node-file-capacity-field');
......@@ -56,6 +63,28 @@ describe('GeoNodeFormCapacities', () => {
const findErrorMessage = () => wrapper.find('.invalid-feedback');
describe('template', () => {
describe.each`
primaryNode | description | link
${true} | ${'Set the synchronization and verification capacity for the secondary node.'} | ${REVERIFICATION_MORE_INFO}
${false} | ${'Set the number of concurrent requests this secondary node will make to the primary node while backfilling.'} | ${BACKFILL_MORE_INFO}
`(`section description`, ({ primaryNode, description, link }) => {
describe(`when node is ${primaryNode ? 'primary' : 'secondary'}`, () => {
beforeEach(() => {
createComponent({
nodeData: { ...defaultProps.nodeData, primary: primaryNode },
});
});
it(`sets section description correctly`, () => {
expect(findGeoNodeFormCapcitiesSectionDescription().text()).toContain(description);
});
it(`sets section More Information link correctly`, () => {
expect(findGeoNodeFormCapacitiesMoreInfoLink().attributes('href')).toBe(link);
});
});
});
describe.each`
primaryNode | showRepoCapacity | showFileCapacity | showVerificationCapacity | showContainerCapacity | showReverificationInterval
${true} | ${false} | ${false} | ${true} | ${false} | ${true}
......@@ -70,42 +99,44 @@ describe('GeoNodeFormCapacities', () => {
showVerificationCapacity,
showReverificationInterval,
}) => {
beforeEach(() => {
createComponent({
nodeData: { ...defaultProps.nodeData, primary: primaryNode },
describe(`when node is ${primaryNode ? 'primary' : 'secondary'}`, () => {
beforeEach(() => {
createComponent({
nodeData: { ...defaultProps.nodeData, primary: primaryNode },
});
});
});
it(`it ${showRepoCapacity ? 'shows' : 'hides'} the Repository Capacity Field`, () => {
expect(findGeoNodeFormRepositoryCapacityField().exists()).toBe(showRepoCapacity);
});
it(`it ${showRepoCapacity ? 'shows' : 'hides'} the Repository Capacity Field`, () => {
expect(findGeoNodeFormRepositoryCapacityField().exists()).toBe(showRepoCapacity);
});
it(`it ${showFileCapacity ? 'shows' : 'hides'} the File Capacity Field`, () => {
expect(findGeoNodeFormFileCapacityField().exists()).toBe(showFileCapacity);
});
it(`it ${showFileCapacity ? 'shows' : 'hides'} the File Capacity Field`, () => {
expect(findGeoNodeFormFileCapacityField().exists()).toBe(showFileCapacity);
});
it(`it ${
showContainerCapacity ? 'shows' : 'hides'
} the Container Repository Capacity Field`, () => {
expect(findGeoNodeFormContainerRepositoryCapacityField().exists()).toBe(
showContainerCapacity,
);
});
it(`it ${
showContainerCapacity ? 'shows' : 'hides'
} the Container Repository Capacity Field`, () => {
expect(findGeoNodeFormContainerRepositoryCapacityField().exists()).toBe(
showContainerCapacity,
);
});
it(`it ${
showVerificationCapacity ? 'shows' : 'hides'
} the Verification Capacity Field`, () => {
expect(findGeoNodeFormVerificationCapacityField().exists()).toBe(
showVerificationCapacity,
);
});
it(`it ${
showVerificationCapacity ? 'shows' : 'hides'
} the Verification Capacity Field`, () => {
expect(findGeoNodeFormVerificationCapacityField().exists()).toBe(
showVerificationCapacity,
);
});
it(`it ${
showReverificationInterval ? 'shows' : 'hides'
} the Reverification Interval Field`, () => {
expect(findGeoNodeFormReverificationIntervalField().exists()).toBe(
showReverificationInterval,
);
it(`it ${
showReverificationInterval ? 'shows' : 'hides'
} the Reverification Interval Field`, () => {
expect(findGeoNodeFormReverificationIntervalField().exists()).toBe(
showReverificationInterval,
);
});
});
},
);
......@@ -120,7 +151,7 @@ describe('GeoNodeFormCapacities', () => {
${999} | ${false} | ${null}
${1000} | ${true} | ${'should be between 1-999'}
`(`errors`, ({ data, showError, errorMessage }) => {
describe('on primary node', () => {
describe('when node is primary', () => {
beforeEach(() => {
createComponent({
nodeData: { ...defaultProps.nodeData, primary: true },
......@@ -158,7 +189,7 @@ describe('GeoNodeFormCapacities', () => {
});
});
describe('on secondary node', () => {
describe('when node is secondary', () => {
beforeEach(() => {
createComponent();
});
......@@ -226,7 +257,7 @@ describe('GeoNodeFormCapacities', () => {
describe('computed', () => {
describe('visibleFormGroups', () => {
describe('when nodeData.primary is true', () => {
describe('when node is primary', () => {
beforeEach(() => {
createComponent({
nodeData: { ...defaultProps.nodeData, primary: true },
......@@ -242,7 +273,7 @@ describe('GeoNodeFormCapacities', () => {
});
});
describe('when nodeData.primary is false', () => {
describe('when node is secondary', () => {
beforeEach(() => {
createComponent();
});
......
import Vuex from 'vuex';
import { createLocalVue, mount } from '@vue/test-utils';
import { GlLink } from '@gitlab/ui';
import GeoNodeFormCore from 'ee/geo_node_form/components/geo_node_form_core.vue';
import { VALIDATION_FIELD_KEYS } from 'ee/geo_node_form/constants';
import { VALIDATION_FIELD_KEYS, NODE_NAME_MORE_INFO } from 'ee/geo_node_form/constants';
import { MOCK_NODE, STRING_OVER_255 } from '../mock_data';
const localVue = createLocalVue();
......@@ -45,7 +46,9 @@ describe('GeoNodeFormCore', () => {
});
const findGeoNodeFormNameField = () => wrapper.find('#node-name-field');
const findGeoNodeFormNameMoreInformation = () => wrapper.find(GlLink);
const findGeoNodeFormUrlField = () => wrapper.find('#node-url-field');
const findGeoNodeInternalUrlField = () => wrapper.find('#node-internal-url-field');
const findErrorMessage = () => wrapper.find('.invalid-feedback');
describe('template', () => {
......@@ -61,6 +64,28 @@ describe('GeoNodeFormCore', () => {
expect(findGeoNodeFormUrlField().exists()).toBe(true);
});
it('renders Geo Node Form Name More Information link correctly', () => {
expect(findGeoNodeFormNameMoreInformation().attributes('href')).toBe(NODE_NAME_MORE_INFO);
});
describe.each`
primaryNode | showInternalUrl
${true} | ${true}
${false} | ${false}
`(`conditional fields`, ({ primaryNode, showInternalUrl }) => {
describe(`when node is ${primaryNode ? 'primary' : 'secondary'}`, () => {
beforeEach(() => {
createComponent({
nodeData: { ...defaultProps.nodeData, primary: primaryNode },
});
});
it(`it ${showInternalUrl ? 'shows' : 'hides'} the Internal URL Field`, () => {
expect(findGeoNodeInternalUrlField().exists()).toBe(showInternalUrl);
});
});
});
describe('errors', () => {
describe.each`
data | showError | errorMessage
......@@ -89,7 +114,7 @@ describe('GeoNodeFormCore', () => {
${''} | ${true} | ${"URL can't be blank"}
${'abcd'} | ${true} | ${'URL must be a valid url (ex: https://gitlab.com)'}
${'https://gitlab.com'} | ${false} | ${null}
`(`Name Field`, ({ data, showError, errorMessage }) => {
`(`URL Field`, ({ data, showError, errorMessage }) => {
beforeEach(() => {
createComponent();
findGeoNodeFormUrlField().setValue(data);
......
import { shallowMount } from '@vue/test-utils';
import { GlFormGroup, GlSprintf } from '@gitlab/ui';
import GeoNodeFormSelectiveSync from 'ee/geo_node_form/components/geo_node_form_selective_sync.vue';
import GeoNodeFormNamespaces from 'ee/geo_node_form/components/geo_node_form_namespaces.vue';
import GeoNodeFormShards from 'ee/geo_node_form/components/geo_node_form_shards.vue';
import { SELECTIVE_SYNC_MORE_INFO, OBJECT_STORAGE_MORE_INFO } from 'ee/geo_node_form/constants';
import { MOCK_NODE, MOCK_SELECTIVE_SYNC_TYPES, MOCK_SYNC_SHARDS } from '../mock_data';
describe('GeoNodeFormSelectiveSync', () => {
......@@ -15,6 +17,7 @@ describe('GeoNodeFormSelectiveSync', () => {
const createComponent = (props = {}) => {
wrapper = shallowMount(GeoNodeFormSelectiveSync, {
stubs: { GlFormGroup, GlSprintf },
propsData: {
...defaultProps,
...props,
......@@ -28,15 +31,26 @@ describe('GeoNodeFormSelectiveSync', () => {
const findGeoNodeFormSyncContainer = () =>
wrapper.find({ ref: 'geoNodeFormSelectiveSyncContainer' });
const findGeoNodeFormSelectiveSyncMoreInfoLink = () =>
wrapper.find('[data-testid="selectiveSyncMoreInfo"]');
const findGeoNodeFormSyncTypeField = () => wrapper.find('#node-selective-synchronization-field');
const findGeoNodeFormNamespacesField = () => wrapper.find(GeoNodeFormNamespaces);
const findGeoNodeFormShardsField = () => wrapper.find(GeoNodeFormShards);
const findGeoNodeObjectStorageField = () => wrapper.find('#node-object-storage-field');
const findGeoNodeFormObjectStorageMoreInformation = () =>
wrapper.find('[data-testid="objectStorageMoreInfo"]');
describe('template', () => {
beforeEach(() => {
createComponent();
});
it('renders section More Information link correctly', () => {
expect(findGeoNodeFormSelectiveSyncMoreInfoLink().attributes('href')).toBe(
SELECTIVE_SYNC_MORE_INFO,
);
});
it('renders Geo Node Form Sync Container', () => {
expect(findGeoNodeFormSyncContainer().exists()).toBe(true);
});
......@@ -45,6 +59,16 @@ describe('GeoNodeFormSelectiveSync', () => {
expect(findGeoNodeFormSyncTypeField().exists()).toBe(true);
});
it('renders Geo Node Object Storage Field', () => {
expect(findGeoNodeObjectStorageField().exists()).toBe(true);
});
it('renders Geo Node Form Object Storage More Information link correctly', () => {
expect(findGeoNodeFormObjectStorageMoreInformation().attributes('href')).toBe(
OBJECT_STORAGE_MORE_INFO,
);
});
describe.each`
syncType | showNamespaces | showShards
${MOCK_SELECTIVE_SYNC_TYPES.ALL} | ${false} | ${false}
......
......@@ -3,6 +3,7 @@ import { createLocalVue, shallowMount } from '@vue/test-utils';
import { visitUrl } from '~/lib/utils/url_utility';
import GeoNodeForm from 'ee/geo_node_form/components/geo_node_form.vue';
import GeoNodeFormCore from 'ee/geo_node_form/components/geo_node_form_core.vue';
import GeoNodeFormSelectiveSync from 'ee/geo_node_form/components/geo_node_form_selective_sync.vue';
import GeoNodeFormCapacities from 'ee/geo_node_form/components/geo_node_form_capacities.vue';
import store from 'ee/geo_node_form/store';
import { MOCK_NODE, MOCK_SELECTIVE_SYNC_TYPES, MOCK_SYNC_SHARDS } from '../mock_data';
......@@ -35,9 +36,8 @@ describe('GeoNodeForm', () => {
});
const findGeoNodeFormCoreField = () => wrapper.find(GeoNodeFormCore);
const findGeoNodeInternalUrlField = () => wrapper.find('#node-internal-url-field');
const findGeoNodeFormSelectiveSyncField = () => wrapper.find(GeoNodeFormSelectiveSync);
const findGeoNodeFormCapacitiesField = () => wrapper.find(GeoNodeFormCapacities);
const findGeoNodeObjectStorageField = () => wrapper.find('#node-object-storage-field');
const findGeoNodeSaveButton = () => wrapper.find('#node-save-button');
const findGeoNodeCancelButton = () => wrapper.find('#node-cancel-button');
......@@ -47,35 +47,28 @@ describe('GeoNodeForm', () => {
});
describe.each`
primaryNode | showCore | showInternalUrl | showCapacities | showObjectStorage
${true} | ${true} | ${true} | ${true} | ${false}
${false} | ${true} | ${false} | ${true} | ${true}
`(
`conditional fields`,
({ primaryNode, showCore, showInternalUrl, showCapacities, showObjectStorage }) => {
beforeEach(() => {
wrapper.setData({
nodeData: { ...wrapper.vm.nodeData, primary: primaryNode },
});
});
it(`it ${showCore ? 'shows' : 'hides'} the Core Field`, () => {
expect(findGeoNodeFormCoreField().exists()).toBe(showCore);
primaryNode | showCore | showSelectiveSync | showCapacities
${true} | ${true} | ${false} | ${true}
${false} | ${true} | ${true} | ${true}
`(`conditional fields`, ({ primaryNode, showCore, showSelectiveSync, showCapacities }) => {
beforeEach(() => {
wrapper.setData({
nodeData: { ...wrapper.vm.nodeData, primary: primaryNode },
});
});
it(`it ${showInternalUrl ? 'shows' : 'hides'} the Internal URL Field`, () => {
expect(findGeoNodeInternalUrlField().exists()).toBe(showInternalUrl);
});
it(`it ${showCore ? 'shows' : 'hides'} the Core Field`, () => {
expect(findGeoNodeFormCoreField().exists()).toBe(showCore);
});
it(`it ${showCapacities ? 'shows' : 'hides'} the Capacities Field`, () => {
expect(findGeoNodeFormCapacitiesField().exists()).toBe(showCapacities);
});
it(`it ${showSelectiveSync ? 'shows' : 'hides'} the Selective Sync Field`, () => {
expect(findGeoNodeFormSelectiveSyncField().exists()).toBe(showSelectiveSync);
});
it(`it ${showObjectStorage ? 'shows' : 'hides'} the Object Storage Field`, () => {
expect(findGeoNodeObjectStorageField().exists()).toBe(showObjectStorage);
});
},
);
it(`it ${showCapacities ? 'shows' : 'hides'} the Capacities Field`, () => {
expect(findGeoNodeFormCapacitiesField().exists()).toBe(showCapacities);
});
});
describe('Save Button', () => {
describe('with errors on form', () => {
......
......@@ -1322,6 +1322,9 @@ msgstr ""
msgid "Add LICENSE"
msgstr ""
msgid "Add New Node"
msgstr ""
msgid "Add README"
msgstr ""
......@@ -4429,15 +4432,9 @@ msgstr ""
msgid "Choose what content you want to see on a group’s overview page"
msgstr ""
msgid "Choose which groups you wish to synchronize to this secondary node"
msgstr ""
msgid "Choose which repositories you want to connect and run CI/CD pipelines."
msgstr ""
msgid "Choose which shards you wish to synchronize to this secondary node"
msgstr ""
msgid "Choose your framework"
msgstr ""
......@@ -6384,21 +6381,6 @@ msgstr ""
msgid "Control the display of third party offers."
msgstr ""
msgid "Control the maximum concurrency of LFS/attachment backfill for this secondary node"
msgstr ""
msgid "Control the maximum concurrency of container repository operations for this Geo node"
msgstr ""
msgid "Control the maximum concurrency of repository backfill for this secondary node"
msgstr ""
msgid "Control the maximum concurrency of verification operations for this Geo node"
msgstr ""
msgid "Control the minimum interval in days that a repository should be reverified for this primary node"
msgstr ""
msgid "Cookie domain"
msgstr ""
......@@ -11958,10 +11940,10 @@ msgstr ""
msgid "If enabled"
msgstr ""
msgid "If enabled, access to projects will be validated on an external service using their classification label."
msgid "If enabled, GitLab will handle Object Storage replication using Geo. %{linkStart}More information%{linkEnd}"
msgstr ""
msgid "If enabled, and if object storage is enabled, GitLab will handle Object Storage replication using Geo"
msgid "If enabled, access to projects will be validated on an external service using their classification label."
msgstr ""
msgid "If there is no previous license or if the previous license has expired, some GitLab functionality will be blocked until a new, valid license is uploaded."
......@@ -14607,6 +14589,9 @@ msgstr ""
msgid "Minimum capacity to be available before we schedule more mirrors preemptively."
msgstr ""
msgid "Minimum interval in days"
msgstr ""
msgid "Minimum length is %{minimum_password_length} characters"
msgstr ""
......@@ -14793,6 +14778,12 @@ msgstr ""
msgid "Multiple uploaders found: %{uploader_types}"
msgstr ""
msgid "Must match with the %{codeStart}external_url%{codeEnd} in %{codeStart}/etc/gitlab/gitlab.rb%{codeEnd}."
msgstr ""
msgid "Must match with the %{codeStart}geo_node_name%{codeEnd} in %{codeStart}/etc/gitlab/gitlab.rb%{codeEnd}. %{linkStart}More information%{linkEnd}"
msgstr ""
msgid "My Awesome Group"
msgstr ""
......@@ -14919,9 +14910,6 @@ msgstr ""
msgid "New File"
msgstr ""
msgid "New Geo Node"
msgstr ""
msgid "New Group"
msgstr ""
......@@ -16270,6 +16258,9 @@ msgstr ""
msgid "Perform common operations on GitLab project"
msgstr ""
msgid "Performance and resource management"
msgstr ""
msgid "Performance optimization"
msgstr ""
......@@ -20658,6 +20649,12 @@ msgstr ""
msgid "Set the milestone to %{milestone_reference}."
msgstr ""
msgid "Set the number of concurrent requests this secondary node will make to the primary node while backfilling."
msgstr ""
msgid "Set the synchronization and verification capacity for the secondary node."
msgstr ""
msgid "Set the timeout in seconds to send a secondary node status to the primary and IPs allowed for the secondary nodes."
msgstr ""
......@@ -20700,6 +20697,9 @@ msgstr ""
msgid "Set weight to %{weight}."
msgstr ""
msgid "Set what should be replicated by choosing specific projects or groups by the secondary node."
msgstr ""
msgid "SetPasswordToCloneLink|set a password"
msgstr ""
......@@ -22490,7 +22490,7 @@ msgstr ""
msgid "The Terraform report %{name} was generated in your pipelines."
msgstr ""
msgid "The URL defined on the primary node that secondary nodes should use to contact it. Defaults to URL"
msgid "The URL defined on the primary node that secondary nodes should use to contact it."
msgstr ""
msgid "The URL to use for connecting to Elasticsearch. Use a comma-separated list to support clustering (e.g., \"http://localhost:9200, http://localhost:9201\")."
......@@ -22801,9 +22801,6 @@ msgstr ""
msgid "The total stage shows the time it takes between creating an issue and deploying the code to production. The data will be automatically added once you have completed the full idea to production cycle."
msgstr ""
msgid "The unique identifier for the Geo node. Must match %{geoNodeName} if it is set in gitlab.rb, otherwise it must match %{externalUrl} with a trailing slash"
msgstr ""
msgid "The update action will time out after %{number_of_minutes} minutes. For big repositories, use a clone/push combination."
msgstr ""
......@@ -22828,9 +22825,6 @@ msgstr ""
msgid "The user you are trying to deactivate has been active in the past %{minimum_inactive_days} days and cannot be deactivated"
msgstr ""
msgid "The user-facing URL of the Geo node"
msgstr ""
msgid "The value lying at the midpoint of a series of observed values. E.g., between 3, 5, 9, the median is 5. Between 3, 5, 7, 8, the median is (5+7)/2 = 6."
msgstr ""
......@@ -27115,9 +27109,6 @@ msgstr ""
msgid "expires on %{timebox_due_date}"
msgstr ""
msgid "external_url"
msgstr ""
msgid "failed"
msgstr ""
......@@ -27153,9 +27144,6 @@ msgstr ""
msgid "from"
msgstr ""
msgid "geo_node_name"
msgstr ""
msgid "group"
msgstr ""
......
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