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,33 +40,36 @@ export default {
</script>
<template>
<nav
class="row d-flex flex-column flex-sm-row align-items-center bg-secondary border-bottom border-secondary-100 p-3"
>
<gl-dropdown :text="__('Filter by status')" class="col px-1 my-1 my-sm-0 w-100">
<gl-dropdown-item
v-for="(filter, index) in filterOptions"
:key="index"
:class="{ 'bg-secondary-100': index === currentFilterIndex }"
@click="filterChange(index)"
>
<span
>{{ filter.label }} <span v-if="filter.label === 'All'">{{ replicableType }}</span></span
>
</gl-dropdown-item>
</gl-dropdown>
<gl-search-box-by-type
v-model="search"
class="col px-1 my-1 my-sm-0 bg-white w-100"
type="text"
:placeholder="__(`Filter by name`)"
/>
<div class="col col-sm-6 d-flex justify-content-end my-1 my-sm-0 w-100">
<gl-button
class="text-secondary-700"
@click="initiateAllReplicableSyncs($options.actionTypes.RESYNC)"
>{{ __('Resync all') }}</gl-button
>
<nav class="bg-secondary border-bottom border-secondary-100 p-3">
<div class="row d-flex flex-column flex-sm-row">
<div class="col">
<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
v-for="(filter, index) in filterOptions"
:key="index"
:class="{ 'bg-secondary-100': index === currentFilterIndex }"
@click="filterChange(index)"
>
<span
>{{ filter.label }}
<span v-if="filter.label === 'All'">{{ replicableType }}</span></span
>
</gl-dropdown-item>
</gl-dropdown>
<gl-search-box-by-type
v-model="search"
class="px-1 my-1 my-sm-0 bg-white w-100"
type="text"
:placeholder="__('Filter by name')"
/>
</div>
</div>
<div class="col col-sm-5 d-flex justify-content-end my-1 my-sm-0 w-100">
<gl-button @click="initiateAllReplicableSyncs($options.actionTypes.RESYNC)">{{
__('Resync all')
}}</gl-button>
</div>
</div>
</nav>
</template>
<script>
import { mapActions } from 'vuex';
import { GlLink, GlDeprecatedButton } from '@gitlab/ui';
import { GlLink, GlButton } from '@gitlab/ui';
import { __ } from '~/locale';
import { ACTION_TYPES } from '../store/constants';
import GeoReplicableStatus from './geo_replicable_status.vue';
......@@ -10,7 +10,7 @@ export default {
name: 'GeoReplicableItem',
components: {
GlLink,
GlDeprecatedButton,
GlButton,
GeoReplicableTimeAgo,
GeoReplicableStatus,
},
......@@ -77,9 +77,10 @@ export default {
<div class="card-header d-flex align-center">
<gl-link class="font-weight-bold" :href="`/${name}`" target="_blank">{{ name }}</gl-link>
<div class="ml-auto">
<gl-deprecated-button
<gl-button
size="small"
@click="initiateReplicableSync({ projectId, name, action: $options.actionTypes.RESYNC })"
>{{ __('Resync') }}</gl-deprecated-button
>{{ __('Resync') }}</gl-button
>
</div>
</div>
......
......@@ -157,11 +157,11 @@ module EE
end
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
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
- 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'),
"geo-troubleshooting-link" => help_page_path('administration/geo/replication/troubleshooting.html'),
"replicable-type" => 'designs' } }
- page_title 'Geo Projects'
- params[:sync_status] ||= []
= render 'admin/geo/shared/replication_nav'
= render 'admin/geo/shared/filter_nav', replicable_name: _('projects'), replicable_controller: 'admin/geo/projects', action_buttons: @action_buttons
- case params[:sync_status]
- when 'failed'
......
- 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
.dropdown.col.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' }
= s_('Geo|Filter by status')
= sprite_icon("chevron-down", size: 16)
%ul.dropdown-menu
= nav_link(html_options: { class: ('bg-secondary-100' if !params[:sync_status].present?) }) do
= link_to controller: replicable_controller do
= sprintf(s_('Geo|All %{replicable_name}'), { replicable_name: replicable_name })
= nav_link(html_options: { class: ('bg-secondary-100' if params[:sync_status] == 'pending') }) do
= link_to controller: replicable_controller, sync_status: 'pending' do
= s_('Geo|In progress')
= nav_link(html_options: { class: ('bg-secondary-100' if params[:sync_status] == 'failed') }) do
= link_to controller: replicable_controller, sync_status: 'failed' do
= s_('Geo|Failed')
= nav_link(html_options: { class: ('bg-secondary-100' if params[:sync_status] == 'synced') }) do
= link_to controller: replicable_controller, sync_status: 'synced' do
= s_('Geo|Synced')
.col.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
.col.col-sm-6.d-flex.justify-content-end.my-1.my-sm-0.w-100
- action_buttons.each do |action_button|
= action_button
%nav.bg-secondary.border-bottom.border-secondary-100.p-3
.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' }
= s_('Geo|Filter by status')
= sprite_icon("chevron-down", size: 16)
%ul.dropdown-menu
= nav_link(html_options: { class: ('bg-secondary-100' if !params[:sync_status].present?) }) do
= link_to controller: replicable_controller do
= sprintf(s_('Geo|All %{replicable_name}'), { replicable_name: replicable_name })
= nav_link(html_options: { class: ('bg-secondary-100' if params[:sync_status] == 'pending') }) do
= link_to controller: replicable_controller, sync_status: 'pending' do
= s_('Geo|In progress')
= nav_link(html_options: { class: ('bg-secondary-100' if params[:sync_status] == 'failed') }) do
= link_to controller: replicable_controller, sync_status: 'failed' do
= s_('Geo|Failed')
= nav_link(html_options: { class: ('bg-secondary-100' if params[:sync_status] == 'synced') }) do
= link_to controller: replicable_controller, sync_status: 'synced' do
= s_('Geo|Synced')
.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
.col.col-sm-5.d-flex.justify-content-end.my-1.my-sm-0.w-100
- action_buttons.each do |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'
- params[:sync_status] ||= []
= render 'admin/geo/shared/replication_nav'
= render 'admin/geo/shared/filter_nav', replicable_name: _('uploads'), replicable_controller: 'admin/geo/uploads'
- if @registries.any?
- @registries.each do |upload_registry|
......
......@@ -10,23 +10,15 @@
%strong.fly-out-top-item-name
#{ _('Geo') }
= 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
= _('Nodes')
- if Gitlab::Geo.secondary?
= nav_link(controller: %w(admin/geo/projects admin/geo/uploads admin/geo/designs)) do
= link_to admin_geo_projects_path, title: _('Replication') do
%span
= _('Replication')
= nav_link(path: 'admin/geo/settings#show') do
= link_to admin_geo_settings_path, title: 'Settings' do
= link_to admin_geo_settings_path, title: _('Settings') do
%span
= _('Settings')
- if Gitlab::Geo.secondary?
= nav_link(path: 'admin/geo/projects#index') do
= link_to admin_geo_projects_path, title: 'Projects' do
%span
= _('Projects')
= nav_link(path: 'admin/geo/designs#index') do
= link_to admin_geo_designs_path, title: _('Designs') do
%span
= _('Designs')
= nav_link(path: 'admin/geo/uploads#index') do
= link_to admin_geo_uploads_path, title: 'Uploads' do
%span
= _('Uploads')
---
title: Geo Replication View
merge_request: 30890
author:
type: added
......@@ -36,26 +36,35 @@ namespace :admin do
namespace :geo do
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 :projects, only: [:index, :destroy] do
member do
post :reverify
post :resync
post :force_redownload
end
scope '/replication' do
get '/', to: redirect(path: 'admin/geo/replication/projects')
collection do
post :reverify_all
post :resync_all
resources :projects, only: [:index, :destroy] do
member do
post :reverify
post :resync
post :force_redownload
end
collection do
post :reverify_all
post :resync_all
end
end
end
resource :settings, only: [:show, :update]
resources :designs, only: [:index]
resources :designs, only: [:index]
resources :uploads, only: [:index, :destroy]
end
resources :uploads, only: [:index, :destroy]
resource :settings, only: [:show, :update]
end
namespace :elasticsearch do
......
# 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
end
end
describe 'clicking the nodes link' do
describe 'visiting geo nodes' do
it_behaves_like 'active sidebar link', 'Nodes' do
let(:path) { admin_geo_nodes_path }
end
end
describe 'clicking the settings link' do
describe 'visiting geo settings' do
before do
stub_env('IN_MEMORY_APPLICATION_SETTINGS', 'false')
end
......@@ -46,20 +46,20 @@ describe 'admin Geo Sidebar', :js, :geo do
stub_secondary_node
end
describe 'clicking the projects link' do
it_behaves_like 'active sidebar link', 'Projects' do
describe 'visiting geo projects' do
it_behaves_like 'active sidebar link', 'Replication' do
let(:path) { admin_geo_projects_path }
end
end
describe 'clicking the designs link' do
it_behaves_like 'active sidebar link', 'Designs' do
describe 'visiting geo designs' do
it_behaves_like 'active sidebar link', 'Replication' do
let(:path) { admin_geo_designs_path }
end
end
describe 'clicking the uploads link' do
it_behaves_like 'active sidebar link', 'Uploads' do
describe 'visiting geo uploads' do
it_behaves_like 'active sidebar link', 'Replication' do
let(:path) { admin_geo_uploads_path }
end
end
......
import Vuex from 'vuex';
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 store from 'ee/geo_replicable/store';
import { ACTION_TYPES } from 'ee/geo_replicable/store/constants';
......@@ -43,7 +43,7 @@ describe('GeoReplicableItem', () => {
const findCard = () => wrapper.find('.card');
const findGlLink = () => findCard().find(GlLink);
const findGlButton = () => findCard().find(GlDeprecatedButton);
const findGlButton = () => findCard().find(GlButton);
const findCardHeader = () => findCard().find('.card-header');
const findCardBody = () => findCard().find('.card-body');
......
......@@ -55,19 +55,19 @@ describe Gitlab::Middleware::ReadOnly do
context 'whitelisted requests' do
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
it 'expects geo replication node api requests to be allowed' do
......
......@@ -6,23 +6,41 @@ describe 'EE-specific admin routing' do
let(:project_registry) { create(:geo_project_registry) }
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
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
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
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
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
......
......@@ -9674,15 +9674,15 @@ msgstr ""
msgid "Geo"
msgstr ""
msgid "Geo Designs"
msgstr ""
msgid "Geo Nodes"
msgstr ""
msgid "Geo Nodes|Cannot remove a primary node if there is a secondary node"
msgstr ""
msgid "Geo Replication"
msgstr ""
msgid "Geo Settings"
msgstr ""
......@@ -9974,6 +9974,9 @@ msgstr ""
msgid "Geo|Reverify all"
msgstr ""
msgid "Geo|Review replication status, and resynchronize and reverify items with the primary node."
msgstr ""
msgid "Geo|Status"
msgstr ""
......@@ -17621,6 +17624,9 @@ msgstr ""
msgid "Replaces the clone URL root."
msgstr ""
msgid "Replication"
msgstr ""
msgid "Reply by email"
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