Commit 371d30ea authored by Zack Cuddy's avatar Zack Cuddy

Merge Geo Replication Views

Currently, everytime we add a new
replicable type, it requires a new item
in the side-nav.

This creates a new design pattern to allow us to better use space
to differentiate replicables.

This view contains all replicable types in one
consistent place.

This opens the door for further UI expansion as we open
up Geo Self-Service to countless new replicable types.
parent e51407e4
...@@ -40,10 +40,11 @@ export default { ...@@ -40,10 +40,11 @@ export default {
</script> </script>
<template> <template>
<nav <nav class="bg-secondary border-bottom border-secondary-100 p-3">
class="row d-flex flex-column flex-sm-row align-items-center bg-secondary border-bottom border-secondary-100 p-3" <div class="row d-flex flex-column flex-sm-row">
> <div class="col">
<gl-dropdown :text="__('Filter by status')" class="col px-1 my-1 my-sm-0 w-100"> <div class="d-sm-flex mx-n1">
<gl-dropdown :text="__('Filter by status')" class="px-1 my-1 my-sm-0 w-100">
<gl-dropdown-item <gl-dropdown-item
v-for="(filter, index) in filterOptions" v-for="(filter, index) in filterOptions"
:key="index" :key="index"
...@@ -51,22 +52,24 @@ export default { ...@@ -51,22 +52,24 @@ export default {
@click="filterChange(index)" @click="filterChange(index)"
> >
<span <span
>{{ filter.label }} <span v-if="filter.label === 'All'">{{ replicableType }}</span></span >{{ filter.label }}
<span v-if="filter.label === 'All'">{{ replicableType }}</span></span
> >
</gl-dropdown-item> </gl-dropdown-item>
</gl-dropdown> </gl-dropdown>
<gl-search-box-by-type <gl-search-box-by-type
v-model="search" v-model="search"
class="col px-1 my-1 my-sm-0 bg-white w-100" class="px-1 my-1 my-sm-0 bg-white w-100"
type="text" type="text"
:placeholder="__(`Filter by name`)" :placeholder="__('Filter by name')"
/> />
<div class="col col-sm-6 d-flex justify-content-end my-1 my-sm-0 w-100"> </div>
<gl-button </div>
class="text-secondary-700" <div class="col col-sm-5 d-flex justify-content-end my-1 my-sm-0 w-100">
@click="initiateAllReplicableSyncs($options.actionTypes.RESYNC)" <gl-button @click="initiateAllReplicableSyncs($options.actionTypes.RESYNC)">{{
>{{ __('Resync all') }}</gl-button __('Resync all')
> }}</gl-button>
</div>
</div> </div>
</nav> </nav>
</template> </template>
<script> <script>
import { mapActions } from 'vuex'; import { mapActions } from 'vuex';
import { GlLink, GlDeprecatedButton } from '@gitlab/ui'; import { GlLink, GlButton } from '@gitlab/ui';
import { __ } from '~/locale'; import { __ } from '~/locale';
import { ACTION_TYPES } from '../store/constants'; import { ACTION_TYPES } from '../store/constants';
import GeoReplicableStatus from './geo_replicable_status.vue'; import GeoReplicableStatus from './geo_replicable_status.vue';
...@@ -10,7 +10,7 @@ export default { ...@@ -10,7 +10,7 @@ export default {
name: 'GeoReplicableItem', name: 'GeoReplicableItem',
components: { components: {
GlLink, GlLink,
GlDeprecatedButton, GlButton,
GeoReplicableTimeAgo, GeoReplicableTimeAgo,
GeoReplicableStatus, GeoReplicableStatus,
}, },
...@@ -77,9 +77,10 @@ export default { ...@@ -77,9 +77,10 @@ export default {
<div class="card-header d-flex align-center"> <div class="card-header d-flex align-center">
<gl-link class="font-weight-bold" :href="`/${name}`" target="_blank">{{ name }}</gl-link> <gl-link class="font-weight-bold" :href="`/${name}`" target="_blank">{{ name }}</gl-link>
<div class="ml-auto"> <div class="ml-auto">
<gl-deprecated-button <gl-button
size="small"
@click="initiateReplicableSync({ projectId, name, action: $options.actionTypes.RESYNC })" @click="initiateReplicableSync({ projectId, name, action: $options.actionTypes.RESYNC })"
>{{ __('Resync') }}</gl-deprecated-button >{{ __('Resync') }}</gl-button
> >
</div> </div>
</div> </div>
......
...@@ -157,11 +157,11 @@ module EE ...@@ -157,11 +157,11 @@ module EE
end end
def resync_all_button def resync_all_button
button_to(s_("Geo|Resync all"), { controller: controller_name, action: :resync_all }, class: "btn btn-default mr-2") button_to(s_("Geo|Resync all"), { controller: controller_name, action: :resync_all }, class: "btn btn-default btn-md mr-2")
end end
def reverify_all_button def reverify_all_button
button_to(s_("Geo|Reverify all"), { controller: controller_name, action: :reverify_all }, class: "btn btn-default") button_to(s_("Geo|Reverify all"), { controller: controller_name, action: :reverify_all }, class: "btn btn-default btn-md")
end end
end end
end end
- page_title _('Geo Designs') = render 'admin/geo/shared/replication_nav'
#js-geo-replicable{ data: { "geo-replicable-empty-svg-path" => image_path('illustrations/empty-state/geo-replication-empty.svg'), #js-geo-replicable{ data: { "geo-replicable-empty-svg-path" => image_path('illustrations/empty-state/geo-replication-empty.svg'),
"geo-troubleshooting-link" => help_page_path('administration/geo/replication/troubleshooting.html'), "geo-troubleshooting-link" => help_page_path('administration/geo/replication/troubleshooting.html'),
"replicable-type" => 'designs' } } "replicable-type" => 'designs' } }
- page_title 'Geo Projects' = render 'admin/geo/shared/replication_nav'
- params[:sync_status] ||= []
= render 'admin/geo/shared/filter_nav', replicable_name: _('projects'), replicable_controller: 'admin/geo/projects', action_buttons: @action_buttons = render 'admin/geo/shared/filter_nav', replicable_name: _('projects'), replicable_controller: 'admin/geo/projects', action_buttons: @action_buttons
- case params[:sync_status] - case params[:sync_status]
- when 'failed' - when 'failed'
......
- action_buttons = local_assigns[:action_buttons] ? action_buttons : [] - action_buttons = local_assigns[:action_buttons] ? action_buttons : []
- params[:sync_status] ||= []
%nav.row.d-flex.flex-column.flex-sm-row.align-items-center.bg-secondary.border-bottom.border-secondary-100.p-3 %nav.bg-secondary.border-bottom.border-secondary-100.p-3
.dropdown.col.px-1.my-1.my-sm-0.w-100 .row.d-flex.flex-column.flex-sm-row
.col
.d-sm-flex.mx-n1
.dropdown.px-1.my-1.my-sm-0.w-100
%a.btn.d-flex.align-items-center.justify-content-between.w-100{ href: '#', data: { toggle: 'dropdown' }, 'aria-haspopup' => 'true', 'aria-expanded' => 'false' } %a.btn.d-flex.align-items-center.justify-content-between.w-100{ href: '#', data: { toggle: 'dropdown' }, 'aria-haspopup' => 'true', 'aria-expanded' => 'false' }
= s_('Geo|Filter by status') = s_('Geo|Filter by status')
= sprite_icon("chevron-down", size: 16) = sprite_icon("chevron-down", size: 16)
...@@ -18,8 +22,8 @@ ...@@ -18,8 +22,8 @@
= nav_link(html_options: { class: ('bg-secondary-100' if params[:sync_status] == 'synced') }) do = nav_link(html_options: { class: ('bg-secondary-100' if params[:sync_status] == 'synced') }) do
= link_to controller: replicable_controller, sync_status: 'synced' do = link_to controller: replicable_controller, sync_status: 'synced' do
= s_('Geo|Synced') = s_('Geo|Synced')
.col.replicable-search.px-1.my-1.my-sm-0.w-100 .replicable-search.px-1.my-1.my-sm-0.w-100
= render 'shared/projects/search_form', autofocus: true, search_form_placeholder: _("Filter by name"), icon: true = render 'shared/projects/search_form', autofocus: true, search_form_placeholder: _("Filter by name"), icon: true
.col.col-sm-6.d-flex.justify-content-end.my-1.my-sm-0.w-100 .col.col-sm-5.d-flex.justify-content-end.my-1.my-sm-0.w-100
- action_buttons.each do |action_button| - action_buttons.each do |action_button|
= action_button = action_button
- page_title _('Geo Replication')
%header.py-2
%h2.page-title
= _('Geo Replication')
%p
= s_('Geo|Review replication status, and resynchronize and reverify items with the primary node.')
%ul.nav-links.nav.nav-tabs.border-top.border-bottom.border-secondary-100
= nav_link(path: 'admin/geo/projects#index', html_options: { class: 'pr-2' }) do
= link_to admin_geo_projects_path, title: _('Projects') do
%span
= _('Projects')
= nav_link(path: 'admin/geo/uploads#index', html_options: { class: 'pr-2' }) do
= link_to admin_geo_uploads_path, title: _('Uploads') do
%span
= _('Uploads')
= nav_link(path: 'admin/geo/designs#index', html_options: { class: 'pr-2' }) do
= link_to admin_geo_designs_path, title: _('Designs') do
%span
= _('Designs')
- page_title 'Geo Uploads' = render 'admin/geo/shared/replication_nav'
- params[:sync_status] ||= []
= render 'admin/geo/shared/filter_nav', replicable_name: _('uploads'), replicable_controller: 'admin/geo/uploads' = render 'admin/geo/shared/filter_nav', replicable_name: _('uploads'), replicable_controller: 'admin/geo/uploads'
- if @registries.any? - if @registries.any?
- @registries.each do |upload_registry| - @registries.each do |upload_registry|
......
...@@ -10,23 +10,15 @@ ...@@ -10,23 +10,15 @@
%strong.fly-out-top-item-name %strong.fly-out-top-item-name
#{ _('Geo') } #{ _('Geo') }
= nav_link(path: 'admin/geo/nodes#index') do = nav_link(path: 'admin/geo/nodes#index') do
= link_to admin_geo_nodes_path, title: 'Nodes' do = link_to admin_geo_nodes_path, title: _('Nodes') do
%span %span
= _('Nodes') = _('Nodes')
= nav_link(path: 'admin/geo/settings#show') do
= link_to admin_geo_settings_path, title: 'Settings' do
%span
= _('Settings')
- if Gitlab::Geo.secondary? - if Gitlab::Geo.secondary?
= nav_link(path: 'admin/geo/projects#index') do = nav_link(controller: %w(admin/geo/projects admin/geo/uploads admin/geo/designs)) do
= link_to admin_geo_projects_path, title: 'Projects' do = link_to admin_geo_projects_path, title: _('Replication') do
%span %span
= _('Projects') = _('Replication')
= nav_link(path: 'admin/geo/designs#index') do = nav_link(path: 'admin/geo/settings#show') do
= link_to admin_geo_designs_path, title: _('Designs') do = link_to admin_geo_settings_path, title: _('Settings') do
%span
= _('Designs')
= nav_link(path: 'admin/geo/uploads#index') do
= link_to admin_geo_uploads_path, title: 'Uploads' do
%span %span
= _('Uploads') = _('Settings')
---
title: Geo Replication View
merge_request: 30890
author:
type: added
...@@ -36,8 +36,16 @@ namespace :admin do ...@@ -36,8 +36,16 @@ namespace :admin do
namespace :geo do namespace :geo do
get '/' => 'nodes#index' get '/' => 'nodes#index'
# Old Routes Replaced in 13.0
get '/projects', to: redirect(path: 'admin/geo/replication/projects')
get '/uploads', to: redirect(path: 'admin/geo/replication/uploads')
get '/designs', to: redirect(path: 'admin/geo/replication/designs')
resources :nodes, only: [:index, :create, :new, :edit, :update] resources :nodes, only: [:index, :create, :new, :edit, :update]
scope '/replication' do
get '/', to: redirect(path: 'admin/geo/replication/projects')
resources :projects, only: [:index, :destroy] do resources :projects, only: [:index, :destroy] do
member do member do
post :reverify post :reverify
...@@ -51,13 +59,14 @@ namespace :admin do ...@@ -51,13 +59,14 @@ namespace :admin do
end end
end end
resource :settings, only: [:show, :update]
resources :designs, only: [:index] resources :designs, only: [:index]
resources :uploads, only: [:index, :destroy] resources :uploads, only: [:index, :destroy]
end end
resource :settings, only: [:show, :update]
end
namespace :elasticsearch do namespace :elasticsearch do
post :enqueue_index post :enqueue_index
end end
......
# frozen_string_literal: true
require 'spec_helper'
describe 'admin Geo Replication Nav', :js, :geo do
include ::EE::GeoHelpers
include StubENV
let_it_be(:admin) { create(:admin) }
before do
stub_licensed_features(geo: true)
sign_in(admin)
stub_secondary_node
end
shared_examples 'active sidebar link' do |link_name|
before do
visit path
wait_for_requests
end
it 'has active class' do
navigation_link = page.find("a[title=\"#{link_name}\"]").find(:xpath, '..')
expect(navigation_link[:class]).to include('active')
end
end
describe 'visit admin/geo/replication/projects' do
it_behaves_like 'active sidebar link', 'Projects' do
let(:path) { admin_geo_projects_path }
end
end
describe 'visit admin/geo/replication/uploads' do
it_behaves_like 'active sidebar link', 'Uploads' do
let(:path) { admin_geo_uploads_path }
end
end
describe 'visit admin/geo/replication/designs' do
it_behaves_like 'active sidebar link', 'Designs' do
let(:path) { admin_geo_designs_path }
end
end
end
...@@ -25,13 +25,13 @@ describe 'admin Geo Sidebar', :js, :geo do ...@@ -25,13 +25,13 @@ describe 'admin Geo Sidebar', :js, :geo do
end end
end end
describe 'clicking the nodes link' do describe 'visiting geo nodes' do
it_behaves_like 'active sidebar link', 'Nodes' do it_behaves_like 'active sidebar link', 'Nodes' do
let(:path) { admin_geo_nodes_path } let(:path) { admin_geo_nodes_path }
end end
end end
describe 'clicking the settings link' do describe 'visiting geo settings' do
before do before do
stub_env('IN_MEMORY_APPLICATION_SETTINGS', 'false') stub_env('IN_MEMORY_APPLICATION_SETTINGS', 'false')
end end
...@@ -46,20 +46,20 @@ describe 'admin Geo Sidebar', :js, :geo do ...@@ -46,20 +46,20 @@ describe 'admin Geo Sidebar', :js, :geo do
stub_secondary_node stub_secondary_node
end end
describe 'clicking the projects link' do describe 'visiting geo projects' do
it_behaves_like 'active sidebar link', 'Projects' do it_behaves_like 'active sidebar link', 'Replication' do
let(:path) { admin_geo_projects_path } let(:path) { admin_geo_projects_path }
end end
end end
describe 'clicking the designs link' do describe 'visiting geo designs' do
it_behaves_like 'active sidebar link', 'Designs' do it_behaves_like 'active sidebar link', 'Replication' do
let(:path) { admin_geo_designs_path } let(:path) { admin_geo_designs_path }
end end
end end
describe 'clicking the uploads link' do describe 'visiting geo uploads' do
it_behaves_like 'active sidebar link', 'Uploads' do it_behaves_like 'active sidebar link', 'Replication' do
let(:path) { admin_geo_uploads_path } let(:path) { admin_geo_uploads_path }
end end
end end
......
import Vuex from 'vuex'; import Vuex from 'vuex';
import { createLocalVue, mount } from '@vue/test-utils'; import { createLocalVue, mount } from '@vue/test-utils';
import { GlLink, GlDeprecatedButton } from '@gitlab/ui'; import { GlLink, GlButton } from '@gitlab/ui';
import GeoReplicableItem from 'ee/geo_replicable/components/geo_replicable_item.vue'; import GeoReplicableItem from 'ee/geo_replicable/components/geo_replicable_item.vue';
import store from 'ee/geo_replicable/store'; import store from 'ee/geo_replicable/store';
import { ACTION_TYPES } from 'ee/geo_replicable/store/constants'; import { ACTION_TYPES } from 'ee/geo_replicable/store/constants';
...@@ -43,7 +43,7 @@ describe('GeoReplicableItem', () => { ...@@ -43,7 +43,7 @@ describe('GeoReplicableItem', () => {
const findCard = () => wrapper.find('.card'); const findCard = () => wrapper.find('.card');
const findGlLink = () => findCard().find(GlLink); const findGlLink = () => findCard().find(GlLink);
const findGlButton = () => findCard().find(GlDeprecatedButton); const findGlButton = () => findCard().find(GlButton);
const findCardHeader = () => findCard().find('.card-header'); const findCardHeader = () => findCard().find('.card-header');
const findCardBody = () => findCard().find('.card-body'); const findCardBody = () => findCard().find('.card-body');
......
...@@ -55,19 +55,19 @@ describe Gitlab::Middleware::ReadOnly do ...@@ -55,19 +55,19 @@ describe Gitlab::Middleware::ReadOnly do
context 'whitelisted requests' do context 'whitelisted requests' do
it_behaves_like 'whitelisted request', :patch, '/admin/geo/nodes/1' it_behaves_like 'whitelisted request', :patch, '/admin/geo/nodes/1'
it_behaves_like 'whitelisted request', :delete, '/admin/geo/projects/1' it_behaves_like 'whitelisted request', :delete, '/admin/geo/replication/projects/1'
it_behaves_like 'whitelisted request', :post, '/admin/geo/projects/1/resync' it_behaves_like 'whitelisted request', :post, '/admin/geo/replication/projects/1/resync'
it_behaves_like 'whitelisted request', :post, '/admin/geo/projects/1/reverify' it_behaves_like 'whitelisted request', :post, '/admin/geo/replication/projects/1/reverify'
it_behaves_like 'whitelisted request', :post, '/admin/geo/projects/reverify_all' it_behaves_like 'whitelisted request', :post, '/admin/geo/replication/projects/reverify_all'
it_behaves_like 'whitelisted request', :post, '/admin/geo/projects/resync_all' it_behaves_like 'whitelisted request', :post, '/admin/geo/replication/projects/resync_all'
it_behaves_like 'whitelisted request', :post, '/admin/geo/projects/1/force_redownload' it_behaves_like 'whitelisted request', :post, '/admin/geo/replication/projects/1/force_redownload'
it_behaves_like 'whitelisted request', :delete, '/admin/geo/uploads/1' it_behaves_like 'whitelisted request', :delete, '/admin/geo/replication/uploads/1'
end end
it 'expects geo replication node api requests to be allowed' do it 'expects geo replication node api requests to be allowed' do
......
...@@ -6,23 +6,41 @@ describe 'EE-specific admin routing' do ...@@ -6,23 +6,41 @@ describe 'EE-specific admin routing' do
let(:project_registry) { create(:geo_project_registry) } let(:project_registry) { create(:geo_project_registry) }
it 'routes / to #index' do it 'routes / to #index' do
expect(get('/admin/geo/projects')).to route_to('admin/geo/projects#index') expect(get('/admin/geo/replication/projects')).to route_to('admin/geo/projects#index')
end end
it 'routes delete /:id to #destroy' do it 'routes delete /:id to #destroy' do
expect(delete("/admin/geo/projects/#{project_registry.id}")).to route_to('admin/geo/projects#destroy', id: project_registry.to_param) expect(delete("/admin/geo/replication/projects/#{project_registry.id}")).to route_to('admin/geo/projects#destroy', id: project_registry.to_param)
end end
it 'routes post /:id/reverify to #reverify' do it 'routes post /:id/reverify to #reverify' do
expect(post("admin/geo/projects/#{project_registry.id}/reverify")).to route_to('admin/geo/projects#reverify', id: project_registry.to_param) expect(post("/admin/geo/replication/projects/#{project_registry.id}/reverify")).to route_to('admin/geo/projects#reverify', id: project_registry.to_param)
end end
it 'routes post /:id/resync to #resync' do it 'routes post /:id/resync to #resync' do
expect(post("admin/geo/projects/#{project_registry.id}/resync")).to route_to('admin/geo/projects#resync', id: project_registry.to_param) expect(post("/admin/geo/replication/projects/#{project_registry.id}/resync")).to route_to('admin/geo/projects#resync', id: project_registry.to_param)
end end
it 'routes post /:id/force_redownload to #force_redownload' do it 'routes post /:id/force_redownload to #force_redownload' do
expect(post("admin/geo/projects/#{project_registry.id}/force_redownload")).to route_to('admin/geo/projects#force_redownload', id: project_registry.to_param) expect(post("/admin/geo/replication/projects/#{project_registry.id}/force_redownload")).to route_to('admin/geo/projects#force_redownload', id: project_registry.to_param)
end
end
describe Admin::Geo::UploadsController, 'routing' do
let!(:upload_registry) { create(:geo_upload_registry, :with_file, :attachment, success: true) }
it 'routes / to #index' do
expect(get('/admin/geo/replication/uploads')).to route_to('admin/geo/uploads#index')
end
it 'routes delete /:id to #destroy' do
expect(delete("/admin/geo/replication/uploads/#{upload_registry.id}")).to route_to('admin/geo/uploads#destroy', id: upload_registry.to_param)
end
end
describe Admin::Geo::DesignsController, 'routing' do
it 'routes / to #index' do
expect(get('/admin/geo/replication/designs')).to route_to('admin/geo/designs#index')
end end
end end
......
...@@ -9674,15 +9674,15 @@ msgstr "" ...@@ -9674,15 +9674,15 @@ msgstr ""
msgid "Geo" msgid "Geo"
msgstr "" msgstr ""
msgid "Geo Designs"
msgstr ""
msgid "Geo Nodes" msgid "Geo Nodes"
msgstr "" msgstr ""
msgid "Geo Nodes|Cannot remove a primary node if there is a secondary node" msgid "Geo Nodes|Cannot remove a primary node if there is a secondary node"
msgstr "" msgstr ""
msgid "Geo Replication"
msgstr ""
msgid "Geo Settings" msgid "Geo Settings"
msgstr "" msgstr ""
...@@ -9974,6 +9974,9 @@ msgstr "" ...@@ -9974,6 +9974,9 @@ msgstr ""
msgid "Geo|Reverify all" msgid "Geo|Reverify all"
msgstr "" msgstr ""
msgid "Geo|Review replication status, and resynchronize and reverify items with the primary node."
msgstr ""
msgid "Geo|Status" msgid "Geo|Status"
msgstr "" msgstr ""
...@@ -17621,6 +17624,9 @@ msgstr "" ...@@ -17621,6 +17624,9 @@ msgstr ""
msgid "Replaces the clone URL root." msgid "Replaces the clone URL root."
msgstr "" msgstr ""
msgid "Replication"
msgstr ""
msgid "Reply by email" msgid "Reply by email"
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