Commit 0c90b5a9 authored by Douglas Barbosa Alexandre's avatar Douglas Barbosa Alexandre

Merge branch '7126-geo-open-projects' into 'master'

Geo: Added a button to Admin UI > Geo Nodes to open Geo Projects screen of any secondary node

Closes #7126

See merge request gitlab-org/gitlab-ee!7512
parents 1ac860fb 7fa619ea
...@@ -2,13 +2,21 @@ ...@@ -2,13 +2,21 @@
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';
import Icon from '~/vue_shared/components/icon.vue';
export default { export default {
components: {
Icon,
},
props: { props: {
node: { node: {
type: Object, type: Object,
required: true, required: true,
}, },
nodeActionsAllowed: {
type: Boolean,
required: true,
},
nodeEditAllowed: { nodeEditAllowed: {
type: Boolean, type: Boolean,
required: true, required: true,
...@@ -25,6 +33,9 @@ ...@@ -25,6 +33,9 @@
nodeToggleLabel() { nodeToggleLabel() {
return this.node.enabled ? __('Disable') : __('Enable'); return this.node.enabled ? __('Disable') : __('Enable');
}, },
isSecondaryNode() {
return !this.node.primary;
},
}, },
methods: { methods: {
onToggleNode() { onToggleNode() {
...@@ -54,52 +65,69 @@ ...@@ -54,52 +65,69 @@
<template> <template>
<div class="geo-node-actions"> <div class="geo-node-actions">
<div <div
v-if="nodeMissingOauth" v-if="isSecondaryNode"
class="node-action-container"
>
<button
type="button"
class="btn btn-default btn-sm btn-node-action"
@click="onRepairNode"
>
{{ s__('Repair authentication') }}
</button>
</div>
<div
v-if="isToggleAllowed"
class="node-action-container"
>
<button
:class="{
'btn-warning': node.enabled,
'btn-success': !node.enabled
}"
type="button"
class="btn btn-sm btn-node-action"
@click="onToggleNode"
>
{{ nodeToggleLabel }}
</button>
</div>
<div
v-if="nodeEditAllowed"
class="node-action-container" class="node-action-container"
> >
<a <a
:href="node.editPath" :href="node.geoProjectsUrl"
class="btn btn-sm btn-node-action" class="btn btn-sm btn-node-action"
target="_blank"
> >
{{ __('Edit') }} <icon
name="external-link"
/>
{{ __('Open projects') }}
</a> </a>
</div> </div>
<div class="node-action-container"> <template v-if="nodeActionsAllowed">
<button <div
type="button" v-if="nodeMissingOauth"
class="btn btn-sm btn-node-action btn-danger" class="node-action-container"
@click="onRemoveNode"
> >
{{ __('Remove') }} <button
</button> type="button"
</div> class="btn btn-default btn-sm btn-node-action"
@click="onRepairNode"
>
{{ s__('Repair authentication') }}
</button>
</div>
<div
v-if="isToggleAllowed"
class="node-action-container"
>
<button
:class="{
'btn-warning': node.enabled,
'btn-success': !node.enabled
}"
type="button"
class="btn btn-sm btn-node-action"
@click="onToggleNode"
>
{{ nodeToggleLabel }}
</button>
</div>
<div
v-if="nodeEditAllowed"
class="node-action-container"
>
<a
:href="node.editPath"
class="btn btn-sm btn-node-action"
>
{{ __('Edit') }}
</a>
</div>
<div class="node-action-container">
<button
type="button"
class="btn btn-sm btn-node-action btn-danger"
@click="onRemoveNode"
>
{{ __('Remove') }}
</button>
</div>
</template>
</div> </div>
</template> </template>
...@@ -65,8 +65,8 @@ ...@@ -65,8 +65,8 @@
/> />
</div> </div>
<geo-node-actions <geo-node-actions
v-if="nodeActionsAllowed"
:node="node" :node="node"
:node-actions-allowed="nodeActionsAllowed"
:node-edit-allowed="nodeEditAllowed" :node-edit-allowed="nodeEditAllowed"
:node-missing-oauth="nodeDetails.missingOAuthApplication" :node-missing-oauth="nodeDetails.missingOAuthApplication"
/> />
......
...@@ -54,6 +54,7 @@ export default class GeoNodesStore { ...@@ -54,6 +54,7 @@ export default class GeoNodesStore {
basePath: rawNode._links.self, basePath: rawNode._links.self,
repairPath: rawNode._links.repair, repairPath: rawNode._links.repair,
editPath: rawNode.web_edit_url, editPath: rawNode.web_edit_url,
geoProjectsUrl: rawNode.web_geo_projects_url,
statusPath: rawNode._links.status, statusPath: rawNode._links.status,
}; };
} }
......
...@@ -153,6 +153,12 @@ class GeoNode < ActiveRecord::Base ...@@ -153,6 +153,12 @@ class GeoNode < ActiveRecord::Base
Gitlab::Routing.url_helpers.oauth_geo_logout_url(url_helper_args.merge(state: state)) Gitlab::Routing.url_helpers.oauth_geo_logout_url(url_helper_args.merge(state: state))
end end
def geo_projects_url
return unless self.secondary?
Gitlab::Routing.url_helpers.admin_geo_projects_url(url_helper_args)
end
def missing_oauth_application? def missing_oauth_application?
self.primary? ? false : !oauth_application.present? self.primary? ? false : !oauth_application.present?
end end
......
---
title: 'Geo: Added a button to Admin UI > Geo Nodes to open Geo Projects screen of any secondary node'
merge_request: 7512
author:
type: added
...@@ -263,6 +263,10 @@ module EE ...@@ -263,6 +263,10 @@ module EE
::Gitlab::Routing.url_helpers.edit_admin_geo_node_url(geo_node) ::Gitlab::Routing.url_helpers.edit_admin_geo_node_url(geo_node)
end end
expose :web_geo_projects_url, if: ->(geo_node, _) { geo_node.secondary? } do |geo_node|
geo_node.geo_projects_url
end
expose :_links do expose :_links do
expose :self do |geo_node| expose :self do |geo_node|
expose_url api_v4_geo_nodes_path(id: geo_node.id) expose_url api_v4_geo_nodes_path(id: geo_node.id)
......
require 'spec_helper' require 'spec_helper'
describe 'GEO Nodes' do describe 'GEO Nodes' do
let(:user) { create(:user) } include ::EE::GeoHelpers
let(:project) { create(:project) }
let(:geo_url) { 'http://geo.example.com' } set(:user) { create(:user) }
set(:geo_primary) { create(:geo_node, :primary) }
set(:geo_secondary) { create(:geo_node) }
context 'Geo Secondary Node' do context 'Geo Secondary Node' do
let(:project) { create(:project) }
before do before do
allow(Gitlab::Geo).to receive(:secondary?) { true } stub_current_geo_node(geo_secondary)
allow(Gitlab::Geo).to receive_message_chain(:primary_node, :url) { geo_url }
project.add_maintainer(user) project.add_maintainer(user)
sign_in(user) sign_in(user)
...@@ -26,4 +29,31 @@ describe 'GEO Nodes' do ...@@ -26,4 +29,31 @@ describe 'GEO Nodes' do
end end
end end
end end
context 'Primary Geo Node' do
let(:admin_user) { create(:user, :admin) }
before do
stub_current_geo_node(geo_primary)
stub_licensed_features(geo: true)
sign_in(admin_user)
end
describe 'Geo Nodes admin screen' do
it "has a 'Open projects' button on listed secondary geo nodes pointing to correct URL", :js do
visit admin_geo_nodes_path
expect(page).to have_content(geo_primary.url)
expect(page).to have_content(geo_secondary.url)
wait_for_requests
geo_node_actions = all('div.geo-node-actions')
expected_url = File.join(geo_secondary.url, '/admin/geo/projects')
expect(geo_node_actions.last).to have_link('Open projects', href: expected_url)
end
end
end
end end
...@@ -21,8 +21,9 @@ ...@@ -21,8 +21,9 @@
"files_max_capacity": { "type": "integer" }, "files_max_capacity": { "type": "integer" },
"repos_max_capacity": { "type": "integer" }, "repos_max_capacity": { "type": "integer" },
"verification_max_capacity": { "type": "integer" }, "verification_max_capacity": { "type": "integer" },
"clone_protocol": { "type": ["string"] }, "clone_protocol": { "type": "string" },
"web_edit_url": { "type": "string" }, "web_edit_url": { "type": "string" },
"web_geo_projects_url" : { "type": ["string", "null"] },
"_links": { "_links": {
"type": "object", "type": "object",
"required": ["self", "repair"], "required": ["self", "repair"],
......
...@@ -6,12 +6,14 @@ import eventHub from 'ee/geo_nodes/event_hub'; ...@@ -6,12 +6,14 @@ import eventHub from 'ee/geo_nodes/event_hub';
import { NODE_ACTIONS } from 'ee/geo_nodes/constants'; import { NODE_ACTIONS } from 'ee/geo_nodes/constants';
import { mockNodes } from '../mock_data'; import { mockNodes } from '../mock_data';
const createComponent = (node = mockNodes[0], nodeEditAllowed = true, nodeMissingOauth = false) => { const createComponent = (node = mockNodes[0], nodeEditAllowed = true,
nodeActionsAllowed = true, nodeMissingOauth = false) => {
const Component = Vue.extend(geoNodeActionsComponent); const Component = Vue.extend(geoNodeActionsComponent);
return mountComponent(Component, { return mountComponent(Component, {
node, node,
nodeEditAllowed, nodeEditAllowed,
nodeActionsAllowed,
nodeMissingOauth, nodeMissingOauth,
}); });
}; };
......
...@@ -47,6 +47,7 @@ export const mockNode = { ...@@ -47,6 +47,7 @@ export const mockNode = {
current: true, current: true,
enabled: true, enabled: true,
nodeActionActive: false, nodeActionActive: false,
nodeActionsAllowed: false,
basePath: 'http://127.0.0.1:3001/api/v4/geo_nodes/1', basePath: 'http://127.0.0.1:3001/api/v4/geo_nodes/1',
repairPath: 'http://127.0.0.1:3001/api/v4/geo_nodes/1/repair', repairPath: 'http://127.0.0.1:3001/api/v4/geo_nodes/1/repair',
statusPath: 'http://127.0.0.1:3001/api/v4/geo_nodes/1/status', statusPath: 'http://127.0.0.1:3001/api/v4/geo_nodes/1/status',
......
...@@ -328,6 +328,18 @@ describe GeoNode, type: :model do ...@@ -328,6 +328,18 @@ describe GeoNode, type: :model do
end end
end end
describe '#geo_projects_url' do
it 'returns the Geo Projects url for the specific node' do
expected_url = 'https://localhost:3000/gitlab/admin/geo/projects'
expect(new_node.geo_projects_url).to eq(expected_url)
end
it 'returns nil when node is a primary one' do
expect(primary_node.geo_projects_url).to be_nil
end
end
describe '#missing_oauth_application?' do describe '#missing_oauth_application?' do
context 'on a primary node' do context 'on a primary node' do
it 'returns false' do it 'returns false' do
......
...@@ -5446,6 +5446,9 @@ msgstr "" ...@@ -5446,6 +5446,9 @@ msgstr ""
msgid "Open in Xcode" msgid "Open in Xcode"
msgstr "" msgstr ""
msgid "Open projects"
msgstr ""
msgid "Open sidebar" msgid "Open sidebar"
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