Skip to content

Commit 0fb8484

Browse files
author
Vidas P
committed
Implement live updates to agents table
This is transitional and somewhat inefficient implementation. Some things (action menu) are still rendered on the backend.
1 parent 4898d97 commit 0fb8484

File tree

13 files changed

+242
-122
lines changed

13 files changed

+242
-122
lines changed

app/assets/javascripts/diagram.js

Lines changed: 6 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ window.updateDiagram = function(options){
3131
});
3232
};
3333

34-
window.setupDiagram = function() {
34+
window.updateDiagramStatus = function(json) {
3535
var setBadge = function(agent_id, count) {
3636
var selector = `#b${agent_id}`;
3737
if (count > 0) {
@@ -41,18 +41,12 @@ window.setupDiagram = function() {
4141
$(selector).hide();
4242
}
4343
};
44-
var fetchStatus = function() {
45-
$.getJSON('/agents', function(json) {
46-
for(const agent_status of json) {
47-
let agent_id = agent_status.id;
48-
let messages_count = agent_status.messages_count;
49-
setBadge(agent_id, messages_count);
50-
}
51-
});
52-
setTimeout(fetchStatus, 2000);
53-
};
54-
fetchStatus();
44+
for(const agent of json) {
45+
setBadge(agent.id, agent.messages_count);
46+
}
47+
}
5548

49+
window.setupDiagram = function() {
5650
let updateVisibility = (show) => {
5751
const $toggle = $('#show-diagram-toggle');
5852
const $diagram = $('.overview-diagram');
Lines changed: 174 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,181 @@
11
this.AgentsIndexPage = class AgentsIndexPage {
22
constructor() {
3-
$("#agents-table").dataTable({
4-
order: [[1, 'asc']],
5-
pageLength: 100
3+
function updateDiagram(json) {
4+
window.updateDiagramStatus(json);
5+
}
6+
7+
function timeAgo(date) {
8+
if (!date) {
9+
return '';
10+
}
11+
var seconds = Math.floor(((new Date().getTime()/1000) - date)),
12+
interval = Math.floor(seconds / 31536000);
13+
14+
if (interval > 1) return interval + "y ago";
15+
16+
interval = Math.floor(seconds / 2592000);
17+
if (interval > 1) return interval + "m ago";
18+
19+
interval = Math.floor(seconds / 86400);
20+
if (interval >= 1) return interval + "d ago";
21+
22+
interval = Math.floor(seconds / 3600);
23+
if (interval >= 1) return interval + "h ago";
24+
25+
interval = Math.floor(seconds / 60);
26+
if (interval >= 1) return interval + "m ago";
27+
28+
return "<1m ago";
29+
}
30+
31+
var agentsTable = $("#agents-table").dataTable({
32+
paging: false,
33+
createdRow: function(row, data, index) {
34+
if (data.unavailable) {
35+
$(row).children().each( function(i, td) {
36+
if ($(td).find('.btn-group').length == 0) {
37+
$(td).addClass('agent-unavailable');
38+
}
39+
});
40+
}
41+
},
42+
columns: [
43+
{
44+
data: 'name',
45+
render: function (data, type, row) {
46+
if (type == 'display') {
47+
var workflow_links = row.workflows.map(workflow => {
48+
return `<a class="badge" style="color:${workflow.fg_color};background-color:${workflow.bg_color};" href="/workflows/${workflow.id}">${workflow.name}</a>`;
49+
});
50+
var agentUrl = `/agents/${row.id}`;
51+
var workflow_id = workflowId();
52+
if (workflow_id) {
53+
agentUrl = `${agentUrl}?workflow_id=${workflow_id}`;
54+
}
55+
return `<a href="${agentUrl}">${data}</a><br/><span class='text-muted'>${row.human_type}</span><span>${workflow_links}</span>`;
56+
}
57+
return data;
58+
}
59+
},
60+
{ data: 'schedule' },
61+
{
62+
data: 'last_check_at',
63+
render: function(data, type, row) {
64+
if (type == 'display') {
65+
return timeAgo(data);
66+
}
67+
return data;
68+
}
69+
},
70+
{
71+
data: 'last_receive_at',
72+
render: function(data, type, row) {
73+
if (type == 'display') {
74+
return timeAgo(data);
75+
}
76+
return data;
77+
}
78+
},
79+
{
80+
data: 'last_message_at',
81+
render: function(data, type, row) {
82+
if (type == 'display') {
83+
return timeAgo(data);
84+
}
85+
return data;
86+
}
87+
},
88+
{
89+
data: 'messages_count',
90+
render: function(data, type, row) {
91+
if (type == 'display') {
92+
return `<a href="/agents/${row.id}?tab=messages">${data}</a>`;
93+
}
94+
return data;
95+
}
96+
},
97+
{
98+
data: 'working',
99+
render: function(data, type, row) {
100+
if (type == 'display') {
101+
if (data == true) {
102+
return '<span class="badge badge-success">Yes</span>';
103+
} else {
104+
return '<span class="badge badge-danger">No</span>';
105+
}
106+
}
107+
return data;
108+
}
109+
},
110+
{
111+
data: 'action_menu',
112+
render: function(data, type, row) {
113+
if (type == 'display') {
114+
return `<div class="btn-group btn-group-sm"><button type="button" ` +
115+
`class="btn btn-primary btn-sm dropdown dropdown-toggle" `+
116+
`data-toggle="dropdown"><i class="fa fa-th-list"></i> Actions <span class="caret"></span></button>${data}</div>`;
117+
}
118+
return data;
119+
}
120+
},
121+
]
122+
});
123+
124+
agentsTable.on('xhr.dt', function(e, settings, json, xhr) {
125+
if (json) {
126+
updateDiagram(json);
127+
}
128+
});
129+
130+
// Check if any pop-ups are open.
131+
function canUpdate() {
132+
if ($('#agents-table .dropdown-menu.show').length > 0) {
133+
return false;
134+
}
135+
if ($('.confirm-agent.modal.show').length > 0) {
136+
return false;
137+
}
138+
return true;
139+
}
140+
141+
function updateTable(json) {
142+
if (canUpdate()) {
143+
var table = agentsTable.api();
144+
table.clear();
145+
table.rows.add(json);
146+
table.draw();
147+
}
148+
}
149+
150+
function workflowId() {
151+
return $('#agents-table').data('workflow_id');
152+
}
153+
154+
function loadData() {
155+
if (canUpdate()) {
156+
var url = '/agents/table.json';
157+
var workflow_id = workflowId();
158+
if (workflow_id) {
159+
url = `${url}?workflow_id=${workflow_id}`;
160+
}
161+
$.getJSON(url, function(json) {
162+
updateTable(json);
163+
updateDiagram(json);
164+
});
165+
}
166+
}
167+
loadData();
168+
169+
setInterval(function() {
170+
loadData();
171+
}, 2000);
172+
173+
// Close modal with remote forms after successful submit.
174+
$(document).on('ajax:success', '.modal', function(event) {
175+
$('.modal').modal('hide');
6176
});
7177
}
8178
};
9179

10-
$(() => Utils.registerPage(AgentsIndexPage, {forPathsMatching: /^agents/}));
180+
$(() => Utils.registerPage(AgentsIndexPage, {forPathsMatching: /^(agents|workflows)/}));
11181

app/assets/javascripts/pages/workflow-show-page.js

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,5 @@
11
this.WorkflowShowPage = class WorkflowShowPage {
22
constructor() {
3-
$("#agents-table").dataTable({
4-
order: [[1, 'asc']],
5-
paging: false
6-
});
7-
this.changeModalText();
83
}
94

105
changeModalText() {

app/controllers/agents_controller.rb

Lines changed: 37 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,27 +5,57 @@ class AgentsController < ApplicationController
55
before_action :set_workflow
66

77
def index
8-
@agents = current_user.agents.preload(:workflows, :controllers).includes(:receivers)
8+
@agents = current_user.agents.includes(:receivers)
99

1010
@agents = @agents.where(disabled: false) if show_only_enabled_agents?
1111

12+
# Trigger undefined agents handling.
13+
@agents.map { |agent| agent } if current_user.undefined_agent_types
14+
1215
respond_to do |format|
1316
format.html
1417
format.json { render json: @agents }
1518
end
1619
end
1720

18-
def status
19-
agents = current_user.agents
21+
def table
22+
agents = if @workflow
23+
@workflow.agents.preload(:workflows).includes(:receivers)
24+
else
25+
current_user.agents.preload(:workflows).includes(:receivers)
26+
end
2027

21-
statuses = agents.pluck(:id, :messages_count).map do |props|
28+
rows = agents.map do |agent|
2229
{
23-
id: props[0],
24-
messages_count: props[1]
30+
id: agent.id,
31+
unavailable: agent.unavailable?,
32+
name: agent.name,
33+
messages_count: agent.messages_count,
34+
schedule: agent.schedule&.humanize&.titleize || '',
35+
human_type: agent.human_type,
36+
workflows: agent.workflows.pluck(:id, :name, :tag_fg_color, :tag_bg_color).map do |row|
37+
{
38+
id: row[0],
39+
name: row[1],
40+
fg_color: row[2],
41+
bg_color: row[3]
42+
}
43+
end,
44+
last_check_at: agent.last_check_at&.to_time&.to_i,
45+
last_receive_at: agent.last_receive_at&.to_time&.to_i,
46+
last_message_at: agent.last_message_at&.to_time&.to_i,
47+
working: agent.working?,
48+
receivers: agent.receivers.map { |receiver| { id: receiver.id } },
49+
action_menu: AgentsController.render(template: 'agents/_action_menu.html',
50+
layout: false,
51+
locals: {
52+
right: true, agent: agent,
53+
workflow_id: @workflow&.id
54+
})
2555
}
2656
end
2757

28-
render json: statuses
58+
render json: rows
2959
end
3060

3161
def toggle_visibility

app/controllers/application_controller.rb

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,10 @@ class ApplicationController < ActionController::Base
77
rescue_from 'ActiveRecord::SubclassNotFound' do
88
@undefined_agent_types = current_user.undefined_agent_types
99

10-
render template: 'application/undefined_agents'
10+
respond_to do |format|
11+
format.html { render template: 'application/undefined_agents' }
12+
format.json { render json: [] }
13+
end
1114
end
1215

1316
protected

app/controllers/workflows_controller.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ def new
2525
def show
2626
@workflow = current_user.workflows.find(params[:id])
2727

28-
@agents = @workflow.agents.preload(:workflows, :controllers).includes(:receivers)
28+
@agents = @workflow.agents.includes(:receivers)
2929

3030
respond_to do |format|
3131
format.html

app/views/agents/_action_menu.html.erb

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,19 @@
11
<div class="dropdown-menu <%= local_assigns[:right] ? 'dropdown-menu-right' : '' %> dropdown-sm" role="menu">
22
<% if agent.can_be_scheduled? %>
3-
<%= link_to icon_tag('fa-sync-alt', class: 'color-success') + ' Run', run_agent_path(agent, params: { workflow_id: @workflow}), method: :post, tabindex: "-1", class: 'dropdown-item' %>
3+
<%= link_to icon_tag('fa-sync-alt', class: 'color-success') + ' Run', run_agent_path(agent, params: { workflow_id: workflow_id }), method: :post, tabindex: "-1", class: 'dropdown-item' %>
44
<% end %>
55

66
<% if agent.can_dry_run? %>
7-
<%= link_to icon_tag('fa-sync-alt') + ' Dry Run', '#', 'data-action-url' => agent_dry_runs_path(agent, params: { workflow_id: @workflow }), 'data-with-message-mode' => agent_dry_run_with_message_mode(agent), tabindex: "-1", onclick: "Utils.handleDryRunButton(this)", class: 'dropdown-item' %>
7+
<%= link_to icon_tag('fa-sync-alt') + ' Dry Run', '#', 'data-action-url' => agent_dry_runs_path(agent, params: { workflow_id: workflow_id }), 'data-with-message-mode' => agent_dry_run_with_message_mode(agent), tabindex: "-1", onclick: "Utils.handleDryRunButton(this)", class: 'dropdown-item' %>
88
<% end %>
99

10-
<%= link_to icon_tag('fa-eye') + ' Show'.html_safe, agent_path(agent, params: { workflow_id: @workflow }), class: 'dropdown-item' %>
10+
<%= link_to icon_tag('fa-eye') + ' Show'.html_safe, agent_path(agent, params: { workflow_id: workflow_id }), class: 'dropdown-item' %>
1111

1212
<div class="dropdown-divider"></div>
1313

14-
<%= link_to icon_tag('fa-edit') + ' Edit agent'.html_safe, edit_agent_path(agent, params: { workflow_id: @workflow }), class: 'dropdown-item' %>
14+
<%= link_to icon_tag('fa-edit') + ' Edit agent'.html_safe, edit_agent_path(agent, params: { workflow_id: workflow_id }), class: 'dropdown-item' %>
1515

16-
<%= link_to icon_tag('fa-copy') + ' Clone agent'.html_safe, new_agent_path(id: agent, params: { workflow_id: @workflow }), tabindex: "-1", class: 'dropdown-item' %>
16+
<%= link_to icon_tag('fa-copy') + ' Clone agent'.html_safe, new_agent_path(id: agent, params: { workflow_id: workflow_id }), tabindex: "-1", class: 'dropdown-item' %>
1717

1818
<%= link_to '#', 'data-toggle' => 'modal', 'data-target' => "#confirm-agent#{agent.id}", class: 'dropdown-item' do %>
1919
<% if agent.disabled? %>
@@ -34,10 +34,10 @@
3434
<div class="dropdown-divider"></div>
3535

3636
<% if agent.can_create_messages? && agent.messages_count > 0 %>
37-
<%= link_to icon_tag('fa-trash-alt') + ' Delete all messages', remove_messages_agent_path(agent, params: { workflow_id: @workflow }), method: :delete, data: {confirm: 'Are you sure you want to delete ALL emitted messages for this Agent?'}, tabindex: "-1", class: 'dropdown-item' %>
37+
<%= link_to icon_tag('fa-trash-alt') + ' Delete all messages', remove_messages_agent_path(agent, params: { workflow_id: workflow_id }), method: :delete, data: {confirm: 'Are you sure you want to delete ALL emitted messages for this Agent?'}, tabindex: "-1", class: 'dropdown-item' %>
3838
<% end %>
3939

40-
<%= link_to icon_tag('fa-times') + ' Delete agent', agent_path(agent, params: { workflow_id: @workflow }), method: :delete, data: { confirm: 'Are you sure that you want to permanently delete this Agent?' }, tabindex: "-1", class: 'dropdown-item' %>
40+
<%= link_to icon_tag('fa-times') + ' Delete agent', agent_path(agent, params: { workflow_id: workflow_id }), method: :delete, data: { confirm: 'Are you sure that you want to permanently delete this Agent?' }, tabindex: "-1", class: 'dropdown-item' %>
4141
</div>
4242

4343
<div id="confirm-agent<%= agent.id %>" class="confirm-agent modal fade" tabindex="-1" role="dialog" aria-labelledby="confirmAgentLabel" aria-hidden="true">
@@ -51,7 +51,7 @@
5151
<p><% if agent.disabled? %>Enable<% else %>Disable<% end %> &quot;<%= agent.name %>&quot;?</p>
5252
</div>
5353
<div class="modal-footer">
54-
<%= form_for(agent, as: :agent, url: agent_path(agent, params: { workflow_id: @workflow }), method: 'PUT') do |f| %>
54+
<%= form_for(agent, as: :agent, url: agent_path(agent, params: { workflow_id: workflow_id }), method: 'PUT', remote: true) do |f| %>
5555
<% if agent.disabled && agent.can_receive_messages? %>
5656
<div class="form-group">
5757
<%= f.check_box :drop_pending_messages %>

0 commit comments

Comments
 (0)