Commit 5ba0ca5e authored by Denys Mishunov's avatar Denys Mishunov

Merge branch '215027-redesign-geo-node-form' into 'master'

Geo Node Form - Redesign Form

Closes #215027

See merge request gitlab-org/gitlab!34561
parents 65a36f9d 8d4d2033
...@@ -29,7 +29,7 @@ export default { ...@@ -29,7 +29,7 @@ export default {
return this.node && this.node.primary; return this.node && this.node.primary;
}, },
pageTitle() { pageTitle() {
return this.node ? __('Edit Geo Node') : __('New Geo Node'); return this.node ? __('Edit Geo Node') : __('Add New Node');
}, },
pillDetails() { pillDetails() {
return { return {
...@@ -44,7 +44,7 @@ export default { ...@@ -44,7 +44,7 @@ export default {
<template> <template>
<article class="geo-node-form-container"> <article class="geo-node-form-container">
<div class="gl-display-flex gl-align-items-center"> <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 <gl-badge
class="rounded-pill gl-font-sm gl-px-3 gl-py-2 gl-ml-3" class="rounded-pill gl-font-sm gl-px-3 gl-py-2 gl-ml-3"
:variant="pillDetails.variant" :variant="pillDetails.variant"
......
<script> <script>
import { mapActions, mapGetters } from 'vuex'; import { mapActions, mapGetters } from 'vuex';
import { GlFormGroup, GlFormInput, GlFormCheckbox, GlButton } from '@gitlab/ui'; import { GlButton } from '@gitlab/ui';
import { __ } from '~/locale';
import { visitUrl } from '~/lib/utils/url_utility'; import { visitUrl } from '~/lib/utils/url_utility';
import GeoNodeFormCore from './geo_node_form_core.vue'; import GeoNodeFormCore from './geo_node_form_core.vue';
import GeoNodeFormSelectiveSync from './geo_node_form_selective_sync.vue'; import GeoNodeFormSelectiveSync from './geo_node_form_selective_sync.vue';
...@@ -10,9 +9,6 @@ import GeoNodeFormCapacities from './geo_node_form_capacities.vue'; ...@@ -10,9 +9,6 @@ import GeoNodeFormCapacities from './geo_node_form_capacities.vue';
export default { export default {
name: 'GeoNodeForm', name: 'GeoNodeForm',
components: { components: {
GlFormGroup,
GlFormInput,
GlFormCheckbox,
GlButton, GlButton,
GeoNodeFormCore, GeoNodeFormCore,
GeoNodeFormSelectiveSync, GeoNodeFormSelectiveSync,
...@@ -54,9 +50,6 @@ export default { ...@@ -54,9 +50,6 @@ export default {
}, },
computed: { computed: {
...mapGetters(['formHasError']), ...mapGetters(['formHasError']),
saveButtonTitle() {
return this.node ? __('Update') : __('Save');
},
}, },
created() { created() {
if (this.node) { if (this.node) {
...@@ -80,52 +73,30 @@ export default { ...@@ -80,52 +73,30 @@ export default {
<template> <template>
<form> <form>
<geo-node-form-core :node-data="nodeData" /> <geo-node-form-core
<section class="mt-3 pl-0 col-sm-6"> :node-data="nodeData"
<gl-form-group class="gl-pb-4 gl-border-b-1 gl-border-b-gray-100 gl-border-b-solid"
v-if="nodeData.primary" />
:label="__('Internal URL (optional)')" <geo-node-form-selective-sync
label-for="node-internal-url-field" v-if="!nodeData.primary"
:description=" class="gl-pb-4 gl-border-b-1 gl-border-b-gray-100 gl-border-b-solid"
__( :node-data="nodeData"
'The URL defined on the primary node that secondary nodes should use to contact it. Defaults to URL', :selective-sync-types="selectiveSyncTypes"
) :sync-shards-options="syncShardsOptions"
" @addSyncOption="addSyncOption"
> @removeSyncOption="removeSyncOption"
<gl-form-input id="node-internal-url-field" v-model="nodeData.internalUrl" type="text" /> />
</gl-form-group> <geo-node-form-capacities :node-data="nodeData" />
<geo-node-form-selective-sync <section
v-if="!nodeData.primary" 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"
: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">
<gl-button <gl-button
id="node-save-button" id="node-save-button"
data-qa-selector="add_node_button" data-qa-selector="add_node_button"
variant="success" variant="success"
:disabled="formHasError" :disabled="formHasError"
@click="saveGeoNode(nodeData)" @click="saveGeoNode(nodeData)"
>{{ saveButtonTitle }}</gl-button >{{ __('Save changes') }}</gl-button
> >
<gl-button id="node-cancel-button" class="gl-ml-auto" @click="redirect">{{ <gl-button id="node-cancel-button" class="gl-ml-auto" @click="redirect">{{
__('Cancel') __('Cancel')
......
<script> <script>
import { GlFormGroup, GlFormInput } from '@gitlab/ui'; import { GlFormGroup, GlFormInput, GlLink } from '@gitlab/ui';
import { mapActions, mapState } from 'vuex'; import { mapActions, mapState } from 'vuex';
import { __ } from '~/locale'; import { __ } from '~/locale';
import { validateCapacity } from '../validations'; import { validateCapacity } from '../validations';
import { VALIDATION_FIELD_KEYS } from '../constants'; import { VALIDATION_FIELD_KEYS, REVERIFICATION_MORE_INFO, BACKFILL_MORE_INFO } from '../constants';
export default { export default {
name: 'GeoNodeFormCapacities', name: 'GeoNodeFormCapacities',
components: { components: {
GlFormGroup, GlFormGroup,
GlFormInput, GlFormInput,
GlLink,
}, },
props: { props: {
nodeData: { nodeData: {
...@@ -23,44 +24,30 @@ export default { ...@@ -23,44 +24,30 @@ export default {
{ {
id: 'node-repository-capacity-field', id: 'node-repository-capacity-field',
label: __('Repository sync capacity'), label: __('Repository sync capacity'),
description: __(
'Control the maximum concurrency of repository backfill for this secondary node',
),
key: VALIDATION_FIELD_KEYS.REPOS_MAX_CAPACITY, key: VALIDATION_FIELD_KEYS.REPOS_MAX_CAPACITY,
conditional: 'secondary', conditional: 'secondary',
}, },
{ {
id: 'node-file-capacity-field', id: 'node-file-capacity-field',
label: __('File sync capacity'), label: __('File sync capacity'),
description: __(
'Control the maximum concurrency of LFS/attachment backfill for this secondary node',
),
key: VALIDATION_FIELD_KEYS.FILES_MAX_CAPACITY, key: VALIDATION_FIELD_KEYS.FILES_MAX_CAPACITY,
conditional: 'secondary', conditional: 'secondary',
}, },
{ {
id: 'node-container-repository-capacity-field', id: 'node-container-repository-capacity-field',
label: __('Container repositories sync capacity'), 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, key: VALIDATION_FIELD_KEYS.CONTAINER_REPOSITORIES_MAX_CAPACITY,
conditional: 'secondary', conditional: 'secondary',
}, },
{ {
id: 'node-verification-capacity-field', id: 'node-verification-capacity-field',
label: __('Verification capacity'), label: __('Verification capacity'),
description: __(
'Control the maximum concurrency of verification operations for this Geo node',
),
key: VALIDATION_FIELD_KEYS.VERIFICATION_MAX_CAPACITY, key: VALIDATION_FIELD_KEYS.VERIFICATION_MAX_CAPACITY,
}, },
{ {
id: 'node-reverification-interval-field', id: 'node-reverification-interval-field',
label: __('Re-verification interval'), label: __('Re-verification interval'),
description: __( description: __('Minimum interval in days'),
'Control the minimum interval in days that a repository should be reverified for this primary node',
),
key: VALIDATION_FIELD_KEYS.MINIMUM_REVERIFICATION_INTERVAL, key: VALIDATION_FIELD_KEYS.MINIMUM_REVERIFICATION_INTERVAL,
conditional: 'primary', conditional: 'primary',
}, },
...@@ -79,6 +66,16 @@ export default { ...@@ -79,6 +66,16 @@ export default {
return true; 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: { methods: {
...mapActions(['setError']), ...mapActions(['setError']),
...@@ -94,6 +91,11 @@ export default { ...@@ -94,6 +91,11 @@ export default {
<template> <template>
<div> <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 <gl-form-group
v-for="formGroup in visibleFormGroups" v-for="formGroup in visibleFormGroups"
:key="formGroup.id" :key="formGroup.id"
......
<script> <script>
import { GlFormGroup, GlFormInput, GlSprintf } from '@gitlab/ui'; import { GlFormGroup, GlFormInput, GlSprintf, GlLink } from '@gitlab/ui';
import { mapActions, mapState } from 'vuex'; import { mapActions, mapState } from 'vuex';
import { validateName, validateUrl } from '../validations'; import { validateName, validateUrl } from '../validations';
import { VALIDATION_FIELD_KEYS } from '../constants'; import { VALIDATION_FIELD_KEYS, NODE_NAME_MORE_INFO } from '../constants';
export default { export default {
name: 'GeoNodeFormCore', name: 'GeoNodeFormCore',
...@@ -10,6 +10,7 @@ export default { ...@@ -10,6 +10,7 @@ export default {
GlFormGroup, GlFormGroup,
GlFormInput, GlFormInput,
GlSprintf, GlSprintf,
GlLink,
}, },
props: { props: {
nodeData: { nodeData: {
...@@ -29,13 +30,13 @@ export default { ...@@ -29,13 +30,13 @@ export default {
this.setError({ key: VALIDATION_FIELD_KEYS.URL, error: validateUrl(this.nodeData.url) }); this.setError({ key: VALIDATION_FIELD_KEYS.URL, error: validateUrl(this.nodeData.url) });
}, },
}, },
NODE_NAME_MORE_INFO,
}; };
</script> </script>
<template> <template>
<section class="form-row"> <section>
<gl-form-group <gl-form-group
class="col-sm-6"
:label="__('Name')" :label="__('Name')"
label-for="node-name-field" label-for="node-name-field"
:state="Boolean(formErrors.name)" :state="Boolean(formErrors.name)"
...@@ -45,43 +46,92 @@ export default { ...@@ -45,43 +46,92 @@ export default {
<gl-sprintf <gl-sprintf
:message=" :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> <template #code="{ content }">
<code>{{ __('geo_node_name') }}</code> <code>{{ content }}</code>
</template> </template>
<template #externalUrl> <template #link="{ content }">
<code>{{ __('external_url') }}</code> <gl-link :href="$options.NODE_NAME_MORE_INFO" target="_blank">{{ content }}</gl-link>
</template> </template>
</gl-sprintf> </gl-sprintf>
</template> </template>
<gl-form-input <div
id="node-name-field"
v-model="nodeData.name"
:class="{ 'is-invalid': Boolean(formErrors.name) }" :class="{ 'is-invalid': Boolean(formErrors.name) }"
data-qa-selector="node_name_field" class="gl-display-flex gl-align-items-center"
type="text" >
@input="checkName" <gl-form-input
/> id="node-name-field"
</gl-form-group> v-model="nodeData.name"
<gl-form-group class="col-sm-6 gl-pr-8!"
class="col-sm-6" :class="{ 'is-invalid': Boolean(formErrors.name) }"
:label="__('URL')" data-qa-selector="node_name_field"
label-for="node-url-field" type="text"
:description="__('The user-facing URL of the Geo node')" @input="checkName"
:state="Boolean(formErrors.url)" />
:invalid-feedback="formErrors.url" <span class="gl-text-gray-700 m-n5 gl-z-index-2">{{ 255 - nodeData.name.length }}</span>
> </div>
<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"
/>
</gl-form-group> </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> </section>
</template> </template>
<script> <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 GeoNodeFormNamespaces from './geo_node_form_namespaces.vue';
import GeoNodeFormShards from './geo_node_form_shards.vue'; import GeoNodeFormShards from './geo_node_form_shards.vue';
import { SELECTIVE_SYNC_MORE_INFO, OBJECT_STORAGE_MORE_INFO } from '../constants';
export default { export default {
name: 'GeoNodeFormSelectiveSync', name: 'GeoNodeFormSelectiveSync',
...@@ -10,6 +11,9 @@ export default { ...@@ -10,6 +11,9 @@ export default {
GlFormSelect, GlFormSelect,
GeoNodeFormNamespaces, GeoNodeFormNamespaces,
GeoNodeFormShards, GeoNodeFormShards,
GlFormCheckbox,
GlSprintf,
GlLink,
}, },
props: { props: {
nodeData: { nodeData: {
...@@ -41,11 +45,27 @@ export default { ...@@ -41,11 +45,27 @@ export default {
this.$emit('removeSyncOption', { key, index }); this.$emit('removeSyncOption', { key, index });
}, },
}, },
SELECTIVE_SYNC_MORE_INFO,
OBJECT_STORAGE_MORE_INFO,
}; };
</script> </script>
<template> <template>
<div ref="geoNodeFormSelectiveSyncContainer"> <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 <gl-form-group
:label="__('Selective synchronization')" :label="__('Selective synchronization')"
label-for="node-selective-synchronization-field" label-for="node-selective-synchronization-field"
...@@ -56,14 +76,13 @@ export default { ...@@ -56,14 +76,13 @@ export default {
:options="selectiveSyncTypes" :options="selectiveSyncTypes"
value-field="value" value-field="value"
text-field="label" text-field="label"
class="col-sm-6" class="col-sm-3"
/> />
</gl-form-group> </gl-form-group>
<gl-form-group <gl-form-group
v-if="selectiveSyncNamespaces" v-if="selectiveSyncNamespaces"
:label="__('Groups to synchronize')" :label="__('Groups to synchronize')"
label-for="node-synchronization-namespaces-field" label-for="node-synchronization-namespaces-field"
:description="__('Choose which groups you wish to synchronize to this secondary node')"
> >
<geo-node-form-namespaces <geo-node-form-namespaces
id="node-synchronization-namespaces-field" id="node-synchronization-namespaces-field"
...@@ -76,7 +95,6 @@ export default { ...@@ -76,7 +95,6 @@ export default {
v-if="selectiveSyncShards" v-if="selectiveSyncShards"
:label="__('Shards to synchronize')" :label="__('Shards to synchronize')"
label-for="node-synchronization-shards-field" label-for="node-synchronization-shards-field"
:description="__('Choose which shards you wish to synchronize to this secondary node')"
> >
<geo-node-form-shards <geo-node-form-shards
id="node-synchronization-shards-field" id="node-synchronization-shards-field"
...@@ -86,5 +104,28 @@ export default { ...@@ -86,5 +104,28 @@ export default {
@removeSyncOption="removeSyncOption" @removeSyncOption="removeSyncOption"
/> />
</gl-form-group> </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> </div>
</template> </template>
...@@ -9,3 +9,18 @@ export const VALIDATION_FIELD_KEYS = { ...@@ -9,3 +9,18 @@ export const VALIDATION_FIELD_KEYS = {
VERIFICATION_MAX_CAPACITY: 'verificationMaxCapacity', VERIFICATION_MAX_CAPACITY: 'verificationMaxCapacity',
MINIMUM_REVERIFICATION_INTERVAL: 'minimumReverificationInterval', 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 ...@@ -29,7 +29,7 @@ class Admin::Geo::NodesController < Admin::Geo::ApplicationController
end end
def new def new
@form_title = _('New Geo Node') @form_title = _('Add New Node')
render :form render :form
end end
......
---
title: Geo Node Form - Redesign
merge_request: 34561
author:
type: changed
...@@ -132,7 +132,7 @@ RSpec.describe 'admin Geo Nodes', :js, :geo do ...@@ -132,7 +132,7 @@ RSpec.describe 'admin Geo Nodes', :js, :geo do
fill_in 'node-url-field', with: 'http://newsite.com' fill_in 'node-url-field', with: 'http://newsite.com'
fill_in 'node-internal-url-field', with: 'http://internal-url.com' fill_in 'node-internal-url-field', with: 'http://internal-url.com'
click_button 'Update' click_button 'Save changes'
wait_for_requests wait_for_requests
expect(current_path).to eq admin_geo_nodes_path expect(current_path).to eq admin_geo_nodes_path
......
...@@ -23,7 +23,7 @@ describe('GeoNodeFormApp', () => { ...@@ -23,7 +23,7 @@ describe('GeoNodeFormApp', () => {
wrapper.destroy(); wrapper.destroy();
}); });
const findGeoNodeFormTitle = () => wrapper.find('.page-title'); const findGeoNodeFormTitle = () => wrapper.find('h2');
const findGeoNodeFormBadge = () => wrapper.find(GlBadge); const findGeoNodeFormBadge = () => wrapper.find(GlBadge);
const findGeoForm = () => wrapper.find(GeoNodeForm); const findGeoForm = () => wrapper.find(GeoNodeForm);
...@@ -34,7 +34,7 @@ describe('GeoNodeFormApp', () => { ...@@ -34,7 +34,7 @@ describe('GeoNodeFormApp', () => {
describe.each` describe.each`
formType | node | title | pillTitle | variant 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 secondary node'} | ${{ primary: false }} | ${'Edit Geo Node'} | ${'Secondary'} | ${'light'}
${'update a primary node'} | ${{ primary: true }} | ${'Edit Geo Node'} | ${'Primary'} | ${'primary'} ${'update a primary node'} | ${{ primary: true }} | ${'Edit Geo Node'} | ${'Primary'} | ${'primary'}
`(`form header`, ({ formType, node, title, pillTitle, variant }) => { `(`form header`, ({ formType, node, title, pillTitle, variant }) => {
......
import Vuex from 'vuex'; import Vuex from 'vuex';
import { createLocalVue, mount } from '@vue/test-utils'; 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 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'; import { MOCK_NODE } from '../mock_data';
const localVue = createLocalVue(); const localVue = createLocalVue();
...@@ -44,6 +49,8 @@ describe('GeoNodeFormCapacities', () => { ...@@ -44,6 +49,8 @@ describe('GeoNodeFormCapacities', () => {
wrapper.destroy(); wrapper.destroy();
}); });
const findGeoNodeFormCapcitiesSectionDescription = () => wrapper.find('p');
const findGeoNodeFormCapacitiesMoreInfoLink = () => wrapper.find(GlLink);
const findGeoNodeFormRepositoryCapacityField = () => const findGeoNodeFormRepositoryCapacityField = () =>
wrapper.find('#node-repository-capacity-field'); wrapper.find('#node-repository-capacity-field');
const findGeoNodeFormFileCapacityField = () => wrapper.find('#node-file-capacity-field'); const findGeoNodeFormFileCapacityField = () => wrapper.find('#node-file-capacity-field');
...@@ -56,6 +63,28 @@ describe('GeoNodeFormCapacities', () => { ...@@ -56,6 +63,28 @@ describe('GeoNodeFormCapacities', () => {
const findErrorMessage = () => wrapper.find('.invalid-feedback'); const findErrorMessage = () => wrapper.find('.invalid-feedback');
describe('template', () => { 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` describe.each`
primaryNode | showRepoCapacity | showFileCapacity | showVerificationCapacity | showContainerCapacity | showReverificationInterval primaryNode | showRepoCapacity | showFileCapacity | showVerificationCapacity | showContainerCapacity | showReverificationInterval
${true} | ${false} | ${false} | ${true} | ${false} | ${true} ${true} | ${false} | ${false} | ${true} | ${false} | ${true}
...@@ -70,42 +99,44 @@ describe('GeoNodeFormCapacities', () => { ...@@ -70,42 +99,44 @@ describe('GeoNodeFormCapacities', () => {
showVerificationCapacity, showVerificationCapacity,
showReverificationInterval, showReverificationInterval,
}) => { }) => {
beforeEach(() => { describe(`when node is ${primaryNode ? 'primary' : 'secondary'}`, () => {
createComponent({ beforeEach(() => {
nodeData: { ...defaultProps.nodeData, primary: primaryNode }, createComponent({
nodeData: { ...defaultProps.nodeData, primary: primaryNode },
});
}); });
});
it(`it ${showRepoCapacity ? 'shows' : 'hides'} the Repository Capacity Field`, () => { it(`it ${showRepoCapacity ? 'shows' : 'hides'} the Repository Capacity Field`, () => {
expect(findGeoNodeFormRepositoryCapacityField().exists()).toBe(showRepoCapacity); expect(findGeoNodeFormRepositoryCapacityField().exists()).toBe(showRepoCapacity);
}); });
it(`it ${showFileCapacity ? 'shows' : 'hides'} the File Capacity Field`, () => { it(`it ${showFileCapacity ? 'shows' : 'hides'} the File Capacity Field`, () => {
expect(findGeoNodeFormFileCapacityField().exists()).toBe(showFileCapacity); expect(findGeoNodeFormFileCapacityField().exists()).toBe(showFileCapacity);
}); });
it(`it ${ it(`it ${
showContainerCapacity ? 'shows' : 'hides' showContainerCapacity ? 'shows' : 'hides'
} the Container Repository Capacity Field`, () => { } the Container Repository Capacity Field`, () => {
expect(findGeoNodeFormContainerRepositoryCapacityField().exists()).toBe( expect(findGeoNodeFormContainerRepositoryCapacityField().exists()).toBe(
showContainerCapacity, showContainerCapacity,
); );
}); });
it(`it ${ it(`it ${
showVerificationCapacity ? 'shows' : 'hides' showVerificationCapacity ? 'shows' : 'hides'
} the Verification Capacity Field`, () => { } the Verification Capacity Field`, () => {
expect(findGeoNodeFormVerificationCapacityField().exists()).toBe( expect(findGeoNodeFormVerificationCapacityField().exists()).toBe(
showVerificationCapacity, showVerificationCapacity,
); );
}); });
it(`it ${ it(`it ${
showReverificationInterval ? 'shows' : 'hides' showReverificationInterval ? 'shows' : 'hides'
} the Reverification Interval Field`, () => { } the Reverification Interval Field`, () => {
expect(findGeoNodeFormReverificationIntervalField().exists()).toBe( expect(findGeoNodeFormReverificationIntervalField().exists()).toBe(
showReverificationInterval, showReverificationInterval,
); );
});
}); });
}, },
); );
...@@ -120,7 +151,7 @@ describe('GeoNodeFormCapacities', () => { ...@@ -120,7 +151,7 @@ describe('GeoNodeFormCapacities', () => {
${999} | ${false} | ${null} ${999} | ${false} | ${null}
${1000} | ${true} | ${'should be between 1-999'} ${1000} | ${true} | ${'should be between 1-999'}
`(`errors`, ({ data, showError, errorMessage }) => { `(`errors`, ({ data, showError, errorMessage }) => {
describe('on primary node', () => { describe('when node is primary', () => {
beforeEach(() => { beforeEach(() => {
createComponent({ createComponent({
nodeData: { ...defaultProps.nodeData, primary: true }, nodeData: { ...defaultProps.nodeData, primary: true },
...@@ -158,7 +189,7 @@ describe('GeoNodeFormCapacities', () => { ...@@ -158,7 +189,7 @@ describe('GeoNodeFormCapacities', () => {
}); });
}); });
describe('on secondary node', () => { describe('when node is secondary', () => {
beforeEach(() => { beforeEach(() => {
createComponent(); createComponent();
}); });
...@@ -226,7 +257,7 @@ describe('GeoNodeFormCapacities', () => { ...@@ -226,7 +257,7 @@ describe('GeoNodeFormCapacities', () => {
describe('computed', () => { describe('computed', () => {
describe('visibleFormGroups', () => { describe('visibleFormGroups', () => {
describe('when nodeData.primary is true', () => { describe('when node is primary', () => {
beforeEach(() => { beforeEach(() => {
createComponent({ createComponent({
nodeData: { ...defaultProps.nodeData, primary: true }, nodeData: { ...defaultProps.nodeData, primary: true },
...@@ -242,7 +273,7 @@ describe('GeoNodeFormCapacities', () => { ...@@ -242,7 +273,7 @@ describe('GeoNodeFormCapacities', () => {
}); });
}); });
describe('when nodeData.primary is false', () => { describe('when node is secondary', () => {
beforeEach(() => { beforeEach(() => {
createComponent(); createComponent();
}); });
......
import Vuex from 'vuex'; import Vuex from 'vuex';
import { createLocalVue, mount } from '@vue/test-utils'; 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 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'; import { MOCK_NODE, STRING_OVER_255 } from '../mock_data';
const localVue = createLocalVue(); const localVue = createLocalVue();
...@@ -45,7 +46,9 @@ describe('GeoNodeFormCore', () => { ...@@ -45,7 +46,9 @@ describe('GeoNodeFormCore', () => {
}); });
const findGeoNodeFormNameField = () => wrapper.find('#node-name-field'); const findGeoNodeFormNameField = () => wrapper.find('#node-name-field');
const findGeoNodeFormNameMoreInformation = () => wrapper.find(GlLink);
const findGeoNodeFormUrlField = () => wrapper.find('#node-url-field'); const findGeoNodeFormUrlField = () => wrapper.find('#node-url-field');
const findGeoNodeInternalUrlField = () => wrapper.find('#node-internal-url-field');
const findErrorMessage = () => wrapper.find('.invalid-feedback'); const findErrorMessage = () => wrapper.find('.invalid-feedback');
describe('template', () => { describe('template', () => {
...@@ -61,6 +64,28 @@ describe('GeoNodeFormCore', () => { ...@@ -61,6 +64,28 @@ describe('GeoNodeFormCore', () => {
expect(findGeoNodeFormUrlField().exists()).toBe(true); 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('errors', () => {
describe.each` describe.each`
data | showError | errorMessage data | showError | errorMessage
...@@ -89,7 +114,7 @@ describe('GeoNodeFormCore', () => { ...@@ -89,7 +114,7 @@ describe('GeoNodeFormCore', () => {
${''} | ${true} | ${"URL can't be blank"} ${''} | ${true} | ${"URL can't be blank"}
${'abcd'} | ${true} | ${'URL must be a valid url (ex: https://gitlab.com)'} ${'abcd'} | ${true} | ${'URL must be a valid url (ex: https://gitlab.com)'}
${'https://gitlab.com'} | ${false} | ${null} ${'https://gitlab.com'} | ${false} | ${null}
`(`Name Field`, ({ data, showError, errorMessage }) => { `(`URL Field`, ({ data, showError, errorMessage }) => {
beforeEach(() => { beforeEach(() => {
createComponent(); createComponent();
findGeoNodeFormUrlField().setValue(data); findGeoNodeFormUrlField().setValue(data);
......
import { shallowMount } from '@vue/test-utils'; 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 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 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 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'; import { MOCK_NODE, MOCK_SELECTIVE_SYNC_TYPES, MOCK_SYNC_SHARDS } from '../mock_data';
describe('GeoNodeFormSelectiveSync', () => { describe('GeoNodeFormSelectiveSync', () => {
...@@ -15,6 +17,7 @@ describe('GeoNodeFormSelectiveSync', () => { ...@@ -15,6 +17,7 @@ describe('GeoNodeFormSelectiveSync', () => {
const createComponent = (props = {}) => { const createComponent = (props = {}) => {
wrapper = shallowMount(GeoNodeFormSelectiveSync, { wrapper = shallowMount(GeoNodeFormSelectiveSync, {
stubs: { GlFormGroup, GlSprintf },
propsData: { propsData: {
...defaultProps, ...defaultProps,
...props, ...props,
...@@ -28,15 +31,26 @@ describe('GeoNodeFormSelectiveSync', () => { ...@@ -28,15 +31,26 @@ describe('GeoNodeFormSelectiveSync', () => {
const findGeoNodeFormSyncContainer = () => const findGeoNodeFormSyncContainer = () =>
wrapper.find({ ref: 'geoNodeFormSelectiveSyncContainer' }); wrapper.find({ ref: 'geoNodeFormSelectiveSyncContainer' });
const findGeoNodeFormSelectiveSyncMoreInfoLink = () =>
wrapper.find('[data-testid="selectiveSyncMoreInfo"]');
const findGeoNodeFormSyncTypeField = () => wrapper.find('#node-selective-synchronization-field'); const findGeoNodeFormSyncTypeField = () => wrapper.find('#node-selective-synchronization-field');
const findGeoNodeFormNamespacesField = () => wrapper.find(GeoNodeFormNamespaces); const findGeoNodeFormNamespacesField = () => wrapper.find(GeoNodeFormNamespaces);
const findGeoNodeFormShardsField = () => wrapper.find(GeoNodeFormShards); const findGeoNodeFormShardsField = () => wrapper.find(GeoNodeFormShards);
const findGeoNodeObjectStorageField = () => wrapper.find('#node-object-storage-field');
const findGeoNodeFormObjectStorageMoreInformation = () =>
wrapper.find('[data-testid="objectStorageMoreInfo"]');
describe('template', () => { describe('template', () => {
beforeEach(() => { beforeEach(() => {
createComponent(); createComponent();
}); });
it('renders section More Information link correctly', () => {
expect(findGeoNodeFormSelectiveSyncMoreInfoLink().attributes('href')).toBe(
SELECTIVE_SYNC_MORE_INFO,
);
});
it('renders Geo Node Form Sync Container', () => { it('renders Geo Node Form Sync Container', () => {
expect(findGeoNodeFormSyncContainer().exists()).toBe(true); expect(findGeoNodeFormSyncContainer().exists()).toBe(true);
}); });
...@@ -45,6 +59,16 @@ describe('GeoNodeFormSelectiveSync', () => { ...@@ -45,6 +59,16 @@ describe('GeoNodeFormSelectiveSync', () => {
expect(findGeoNodeFormSyncTypeField().exists()).toBe(true); 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` describe.each`
syncType | showNamespaces | showShards syncType | showNamespaces | showShards
${MOCK_SELECTIVE_SYNC_TYPES.ALL} | ${false} | ${false} ${MOCK_SELECTIVE_SYNC_TYPES.ALL} | ${false} | ${false}
......
...@@ -3,6 +3,7 @@ import { createLocalVue, shallowMount } from '@vue/test-utils'; ...@@ -3,6 +3,7 @@ import { createLocalVue, shallowMount } from '@vue/test-utils';
import { visitUrl } from '~/lib/utils/url_utility'; import { visitUrl } from '~/lib/utils/url_utility';
import GeoNodeForm from 'ee/geo_node_form/components/geo_node_form.vue'; 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 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 GeoNodeFormCapacities from 'ee/geo_node_form/components/geo_node_form_capacities.vue';
import store from 'ee/geo_node_form/store'; import store from 'ee/geo_node_form/store';
import { MOCK_NODE, MOCK_SELECTIVE_SYNC_TYPES, MOCK_SYNC_SHARDS } from '../mock_data'; import { MOCK_NODE, MOCK_SELECTIVE_SYNC_TYPES, MOCK_SYNC_SHARDS } from '../mock_data';
...@@ -35,9 +36,8 @@ describe('GeoNodeForm', () => { ...@@ -35,9 +36,8 @@ describe('GeoNodeForm', () => {
}); });
const findGeoNodeFormCoreField = () => wrapper.find(GeoNodeFormCore); const findGeoNodeFormCoreField = () => wrapper.find(GeoNodeFormCore);
const findGeoNodeInternalUrlField = () => wrapper.find('#node-internal-url-field'); const findGeoNodeFormSelectiveSyncField = () => wrapper.find(GeoNodeFormSelectiveSync);
const findGeoNodeFormCapacitiesField = () => wrapper.find(GeoNodeFormCapacities); const findGeoNodeFormCapacitiesField = () => wrapper.find(GeoNodeFormCapacities);
const findGeoNodeObjectStorageField = () => wrapper.find('#node-object-storage-field');
const findGeoNodeSaveButton = () => wrapper.find('#node-save-button'); const findGeoNodeSaveButton = () => wrapper.find('#node-save-button');
const findGeoNodeCancelButton = () => wrapper.find('#node-cancel-button'); const findGeoNodeCancelButton = () => wrapper.find('#node-cancel-button');
...@@ -47,35 +47,28 @@ describe('GeoNodeForm', () => { ...@@ -47,35 +47,28 @@ describe('GeoNodeForm', () => {
}); });
describe.each` describe.each`
primaryNode | showCore | showInternalUrl | showCapacities | showObjectStorage primaryNode | showCore | showSelectiveSync | showCapacities
${true} | ${true} | ${true} | ${true} | ${false} ${true} | ${true} | ${false} | ${true}
${false} | ${true} | ${false} | ${true} | ${true} ${false} | ${true} | ${true} | ${true}
`( `(`conditional fields`, ({ primaryNode, showCore, showSelectiveSync, showCapacities }) => {
`conditional fields`, beforeEach(() => {
({ primaryNode, showCore, showInternalUrl, showCapacities, showObjectStorage }) => { wrapper.setData({
beforeEach(() => { nodeData: { ...wrapper.vm.nodeData, primary: primaryNode },
wrapper.setData({
nodeData: { ...wrapper.vm.nodeData, primary: primaryNode },
});
});
it(`it ${showCore ? 'shows' : 'hides'} the Core Field`, () => {
expect(findGeoNodeFormCoreField().exists()).toBe(showCore);
}); });
});
it(`it ${showInternalUrl ? 'shows' : 'hides'} the Internal URL Field`, () => { it(`it ${showCore ? 'shows' : 'hides'} the Core Field`, () => {
expect(findGeoNodeInternalUrlField().exists()).toBe(showInternalUrl); expect(findGeoNodeFormCoreField().exists()).toBe(showCore);
}); });
it(`it ${showCapacities ? 'shows' : 'hides'} the Capacities Field`, () => { it(`it ${showSelectiveSync ? 'shows' : 'hides'} the Selective Sync Field`, () => {
expect(findGeoNodeFormCapacitiesField().exists()).toBe(showCapacities); expect(findGeoNodeFormSelectiveSyncField().exists()).toBe(showSelectiveSync);
}); });
it(`it ${showObjectStorage ? 'shows' : 'hides'} the Object Storage Field`, () => { it(`it ${showCapacities ? 'shows' : 'hides'} the Capacities Field`, () => {
expect(findGeoNodeObjectStorageField().exists()).toBe(showObjectStorage); expect(findGeoNodeFormCapacitiesField().exists()).toBe(showCapacities);
}); });
}, });
);
describe('Save Button', () => { describe('Save Button', () => {
describe('with errors on form', () => { describe('with errors on form', () => {
......
...@@ -1313,6 +1313,9 @@ msgstr "" ...@@ -1313,6 +1313,9 @@ msgstr ""
msgid "Add LICENSE" msgid "Add LICENSE"
msgstr "" msgstr ""
msgid "Add New Node"
msgstr ""
msgid "Add README" msgid "Add README"
msgstr "" msgstr ""
...@@ -4420,15 +4423,9 @@ msgstr "" ...@@ -4420,15 +4423,9 @@ msgstr ""
msgid "Choose what content you want to see on a group’s overview page" msgid "Choose what content you want to see on a group’s overview page"
msgstr "" 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." msgid "Choose which repositories you want to connect and run CI/CD pipelines."
msgstr "" msgstr ""
msgid "Choose which shards you wish to synchronize to this secondary node"
msgstr ""
msgid "Choose your framework" msgid "Choose your framework"
msgstr "" msgstr ""
...@@ -6375,21 +6372,6 @@ msgstr "" ...@@ -6375,21 +6372,6 @@ msgstr ""
msgid "Control the display of third party offers." msgid "Control the display of third party offers."
msgstr "" 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" msgid "Cookie domain"
msgstr "" msgstr ""
...@@ -11946,10 +11928,10 @@ msgstr "" ...@@ -11946,10 +11928,10 @@ msgstr ""
msgid "If enabled" msgid "If enabled"
msgstr "" 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 "" 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 "" 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." 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."
...@@ -14595,6 +14577,9 @@ msgstr "" ...@@ -14595,6 +14577,9 @@ msgstr ""
msgid "Minimum capacity to be available before we schedule more mirrors preemptively." msgid "Minimum capacity to be available before we schedule more mirrors preemptively."
msgstr "" msgstr ""
msgid "Minimum interval in days"
msgstr ""
msgid "Minimum length is %{minimum_password_length} characters" msgid "Minimum length is %{minimum_password_length} characters"
msgstr "" msgstr ""
...@@ -14781,6 +14766,12 @@ msgstr "" ...@@ -14781,6 +14766,12 @@ msgstr ""
msgid "Multiple uploaders found: %{uploader_types}" msgid "Multiple uploaders found: %{uploader_types}"
msgstr "" 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" msgid "My Awesome Group"
msgstr "" msgstr ""
...@@ -14907,9 +14898,6 @@ msgstr "" ...@@ -14907,9 +14898,6 @@ msgstr ""
msgid "New File" msgid "New File"
msgstr "" msgstr ""
msgid "New Geo Node"
msgstr ""
msgid "New Group" msgid "New Group"
msgstr "" msgstr ""
...@@ -16258,6 +16246,9 @@ msgstr "" ...@@ -16258,6 +16246,9 @@ msgstr ""
msgid "Perform common operations on GitLab project" msgid "Perform common operations on GitLab project"
msgstr "" msgstr ""
msgid "Performance and resource management"
msgstr ""
msgid "Performance optimization" msgid "Performance optimization"
msgstr "" msgstr ""
...@@ -20640,6 +20631,12 @@ msgstr "" ...@@ -20640,6 +20631,12 @@ msgstr ""
msgid "Set the milestone to %{milestone_reference}." msgid "Set the milestone to %{milestone_reference}."
msgstr "" 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." msgid "Set the timeout in seconds to send a secondary node status to the primary and IPs allowed for the secondary nodes."
msgstr "" msgstr ""
...@@ -20682,6 +20679,9 @@ msgstr "" ...@@ -20682,6 +20679,9 @@ msgstr ""
msgid "Set weight to %{weight}." msgid "Set weight to %{weight}."
msgstr "" msgstr ""
msgid "Set what should be replicated by choosing specific projects or groups by the secondary node."
msgstr ""
msgid "SetPasswordToCloneLink|set a password" msgid "SetPasswordToCloneLink|set a password"
msgstr "" msgstr ""
...@@ -22469,7 +22469,7 @@ msgstr "" ...@@ -22469,7 +22469,7 @@ msgstr ""
msgid "The Terraform report %{name} was generated in your pipelines." msgid "The Terraform report %{name} was generated in your pipelines."
msgstr "" 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 "" 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\")." 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\")."
...@@ -22780,9 +22780,6 @@ msgstr "" ...@@ -22780,9 +22780,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." 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 "" 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." msgid "The update action will time out after %{number_of_minutes} minutes. For big repositories, use a clone/push combination."
msgstr "" msgstr ""
...@@ -22807,9 +22804,6 @@ msgstr "" ...@@ -22807,9 +22804,6 @@ msgstr ""
msgid "The user you are trying to deactivate has been active in the past %{minimum_inactive_days} days and cannot be deactivated" msgid "The user you are trying to deactivate has been active in the past %{minimum_inactive_days} days and cannot be deactivated"
msgstr "" 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." 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 "" msgstr ""
...@@ -27094,9 +27088,6 @@ msgstr "" ...@@ -27094,9 +27088,6 @@ msgstr ""
msgid "expires on %{timebox_due_date}" msgid "expires on %{timebox_due_date}"
msgstr "" msgstr ""
msgid "external_url"
msgstr ""
msgid "failed" msgid "failed"
msgstr "" msgstr ""
...@@ -27132,9 +27123,6 @@ msgstr "" ...@@ -27132,9 +27123,6 @@ msgstr ""
msgid "from" msgid "from"
msgstr "" msgstr ""
msgid "geo_node_name"
msgstr ""
msgid "group" msgid "group"
msgstr "" 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