Commit c27e6fc5 authored by Natalia Tepluhina's avatar Natalia Tepluhina

Merge branch '9682-disable-remove-primary' into 'master'

Geo Nodes - Conditional Removal Button

Closes #9682

See merge request gitlab-org/gitlab!27836
parents 6e0a5d85 9cce8035
...@@ -200,6 +200,9 @@ export default { ...@@ -200,6 +200,9 @@ export default {
hideNodeActionModal() { hideNodeActionModal() {
this.$root.$emit('bv::hide::modal', this.modalId); this.$root.$emit('bv::hide::modal', this.modalId);
}, },
nodeRemovalAllowed(node) {
return !node.primary || this.nodes.length <= 1;
},
}, },
}; };
</script> </script>
...@@ -219,6 +222,7 @@ export default { ...@@ -219,6 +222,7 @@ export default {
:primary-node="node.primary" :primary-node="node.primary"
:node-actions-allowed="nodeActionsAllowed" :node-actions-allowed="nodeActionsAllowed"
:node-edit-allowed="nodeEditAllowed" :node-edit-allowed="nodeEditAllowed"
:node-removal-allowed="nodeRemovalAllowed(node)"
:geo-troubleshooting-help-path="geoTroubleshootingHelpPath" :geo-troubleshooting-help-path="geoTroubleshootingHelpPath"
/> />
<gl-modal <gl-modal
......
<script> <script>
import { GlButton, GlTooltipDirective } from '@gitlab/ui';
import { __, s__ } from '~/locale'; import { __, s__ } from '~/locale';
import eventHub from '../event_hub'; import eventHub from '../event_hub';
import { NODE_ACTIONS } from '../constants'; import { NODE_ACTIONS } from '../constants';
...@@ -7,6 +8,10 @@ import Icon from '~/vue_shared/components/icon.vue'; ...@@ -7,6 +8,10 @@ import Icon from '~/vue_shared/components/icon.vue';
export default { export default {
components: { components: {
Icon, Icon,
GlButton,
},
directives: {
GlTooltip: GlTooltipDirective,
}, },
props: { props: {
node: { node: {
...@@ -21,6 +26,10 @@ export default { ...@@ -21,6 +26,10 @@ export default {
type: Boolean, type: Boolean,
required: true, required: true,
}, },
nodeRemovalAllowed: {
type: Boolean,
required: true,
},
nodeMissingOauth: { nodeMissingOauth: {
type: Boolean, type: Boolean,
required: true, required: true,
...@@ -39,6 +48,11 @@ export default { ...@@ -39,6 +48,11 @@ export default {
isSecondaryNode() { isSecondaryNode() {
return !this.node.primary; return !this.node.primary;
}, },
disabledRemovalTooltip() {
return this.nodeRemovalAllowed
? ''
: s__('Geo Nodes|Cannot remove a primary node if there is a secondary node');
},
}, },
methods: { methods: {
onToggleNode() { onToggleNode() {
...@@ -83,48 +97,60 @@ export default { ...@@ -83,48 +97,60 @@ export default {
<template> <template>
<div class="d-flex align-items-center justify-content-end geo-node-actions"> <div class="d-flex align-items-center justify-content-end geo-node-actions">
<a v-if="isSecondaryNode" :href="node.geoProjectsUrl" class="btn btn-sm mx-1 " target="_blank"> <a
v-if="isSecondaryNode"
:href="node.geoProjectsUrl"
class="btn btn-sm mx-1 sm-column-spacing"
target="_blank"
>
<icon v-if="!node.current" name="external-link" /> {{ __('Open projects') }} <icon v-if="!node.current" name="external-link" /> {{ __('Open projects') }}
</a> </a>
<template v-if="nodeActionsAllowed"> <template v-if="nodeActionsAllowed">
<button <gl-button
v-if="nodeMissingOauth" v-if="nodeMissingOauth"
type="button" class="btn btn-sm btn-default mx-1 sm-column-spacing"
class="btn btn-sm btn-default mx-1"
@click="onRepairNode" @click="onRepairNode"
> >
{{ s__('Repair authentication') }} {{ s__('Repair authentication') }}
</button> </gl-button>
<button <gl-button
v-if="isToggleAllowed" v-if="isToggleAllowed"
:class="{ :class="{
'btn-warning': node.enabled, 'btn-warning': node.enabled,
'btn-success': !node.enabled, 'btn-success': !node.enabled,
}" }"
type="button" class="btn btn-sm mx-1 sm-column-spacing"
class="btn btn-sm mx-1"
@click="onToggleNode" @click="onToggleNode"
> >
<icon :name="nodeToggleIcon" /> <icon :name="nodeToggleIcon" />
{{ nodeToggleLabel }} {{ nodeToggleLabel }}
</button> </gl-button>
<a v-if="nodeEditAllowed" :href="node.editPath" class="btn btn-sm mx-1"> {{ __('Edit') }} </a> <a v-if="nodeEditAllowed" :href="node.editPath" class="btn btn-sm mx-1 sm-column-spacing">
<button {{ __('Edit') }}
</a>
<gl-button
v-if="isSecondaryNode" v-if="isSecondaryNode"
type="button" class="btn btn-sm btn-danger mx-1 sm-column-spacing"
class="btn btn-sm btn-danger mx-1" :disabled="!nodeRemovalAllowed"
@click="onRemoveSecondaryNode" @click="onRemoveSecondaryNode"
> >
{{ __('Remove') }} {{ __('Remove') }}
</button> </gl-button>
<button <div
v-gl-tooltip.hover
name="disabledRemovalTooltip"
class="mx-1 sm-column-spacing"
:title="disabledRemovalTooltip"
>
<gl-button
v-if="!isSecondaryNode" v-if="!isSecondaryNode"
type="button" class="btn btn-sm btn-danger w-100"
class="btn btn-sm btn-danger mx-1" :disabled="!nodeRemovalAllowed"
@click="onRemovePrimaryNode" @click="onRemovePrimaryNode"
> >
{{ __('Remove') }} {{ __('Remove') }}
</button> </gl-button>
</div>
</template> </template>
</div> </div>
</template> </template>
...@@ -33,6 +33,10 @@ export default { ...@@ -33,6 +33,10 @@ export default {
type: Boolean, type: Boolean,
required: true, required: true,
}, },
nodeRemovalAllowed: {
type: Boolean,
required: true,
},
geoTroubleshootingHelpPath: { geoTroubleshootingHelpPath: {
type: String, type: String,
required: true, required: true,
...@@ -72,6 +76,7 @@ export default { ...@@ -72,6 +76,7 @@ export default {
:node-details="nodeDetails" :node-details="nodeDetails"
:node-actions-allowed="nodeActionsAllowed" :node-actions-allowed="nodeActionsAllowed"
:node-edit-allowed="nodeEditAllowed" :node-edit-allowed="nodeEditAllowed"
:node-removal-allowed="nodeRemovalAllowed"
:version-mismatch="hasVersionMismatch" :version-mismatch="hasVersionMismatch"
/> />
<node-details-section-sync v-if="!node.primary" :node-details="nodeDetails" /> <node-details-section-sync v-if="!node.primary" :node-details="nodeDetails" />
......
...@@ -29,6 +29,10 @@ export default { ...@@ -29,6 +29,10 @@ export default {
type: Boolean, type: Boolean,
required: true, required: true,
}, },
nodeRemovalAllowed: {
type: Boolean,
required: true,
},
geoTroubleshootingHelpPath: { geoTroubleshootingHelpPath: {
type: String, type: String,
required: true, required: true,
...@@ -91,6 +95,7 @@ export default { ...@@ -91,6 +95,7 @@ export default {
:node-details="nodeDetails" :node-details="nodeDetails"
:node-edit-allowed="nodeEditAllowed" :node-edit-allowed="nodeEditAllowed"
:node-actions-allowed="nodeActionsAllowed" :node-actions-allowed="nodeActionsAllowed"
:node-removal-allowed="nodeRemovalAllowed"
:geo-troubleshooting-help-path="geoTroubleshootingHelpPath" :geo-troubleshooting-help-path="geoTroubleshootingHelpPath"
/> />
<div v-if="isNodeDetailsFailed"> <div v-if="isNodeDetailsFailed">
......
...@@ -18,6 +18,10 @@ export default { ...@@ -18,6 +18,10 @@ export default {
type: Boolean, type: Boolean,
required: true, required: true,
}, },
nodeRemovalAllowed: {
type: Boolean,
required: true,
},
geoTroubleshootingHelpPath: { geoTroubleshootingHelpPath: {
type: String, type: String,
required: true, required: true,
...@@ -35,6 +39,7 @@ export default { ...@@ -35,6 +39,7 @@ export default {
:primary-node="node.primary" :primary-node="node.primary"
:node-actions-allowed="nodeActionsAllowed" :node-actions-allowed="nodeActionsAllowed"
:node-edit-allowed="nodeEditAllowed" :node-edit-allowed="nodeEditAllowed"
:node-removal-allowed="nodeRemovalAllowed"
:geo-troubleshooting-help-path="geoTroubleshootingHelpPath" :geo-troubleshooting-help-path="geoTroubleshootingHelpPath"
/> />
</div> </div>
......
...@@ -26,6 +26,10 @@ export default { ...@@ -26,6 +26,10 @@ export default {
type: Boolean, type: Boolean,
required: true, required: true,
}, },
nodeRemovalAllowed: {
type: Boolean,
required: true,
},
versionMismatch: { versionMismatch: {
type: Boolean, type: Boolean,
required: true, required: true,
...@@ -47,11 +51,21 @@ export default { ...@@ -47,11 +51,21 @@ export default {
<template> <template>
<div class="row-fluid clearfix py-3 primary-section"> <div class="row-fluid clearfix py-3 primary-section">
<div class="col-md-8"> <div class="col-md-12">
<div class="d-flex geo-node-actions-container">
<div class="d-flex flex-column"> <div class="d-flex flex-column">
<span class="text-secondary-700 js-node-url-title">{{ s__('GeoNodes|Node URL') }}</span> <span class="text-secondary-700 js-node-url-title">{{ s__('GeoNodes|Node URL') }}</span>
<span class="mt-1 font-weight-bold js-node-url-value">{{ node.url }}</span> <span class="mt-1 font-weight-bold js-node-url-value">{{ node.url }}</span>
</div> </div>
<geo-node-actions
class="flex-grow-1"
:node="node"
:node-actions-allowed="nodeActionsAllowed"
:node-edit-allowed="nodeEditAllowed"
:node-removal-allowed="nodeRemovalAllowed"
:node-missing-oauth="nodeDetails.missingOAuthApplication"
/>
</div>
<div class="d-flex flex-column mt-2"> <div class="d-flex flex-column mt-2">
<span class="text-secondary-700 js-node-version-title">{{ <span class="text-secondary-700 js-node-version-title">{{
s__('GeoNodes|GitLab version') s__('GeoNodes|GitLab version')
...@@ -65,11 +79,5 @@ export default { ...@@ -65,11 +79,5 @@ export default {
</div> </div>
<geo-node-health-status :status="nodeHealthStatus" /> <geo-node-health-status :status="nodeHealthStatus" />
</div> </div>
<geo-node-actions
:node="node"
:node-actions-allowed="nodeActionsAllowed"
:node-edit-allowed="nodeEditAllowed"
:node-missing-oauth="nodeDetails.missingOAuthApplication"
/>
</div> </div>
</template> </template>
...@@ -3,11 +3,15 @@ ...@@ -3,11 +3,15 @@
flex-direction: column; flex-direction: column;
margin: 0 1rem; margin: 0 1rem;
.btn-sm { .sm-column-spacing {
width: 100%; width: 100%;
margin-top: 1rem; margin-top: 1rem;
} }
} }
.geo-node-actions-container {
flex-direction: column;
}
} }
.project-card-errors { .project-card-errors {
......
---
title: Disabled Primary Node Removal button when removal is not allowed
merge_request: 27836
author:
type: changed
...@@ -445,6 +445,31 @@ describe('AppComponent', () => { ...@@ -445,6 +445,31 @@ describe('AppComponent', () => {
expect(rootEmit).toHaveBeenCalledWith('bv::hide::modal', vm.modalId); expect(rootEmit).toHaveBeenCalledWith('bv::hide::modal', vm.modalId);
}); });
}); });
describe('nodeRemovalAllowed', () => {
describe.each`
primaryNode | nodesLength | nodeRemovalAllowed
${false} | ${2} | ${true}
${false} | ${1} | ${true}
${true} | ${2} | ${false}
${true} | ${1} | ${true}
`(
'with (primaryNode = $primaryNode, nodesLength = $nodesLength)',
({ primaryNode, nodesLength, nodeRemovalAllowed }) => {
const testPhrasing = nodeRemovalAllowed ? 'allow' : 'disallow';
let node;
beforeEach(() => {
node = { ...mockNode, primary: primaryNode };
vm.store.state.nodes = [mockNode, node].slice(0, nodesLength);
});
it(`should ${testPhrasing} node removal`, () => {
expect(vm.nodeRemovalAllowed(node)).toBe(nodeRemovalAllowed);
});
},
);
});
}); });
describe('created', () => { describe('created', () => {
......
...@@ -12,6 +12,7 @@ const createComponent = ( ...@@ -12,6 +12,7 @@ const createComponent = (
node = mockNodes[0], node = mockNodes[0],
nodeEditAllowed = true, nodeEditAllowed = true,
nodeActionsAllowed = true, nodeActionsAllowed = true,
nodeRemovalAllowed = true,
nodeMissingOauth = false, nodeMissingOauth = false,
) => { ) => {
const Component = Vue.extend(geoNodeActionsComponent); const Component = Vue.extend(geoNodeActionsComponent);
...@@ -20,6 +21,7 @@ const createComponent = ( ...@@ -20,6 +21,7 @@ const createComponent = (
node, node,
nodeEditAllowed, nodeEditAllowed,
nodeActionsAllowed, nodeActionsAllowed,
nodeRemovalAllowed,
nodeMissingOauth, nodeMissingOauth,
}); });
}; };
...@@ -65,6 +67,23 @@ describe('GeoNodeActionsComponent', () => { ...@@ -65,6 +67,23 @@ describe('GeoNodeActionsComponent', () => {
vmX.$destroy(); vmX.$destroy();
}); });
}); });
describe('disabledRemovalTooltip', () => {
describe.each`
nodeRemovalAllowed | tooltip
${true} | ${''}
${false} | ${'Cannot remove a primary node if there is a secondary node'}
`('when nodeRemovalAllowed is $nodeRemovalAllowed', ({ nodeRemovalAllowed, tooltip }) => {
beforeEach(() => {
vm = createComponent(mockNodes[0], true, true, nodeRemovalAllowed, false);
});
it('renders the correct tooltip', () => {
const tip = vm.$el.querySelector('div[name=disabledRemovalTooltip]');
expect(tip.title).toBe(tooltip);
});
});
});
}); });
describe('methods', () => { describe('methods', () => {
...@@ -128,5 +147,29 @@ describe('GeoNodeActionsComponent', () => { ...@@ -128,5 +147,29 @@ describe('GeoNodeActionsComponent', () => {
expect(vm.$el.classList.contains('geo-node-actions')).toBe(true); expect(vm.$el.classList.contains('geo-node-actions')).toBe(true);
expect(vm.$el.querySelectorAll('.btn-sm').length).not.toBe(0); expect(vm.$el.querySelectorAll('.btn-sm').length).not.toBe(0);
}); });
describe.each`
nodeRemovalAllowed | buttonDisabled
${false} | ${true}
${true} | ${false}
`(
`when nodeRemovalAllowed is $nodeRemovalAllowed`,
({ nodeRemovalAllowed, buttonDisabled }) => {
let removeButton;
beforeEach(() => {
vm = createComponent(mockNodes[0], true, true, nodeRemovalAllowed, false);
removeButton = vm.$el.querySelector('.btn-danger');
});
it('has the correct button text', () => {
expect(removeButton.innerText.trim()).toBe('Remove');
});
it(`the button's disabled attribute should be ${buttonDisabled}`, () => {
expect(removeButton.disabled).toBe(buttonDisabled);
});
},
);
}); });
}); });
...@@ -12,6 +12,7 @@ describe('GeoNodeDetailsComponent', () => { ...@@ -12,6 +12,7 @@ describe('GeoNodeDetailsComponent', () => {
nodeDetails: mockNodeDetails, nodeDetails: mockNodeDetails,
nodeActionsAllowed: true, nodeActionsAllowed: true,
nodeEditAllowed: true, nodeEditAllowed: true,
nodeRemovalAllowed: true,
geoTroubleshootingHelpPath: '/foo/bar', geoTroubleshootingHelpPath: '/foo/bar',
}; };
......
...@@ -15,6 +15,7 @@ describe('GeoNodeItemComponent', () => { ...@@ -15,6 +15,7 @@ describe('GeoNodeItemComponent', () => {
primaryNode: true, primaryNode: true,
nodeActionsAllowed: true, nodeActionsAllowed: true,
nodeEditAllowed: true, nodeEditAllowed: true,
nodeRemovalAllowed: true,
geoTroubleshootingHelpPath: '/foo/bar', geoTroubleshootingHelpPath: '/foo/bar',
}; };
......
...@@ -11,6 +11,7 @@ const createComponent = () => { ...@@ -11,6 +11,7 @@ const createComponent = () => {
nodes: mockNodes, nodes: mockNodes,
nodeActionsAllowed: true, nodeActionsAllowed: true,
nodeEditAllowed: true, nodeEditAllowed: true,
nodeRemovalAllowed: true,
geoTroubleshootingHelpPath: '/foo/bar', geoTroubleshootingHelpPath: '/foo/bar',
}); });
}; };
......
...@@ -11,6 +11,7 @@ const createComponent = ({ ...@@ -11,6 +11,7 @@ const createComponent = ({
nodeDetails = Object.assign({}, mockNodeDetails), nodeDetails = Object.assign({}, mockNodeDetails),
nodeActionsAllowed = true, nodeActionsAllowed = true,
nodeEditAllowed = true, nodeEditAllowed = true,
nodeRemovalAllowed = true,
versionMismatch = false, versionMismatch = false,
}) => { }) => {
const Component = Vue.extend(NodeDetailsSectionMainComponent); const Component = Vue.extend(NodeDetailsSectionMainComponent);
...@@ -20,6 +21,7 @@ const createComponent = ({ ...@@ -20,6 +21,7 @@ const createComponent = ({
nodeDetails, nodeDetails,
nodeActionsAllowed, nodeActionsAllowed,
nodeEditAllowed, nodeEditAllowed,
nodeRemovalAllowed,
versionMismatch, versionMismatch,
}); });
}; };
......
...@@ -9144,6 +9144,9 @@ msgstr "" ...@@ -9144,6 +9144,9 @@ msgstr ""
msgid "Geo Nodes" msgid "Geo Nodes"
msgstr "" msgstr ""
msgid "Geo Nodes|Cannot remove a primary node if there is a secondary node"
msgstr ""
msgid "Geo Settings" msgid "Geo Settings"
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