Commit 18511be9 authored by Tim Zallmann's avatar Tim Zallmann

Merge branch '3195-geo-admin-enhancements' into 'master'

Enhancements for Geo admin screen

Closes #3195

See merge request gitlab-org/gitlab-ee!3545
parents 89311f9f 0df7c295
This diff is collapsed.
...@@ -58,3 +58,4 @@ ...@@ -58,3 +58,4 @@
@import "framework/snippets"; @import "framework/snippets";
@import "framework/memory_graph"; @import "framework/memory_graph";
@import "framework/responsive_tables"; @import "framework/responsive_tables";
@import "framework/stacked-progress-bar";
.stacked-progress-bar {
display: flex;
height: 20px;
width: 350px;
border-radius: 10px;
overflow: hidden;
background-color: $theme-gray-100;
.status-unavailable,
.status-green,
.status-neutral,
.status-red, {
height: 100%;
font-weight: normal;
color: $white-light;
line-height: 20px;
&.has-value {
padding: 0 10px;
}
&:hover {
cursor: pointer;
}
}
.status-unavailable {
padding: 0 10px;
color: $theme-gray-700;
}
.status-green {
background-color: $green-500;
&:hover {
background-color: $green-600;
}
}
.status-neutral {
background-color: $theme-gray-200;
color: $gl-gray-dark;
&:hover {
background-color: $theme-gray-300;
}
}
.status-red {
background-color: $red-500;
&:hover {
background-color: $red-600;
}
}
}
.geo-admin-container {
.page-title,
.page-title-separator {
margin-top: 10px;
}
.title-text {
line-height: 34px;
}
.page-subtitle {
margin-bottom: 24px;
}
}
.geo-node-status {
td {
vertical-align: top;
}
.help-block {
width: 135px;
text-align: right;
}
.node-info {
font-weight: $gl-font-weight-bold;
}
.event-timestamp {
font-weight: normal;
color: $theme-gray-800;
}
.sync-status {
font-weight: normal;
svg {
vertical-align: middle;
}
.sync-status-icon svg,
.sync-status-timestamp {
fill: $theme-gray-700;
color: $theme-gray-700;
}
&.sync-status-failure {
.sync-status-icon svg,
.sync-status-timestamp {
fill: $red-700;
color: $red-700;
}
}
}
}
.advanced-geo-node-status-toggler {
display: block;
.show-advance-chevron {
margin-top: 2px;
}
}
.geo-node-healthy { .geo-node-healthy {
color: $gl-success; color: $gl-success;
} }
...@@ -52,6 +117,16 @@ ...@@ -52,6 +117,16 @@
white-space: pre-wrap; white-space: pre-wrap;
} }
.geo-nodes {
.health-message {
padding: 1px 8px;
background-color: $red-100;
color: $red-500;
border-radius: $border-radius-default;
font-weight: 500;
}
}
.node-badge { .node-badge {
color: $white-light; color: $white-light;
display: inline-block; display: inline-block;
......
...@@ -3,6 +3,7 @@ class GeoNodeStatus < ActiveRecord::Base ...@@ -3,6 +3,7 @@ class GeoNodeStatus < ActiveRecord::Base
# Whether we were successful in reaching this node # Whether we were successful in reaching this node
attr_accessor :success attr_accessor :success
attr_accessor :health_status
# Be sure to keep this consistent with Prometheus naming conventions # Be sure to keep this consistent with Prometheus naming conventions
PROMETHEUS_METRICS = { PROMETHEUS_METRICS = {
...@@ -47,8 +48,8 @@ class GeoNodeStatus < ActiveRecord::Base ...@@ -47,8 +48,8 @@ class GeoNodeStatus < ActiveRecord::Base
end end
def self.allowed_params def self.allowed_params
excluded_params = %w(id last_successful_status_check_at created_at updated_at).freeze excluded_params = %w(id created_at updated_at).freeze
extra_params = %w(success health last_event_timestamp cursor_last_event_timestamp).freeze extra_params = %w(success health health_status last_event_timestamp cursor_last_event_timestamp).freeze
self.column_names - excluded_params + extra_params self.column_names - excluded_params + extra_params
end end
...@@ -89,6 +90,10 @@ class GeoNodeStatus < ActiveRecord::Base ...@@ -89,6 +90,10 @@ class GeoNodeStatus < ActiveRecord::Base
status_message.blank? || status_message == 'Healthy'.freeze status_message.blank? || status_message == 'Healthy'.freeze
end end
def health_status
@health_status || (healthy? ? 'Healthy' : 'Unhealthy')
end
def last_successful_status_check_timestamp def last_successful_status_check_timestamp
self.last_successful_status_check_at.to_i self.last_successful_status_check_at.to_i
end end
......
...@@ -7,6 +7,7 @@ class GeoNodeStatusEntity < Grape::Entity ...@@ -7,6 +7,7 @@ class GeoNodeStatusEntity < Grape::Entity
expose :health do |node| expose :health do |node|
node.healthy? ? 'Healthy' : node.health node.healthy? ? 'Healthy' : node.health
end end
expose :health_status
expose :attachments_count expose :attachments_count
expose :attachments_synced_count expose :attachments_synced_count
...@@ -37,4 +38,10 @@ class GeoNodeStatusEntity < Grape::Entity ...@@ -37,4 +38,10 @@ class GeoNodeStatusEntity < Grape::Entity
expose :cursor_last_event_timestamp expose :cursor_last_event_timestamp
expose :last_successful_status_check_timestamp expose :last_successful_status_check_timestamp
expose :namespaces, using: NamespaceEntity
def namespaces
object.geo_node.namespaces
end
end end
class NamespaceEntity < Grape::Entity
expose :id, :name, :path, :kind, :full_path, :parent_id
end
...@@ -6,7 +6,8 @@ module Geo ...@@ -6,7 +6,8 @@ module Geo
def call(geo_node) def call(geo_node)
return GeoNodeStatus.current_node_status if geo_node.current? return GeoNodeStatus.current_node_status if geo_node.current?
data = { success: false } data = GeoNodeStatus.find_or_initialize_by(geo_node: geo_node).attributes
data = data.merge(success: false, health_status: 'Offline')
begin begin
response = self.class.get(geo_node.status_url, headers: headers, timeout: timeout) response = self.class.get(geo_node.status_url, headers: headers, timeout: timeout)
...@@ -29,8 +30,10 @@ module Geo ...@@ -29,8 +30,10 @@ module Geo
end end
rescue Gitlab::Geo::GeoNodeNotFoundError rescue Gitlab::Geo::GeoNodeNotFoundError
data[:health] = 'This GitLab instance does not appear to be configured properly as a Geo node. Make sure the URLs are using the correct fully-qualified domain names.' data[:health] = 'This GitLab instance does not appear to be configured properly as a Geo node. Make sure the URLs are using the correct fully-qualified domain names.'
data[:health_status] = 'Unhealthy'
rescue OpenSSL::Cipher::CipherError rescue OpenSSL::Cipher::CipherError
data[:health] = 'Error decrypting the Geo secret from the database. Check that the primary uses the correct db_key_base.' data[:health] = 'Error decrypting the Geo secret from the database. Check that the primary uses the correct db_key_base.'
data[:health_status] = 'Unhealthy'
rescue HTTParty::Error, Timeout::Error, SocketError, SystemCallError, OpenSSL::SSL::SSLError => e rescue HTTParty::Error, Timeout::Error, SocketError, SystemCallError, OpenSSL::SSL::SSLError => e
data[:health] = e.message data[:health] = e.message
end end
......
- page_title 'Geo nodes' - page_title 'Geo nodes'
%h3.page-title - @content_class = "geo-admin-container"
Geo Nodes %h2.page-title.clearfix
%span.title-text.pull-left
Geo Nodes
= link_to "New node", new_admin_geo_node_path, class: 'btn btn-create pull-right'
%hr.page-title-separator
%p.light %p.page-subtitle.light
With #{link_to 'GitLab Geo', help_page_path('gitlab-geo/README'), class: 'vlink'} you can install a special With #{link_to 'GitLab Geo', help_page_path('gitlab-geo/README'), class: 'vlink'} you can install a special
read-only and replicated instance anywhere. read-only and replicated instance anywhere.
Before you add nodes, follow the Before you add nodes, follow the
...@@ -11,16 +15,6 @@ ...@@ -11,16 +15,6 @@
%strong exact order %strong exact order
they appear. they appear.
%hr
- if Gitlab::Geo.license_allows?
= form_for [:admin, @node], as: :geo_node, url: admin_geo_nodes_path, html: { class: 'form-horizontal js-geo-node-form' } do |f|
= render partial: 'form', locals: { form: f, geo_node: @node }
.form-actions
= f.submit 'Add Node', class: 'btn btn-create'
%hr
- if @nodes.any? - if @nodes.any?
.panel.panel-default .panel.panel-default
.panel-heading .panel-heading
...@@ -38,59 +32,84 @@ ...@@ -38,59 +32,84 @@
%span.help-block Primary node %span.help-block Primary node
- else - else
= status_loading_icon = status_loading_icon
- if node.restricted_project_ids %table.geo-node-status.js-geo-node-status.hidden
%p
%span.help-block
Namespaces to replicate:
%strong.node-info
= node_selected_namespaces_to_replicate(node)
.js-geo-node-status{ style: 'display: none' }
- if node.enabled? - if node.enabled?
%p %tr
%span.help-block %td
Health Status: .help-block
%span.js-health-status Health Status:
%p %td
%span.help-block .health-status.prepend-top-10.prepend-left-5.js-health-status
Repositories synced: %tr
%strong.node-info.js-repositories-synced %td
%p .help-block.prepend-top-10
%span.help-block Repositories:
Repositories failed: %td
%strong.node-info.js-repositories-failed .node-info.prepend-top-10.prepend-left-5.stacked-progress-bar.js-repositories
%p %span.status-unavailable.js-stats-unavailable
%span.help-block Not available
LFS objects synced: %span.status-green.js-synced
%strong.node-info.js-lfs-objects-synced %span.status-neutral.js-waiting
%p %span.status-red.js-failed
%span.help-block %tr
LFS objects failed: %td
%strong.node-info.js-lfs-objects-failed .help-block.prepend-top-10
%p LFS objects:
%span.help-block %td
Attachments synced: .node-info.prepend-top-10.prepend-left-5.stacked-progress-bar.js-lfs-objects
%strong.node-info.js-attachments-synced %span.status-unavailable.js-stats-unavailable
%p Not available
%span.help-block %span.status-green.js-synced
Attachments failed: %span.status-neutral.js-waiting
%strong.node-info.js-attachments-failed %span.status-red.js-failed
%p %tr
.advanced-geo-node-status-container %td
.advanced-status.hidden .help-block.prepend-top-10
%span.help-block Attachments:
%td
.node-info.prepend-top-10.prepend-left-5.stacked-progress-bar.js-attachments
%span.status-unavailable.js-stats-unavailable
Not available
%span.status-green.js-synced
%span.status-neutral.js-waiting
%span.status-red.js-failed
%tr
%td
.help-block.prepend-top-10
Sync settings:
%td
.node-info.prepend-top-10.prepend-left-5.js-sync-settings
%span.js-sync-type
%span.has-tooltip.sync-status.js-sync-status
%i.sync-status-icon.js-sync-status-icon
%span.sync-status-timestamp.js-sync-status-timestamp
%tr.js-advanced-status.hidden
%td
.help-block.prepend-top-10
Database replication lag: Database replication lag:
%strong.node-info.js-db-replication-lag %td
%span.help-block .node-info.prepend-top-10.prepend-left-5.js-db-replication-lag
%tr.js-advanced-status.hidden
%td
.help-block.prepend-top-10
Last event ID seen from primary: Last event ID seen from primary:
%strong.node-info.js-last-event-seen %td
%span.help-block .node-info.prepend-top-10.prepend-left-5.js-last-event-seen
%span.js-event-id
%span.event-timestamp.js-event-timestamp.has-tooltip
%tr.js-advanced-status.hidden
%td
.help-block.prepend-top-10
Last event ID processed by cursor: Last event ID processed by cursor:
%strong.node-info.js-last-cursor-event %td
%button.btn-link.js-advanced-geo-node-status-toggler .node-info.prepend-top-10.prepend-left-5.js-last-cursor-event
%span> Advanced %span.js-event-id
= icon('angle-down') %span.event-timestamp.js-event-timestamp.has-tooltip
%p %button.btn-link.advanced-geo-node-status-toggler.js-advanced-geo-node-status-toggler
.js-health %span> Advanced
%span.js-advance-toggle.show-advance-chevron.pull-right.inline.prepend-left-5
= sprite_icon('angle-down', css_class: 's16')
%p.health-message.hidden.js-health-message
- if Gitlab::Database.read_write? - if Gitlab::Database.read_write?
.node-actions .node-actions
......
- page_title 'New node'
- @content_class = "geo-admin-container"
%h2.page-title
%span.title-text
New node
%hr.page-title-separator
- if Gitlab::Geo.license_allows?
= form_for [:admin, @node], as: :geo_node, url: admin_geo_nodes_path, html: { class: 'form-horizontal js-geo-node-form' } do |f|
= render partial: 'form', locals: { form: f, geo_node: @node }
.form-actions
= f.submit 'Add Node', class: 'btn btn-create'
---
title: Enhancements for Geo admin screen
merge_request: 3545
author:
type: changed
...@@ -127,7 +127,7 @@ namespace :admin do ...@@ -127,7 +127,7 @@ namespace :admin do
get :download, on: :member get :download, on: :member
end end
resources :geo_nodes, only: [:index, :create, :edit, :update, :destroy] do resources :geo_nodes, only: [:index, :create, :new, :edit, :update, :destroy] do
member do member do
post :repair post :repair
post :toggle post :toggle
......
...@@ -22,10 +22,14 @@ class Admin::GeoNodesController < Admin::ApplicationController ...@@ -22,10 +22,14 @@ class Admin::GeoNodesController < Admin::ApplicationController
else else
@nodes = GeoNode.all @nodes = GeoNode.all
render :index render :new
end end
end end
def new
@node = GeoNode.new
end
def update def update
if Geo::NodeUpdateService.new(@node, geo_node_params).execute if Geo::NodeUpdateService.new(@node, geo_node_params).execute
redirect_to admin_geo_nodes_path, notice: 'Node was successfully updated.' redirect_to admin_geo_nodes_path, notice: 'Node was successfully updated.'
......
...@@ -30,8 +30,10 @@ describe Admin::GeoNodesController, :postgresql do ...@@ -30,8 +30,10 @@ describe Admin::GeoNodesController, :postgresql do
allow(Gitlab::Geo).to receive(:license_allows?).and_return(true) allow(Gitlab::Geo).to receive(:license_allows?).and_return(true)
end end
it 'renders creation form' do it 'does not display a flash message' do
expect(go).to render_template(partial: 'admin/geo_nodes/_form') go
expect(flash).not_to include(:alert)
end end
end end
...@@ -40,10 +42,6 @@ describe Admin::GeoNodesController, :postgresql do ...@@ -40,10 +42,6 @@ describe Admin::GeoNodesController, :postgresql do
allow(Gitlab::Geo).to receive(:license_allows?).and_return(false) allow(Gitlab::Geo).to receive(:license_allows?).and_return(false)
end end
it 'does not render the creation form' do
expect(go).not_to render_template(partial: 'admin/geo_nodes/_form')
end
it 'displays a flash message' do it 'displays a flash message' do
go go
......
...@@ -18,6 +18,7 @@ FactoryGirl.define do ...@@ -18,6 +18,7 @@ FactoryGirl.define do
last_event_timestamp Time.now.to_i last_event_timestamp Time.now.to_i
cursor_last_event_id 1 cursor_last_event_id 1
cursor_last_event_timestamp Time.now.to_i cursor_last_event_timestamp Time.now.to_i
last_successful_status_check_timestamp Time.now.beginning_of_day
end end
trait :unhealthy do trait :unhealthy do
......
...@@ -8,9 +8,10 @@ RSpec.describe 'admin Geo Nodes', type: :feature do ...@@ -8,9 +8,10 @@ RSpec.describe 'admin Geo Nodes', type: :feature do
sign_in(create(:admin)) sign_in(create(:admin))
end end
it 'show all public Geo Nodes' do it 'show all public Geo Nodes and create new node link' do
visit admin_geo_nodes_path visit admin_geo_nodes_path
expect(page).to have_link('New node', href: new_admin_geo_node_path)
page.within(find('.geo-nodes', match: :first)) do page.within(find('.geo-nodes', match: :first)) do
expect(page).to have_content(geo_node.url) expect(page).to have_content(geo_node.url)
end end
...@@ -20,7 +21,7 @@ RSpec.describe 'admin Geo Nodes', type: :feature do ...@@ -20,7 +21,7 @@ RSpec.describe 'admin Geo Nodes', type: :feature do
let(:new_ssh_key) { attributes_for(:key)[:key] } let(:new_ssh_key) { attributes_for(:key)[:key] }
before do before do
visit admin_geo_nodes_path visit new_admin_geo_node_path
end end
it 'creates a new Geo Node' do it 'creates a new Geo Node' do
...@@ -36,12 +37,10 @@ RSpec.describe 'admin Geo Nodes', type: :feature do ...@@ -36,12 +37,10 @@ RSpec.describe 'admin Geo Nodes', type: :feature do
end end
it 'returns an error message when a duplicate primary is added' do it 'returns an error message when a duplicate primary is added' do
check 'This is a primary node' create(:geo_node, :primary)
fill_in 'geo_node_url', with: 'https://test.example.com'
click_button 'Add Node'
check 'This is a primary node' check 'This is a primary node'
fill_in 'geo_node_url', with: 'https://secondary.example.com' fill_in 'geo_node_url', with: 'https://another-primary.example.com'
click_button 'Add Node' click_button 'Add Node'
expect(current_path).to eq admin_geo_nodes_path expect(current_path).to eq admin_geo_nodes_path
......
...@@ -4,6 +4,7 @@ ...@@ -4,6 +4,7 @@
"geo_node_id", "geo_node_id",
"healthy", "healthy",
"health", "health",
"health_status",
"attachments_count", "attachments_count",
"attachments_failed_count", "attachments_failed_count",
"attachments_synced_count", "attachments_synced_count",
...@@ -17,12 +18,14 @@ ...@@ -17,12 +18,14 @@
"last_event_id", "last_event_id",
"last_event_timestamp", "last_event_timestamp",
"cursor_last_event_id", "cursor_last_event_id",
"cursor_last_event_timestamp" "cursor_last_event_timestamp",
"namespaces"
], ],
"properties" : { "properties" : {
"geo_node_id": { "type": "integer" }, "geo_node_id": { "type": "integer" },
"healthy": { "type": "boolean" }, "healthy": { "type": "boolean" },
"health": { "type": ["string", "null"] }, "health": { "type": ["string", "null"] },
"health_status": { "type": "string" },
"attachments_count": { "type": "integer" }, "attachments_count": { "type": "integer" },
"attachments_failed_count": { "type": "integer" }, "attachments_failed_count": { "type": "integer" },
"attachments_synced_count": { "type": "integer" }, "attachments_synced_count": { "type": "integer" },
...@@ -40,7 +43,8 @@ ...@@ -40,7 +43,8 @@
"last_event_timestamp": { "type": ["integer", "null"] }, "last_event_timestamp": { "type": ["integer", "null"] },
"cursor_last_event_id": { "type": ["integer", "null"] }, "cursor_last_event_id": { "type": ["integer", "null"] },
"cursor_last_event_timestamp": { "type": ["integer", "null"] }, "cursor_last_event_timestamp": { "type": ["integer", "null"] },
"last_successful_status_check_timestamp": { "type": ["integer", "null"] } "last_successful_status_check_timestamp": { "type": ["integer", "null"] },
"namespaces": { "type": "array" }
}, },
"additionalProperties": false "additionalProperties": false
} }
require 'spec_helper' require 'spec_helper'
describe GeoNodeStatusEntity, :postgresql do describe GeoNodeStatusEntity, :postgresql do
let(:geo_node_status) do let(:geo_node_status) { build(:geo_node_status) }
GeoNodeStatus.new( let(:entity) { described_class.new(geo_node_status, request: double) }
geo_node_id: 1, let(:error) { 'Could not connect to Geo database' }
health: '',
attachments_count: 329,
attachments_failed_count: 25,
attachments_synced_count: 141,
lfs_objects_count: 256,
lfs_objects_failed_count: 12,
lfs_objects_synced_count: 123,
repositories_count: 10,
repositories_synced_count: 5,
repositories_failed_count: 0,
last_successful_status_check_timestamp: Time.now.beginning_of_day
)
end
let(:entity) do
described_class.new(geo_node_status, request: double)
end
let(:error) do
'Could not connect to Geo database'
end
subject { entity.as_json } subject { entity.as_json }
...@@ -44,6 +23,7 @@ describe GeoNodeStatusEntity, :postgresql do ...@@ -44,6 +23,7 @@ describe GeoNodeStatusEntity, :postgresql do
it { is_expected.to have_key(:repositories_synced_count)} it { is_expected.to have_key(:repositories_synced_count)}
it { is_expected.to have_key(:repositories_synced_in_percentage) } it { is_expected.to have_key(:repositories_synced_in_percentage) }
it { is_expected.to have_key(:last_successful_status_check_timestamp) } it { is_expected.to have_key(:last_successful_status_check_timestamp) }
it { is_expected.to have_key(:namespaces) }
describe '#healthy' do describe '#healthy' do
context 'when node is healthy' do context 'when node is healthy' do
...@@ -87,19 +67,47 @@ describe GeoNodeStatusEntity, :postgresql do ...@@ -87,19 +67,47 @@ describe GeoNodeStatusEntity, :postgresql do
describe '#attachments_synced_in_percentage' do describe '#attachments_synced_in_percentage' do
it 'formats as percentage' do it 'formats as percentage' do
geo_node_status.assign_attributes(attachments_count: 329,
attachments_failed_count: 25,
attachments_synced_count: 141)
expect(subject[:attachments_synced_in_percentage]).to eq '42.86%' expect(subject[:attachments_synced_in_percentage]).to eq '42.86%'
end end
end end
describe '#lfs_objects_synced_in_percentage' do describe '#lfs_objects_synced_in_percentage' do
it 'formats as percentage' do it 'formats as percentage' do
geo_node_status.assign_attributes(lfs_objects_count: 256,
lfs_objects_failed_count: 12,
lfs_objects_synced_count: 123)
expect(subject[:lfs_objects_synced_in_percentage]).to eq '48.05%' expect(subject[:lfs_objects_synced_in_percentage]).to eq '48.05%'
end end
end end
describe '#repositories_synced_in_percentage' do describe '#repositories_synced_in_percentage' do
it 'formats as percentage' do it 'formats as percentage' do
geo_node_status.assign_attributes(repositories_count: 10,
repositories_synced_count: 5,
repositories_failed_count: 0)
expect(subject[:repositories_synced_in_percentage]).to eq '50.00%' expect(subject[:repositories_synced_in_percentage]).to eq '50.00%'
end end
end end
describe '#namespaces' do
it 'returns empty array when full sync is active' do
expect(subject[:namespaces]).to be_empty
end
it 'returns array of namespace ids and paths for selective sync' do
namespace = create(:namespace)
geo_node_status.geo_node.namespaces << namespace
expect(subject[:namespaces]).not_to be_empty
expect(subject[:namespaces]).to be_an(Array)
expect(subject[:namespaces].first[:id]).to eq(namespace.id)
expect(subject[:namespaces].first[:path]).to eq(namespace.path)
end
end
end end
...@@ -121,5 +121,29 @@ describe Geo::NodeStatusFetchService, :geo do ...@@ -121,5 +121,29 @@ describe Geo::NodeStatusFetchService, :geo do
expect(status.status_message).to eq('This GitLab instance does not appear to be configured properly as a Geo node. Make sure the URLs are using the correct fully-qualified domain names.') expect(status.status_message).to eq('This GitLab instance does not appear to be configured properly as a Geo node. Make sure the URLs are using the correct fully-qualified domain names.')
end end
it 'returns the status from database if it could not fetch it' do
allow(described_class).to receive(:get).and_raise(Errno::ECONNREFUSED.new('bad connection'))
db_status = create(:geo_node_status, :healthy, geo_node: secondary)
status = subject.call(secondary)
expect(status.status_message).to eq('Connection refused - bad connection')
expect(status).not_to be_healthy
expect(status.attachments_count).to eq(db_status.attachments_count)
expect(status.attachments_failed_count).to eq(db_status.attachments_failed_count)
expect(status.attachments_synced_count).to eq(db_status.attachments_synced_count)
expect(status.lfs_objects_count).to eq(db_status.lfs_objects_count)
expect(status.lfs_objects_failed_count).to eq(db_status.lfs_objects_failed_count)
expect(status.lfs_objects_synced_count).to eq(db_status.lfs_objects_synced_count)
expect(status.repositories_count).to eq(db_status.repositories_count)
expect(status.repositories_synced_count).to eq(db_status.repositories_synced_count)
expect(status.repositories_failed_count).to eq(db_status.repositories_failed_count)
expect(status.last_event_id).to eq(db_status.last_event_id)
expect(status.last_event_timestamp).to eq(db_status.last_event_timestamp)
expect(status.cursor_last_event_id).to eq(db_status.cursor_last_event_id)
expect(status.cursor_last_event_timestamp).to eq(db_status.cursor_last_event_timestamp)
expect(status.last_successful_status_check_timestamp).to eq(db_status.last_successful_status_check_timestamp)
end
end end
end end
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