blob: d2951b3faa7f0db537e330aab6b21bfecb12cf31 [file] [log] [blame]
// Copyright (c) 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:flutter/material.dart';
import 'package:cocoon_service/protos.dart' show Agent;
import 'agent_health_details.dart';
import 'agent_tile.dart';
import 'state/agent.dart';
/// Shows [List<Agent>] that have [Agent.agentId] that contains [agentFilter] in
/// a ListView of [AgentTile].
///
/// Sorts this list to show unhealthy agents first.
class AgentList extends StatefulWidget {
const AgentList({
this.agents,
this.agentState,
this.agentFilter,
@visibleForTesting this.insertKeys = false,
});
/// All known agents that can be shown.
final List<Agent> agents;
final AgentState agentState;
/// Search term to filter the agents from [agentState] and show only those
/// that contain this term.
///
/// This is set either:
/// 1. Route argument set when being redirected from the build dashboard
/// to view a specific agent that ran a task.
/// 2. In the search bar of this widget.
final String agentFilter;
/// When true, will set a key for the [AgentTile] that is composed of its
/// position in the list and the agent id.
@visibleForTesting
final bool insertKeys;
@override
_AgentListState createState() => _AgentListState();
}
class _AgentListState extends State<AgentList> {
/// Controller for filtering the agents, acting as a search bar.
TextEditingController filterAgentsController = TextEditingController();
@override
void initState() {
super.initState();
/// When redirected from the build dashboard, display a specific agent.
/// Updates the search bar to show [agentFilter].
if (widget.agentFilter != null && widget.agentFilter.isNotEmpty) {
filterAgentsController.text = widget.agentFilter;
}
}
@override
Widget build(BuildContext context) {
if (widget.agents.isEmpty) {
return const Center(
child: SizedBox(
height: 100,
width: 100,
child: CircularProgressIndicator(),
),
);
}
final List<FullAgent> fullAgents = widget.agents
.map((Agent agent) => FullAgent(agent, AgentHealthDetails(agent)))
.toList()
// TODO(chillers): Remove sort once backend handles sorting. https://github.com/flutter/flutter/issues/48249
..sort();
List<FullAgent> filteredAgents =
filterAgents(fullAgents, filterAgentsController.text);
return Column(
children: <Widget>[
// Padding for the search bar
Container(height: 25),
TextField(
onChanged: (String value) {
setState(() {
filteredAgents = filterAgents(fullAgents, value);
});
},
controller: filterAgentsController,
decoration: InputDecoration(
labelText: 'Filter',
hintText: 'Filter',
prefixIcon: Icon(Icons.search),
border: const OutlineInputBorder(
borderRadius: BorderRadius.all(
Radius.circular(25.0),
),
),
),
),
Expanded(
child: ListView(
children: List<AgentTile>.generate(filteredAgents.length, (int i) {
return AgentTile(
key: widget.insertKeys
? Key('$i-${filteredAgents[i].agent.agentId}')
: null,
fullAgent: filteredAgents[i],
agentState: widget.agentState,
);
}),
),
),
],
);
}
/// Creates a new [List<FullAgent>] of only the agents that have an [agentId]
/// that contains [filter].
///
/// If filter is empty, the original list of agents.
List<FullAgent> filterAgents(List<FullAgent> agents, String filter) {
if (filter.isEmpty) {
return agents;
}
return agents
.where(
(FullAgent fullAgent) => fullAgent.agent.agentId.contains(filter))
.toList();
}
}
/// A wrapper class to help not have to generate [AgentHealthDetails] each time
/// it is needed.
///
/// Sorts to show unhealthy agents before healthy agents, with those groups
/// being sorted alphabetically.
class FullAgent implements Comparable<FullAgent> {
const FullAgent(this.agent, this.healthDetails);
final Agent agent;
final AgentHealthDetails healthDetails;
@override
int compareTo(FullAgent other) {
if (healthDetails.isHealthy && other.healthDetails.isHealthy) {
return agent.agentId.compareTo(other.agent.agentId);
} else if (healthDetails.isHealthy) {
return 1;
}
return -1;
}
}