blob: c48f452724e258de91da84b71429f100cd710bf7 [file] [log] [blame]
// Copyright 2019 The Flutter 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 'dart:html';
import 'package:intl/intl.dart';
import 'package:flutter_web/material.dart';
import '../models/build_status.dart';
import '../models/status_page_status.dart';
import '../models/providers.dart';
const double _kAvatarRadius = 36.0;
class InfrastructureDetails extends StatelessWidget {
const InfrastructureDetails();
@override
Widget build(BuildContext context) {
return Theme(
data: Theme.of(context).copyWith(
textTheme: Theme.of(context).textTheme.apply(fontSizeFactor: 1.8),
chipTheme: ChipTheme.of(context).copyWith(
labelPadding: const EdgeInsets.fromLTRB(10.0, 3.0, 20.0, 3.0),
labelStyle:
ChipTheme.of(context).labelStyle.apply(fontSizeFactor: 1.8)),
),
child: ModelBinding<BuildStatus>(
initialModel: const BuildStatus(),
child: RefreshBuildStatus(
child: Row(
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
Expanded(
child: Column(children: const <Widget>[
BuildStatusWidget(),
ModelBinding<StatusPageStatus>(
initialModel: StatusPageStatus(),
child: RefreshGitHubStatus(
child: StatusPageWidget(
name: 'GitHub',
serviceIcon: Icons.compare_arrows,
url: 'https://www.githubstatus.com',
))),
ModelBinding<StatusPageStatus>(
initialModel: StatusPageStatus(),
child: RefreshCoverallsStatus(
child: StatusPageWidget(
name: 'Coveralls',
serviceIcon: Icons.code,
url: 'https://status.coveralls.io/',
)))
]),
),
const Expanded(
child: FailingAgentWidget(),
),
const Expanded(
child: CommitResultsWidget(),
)
]),
),
),
);
}
}
class StatusPageWidget extends StatelessWidget {
const StatusPageWidget(
{@required this.name, @required this.serviceIcon, @required this.url});
final String name;
final IconData serviceIcon;
final String url;
@override
Widget build(BuildContext context) {
final StatusPageStatus githubStatus =
ModelBinding.of<StatusPageStatus>(context);
IconData icon;
Color backgroundColor;
switch (githubStatus.indicator) {
case 'none':
icon = Icons.check;
backgroundColor = Colors.green;
break;
case 'minor':
icon = Icons.warning;
backgroundColor = Colors.amberAccent;
break;
case 'major':
icon = Icons.error;
backgroundColor = Colors.orangeAccent;
break;
case 'critical':
case 'maintenance':
icon = Icons.error;
backgroundColor = Colors.redAccent;
break;
default:
icon = Icons.help_outline;
backgroundColor = Colors.grey;
}
return ListTile(
leading: CircleAvatar(
child: Icon(serviceIcon),
radius: _kAvatarRadius,
),
title: Text(name),
subtitle: Semantics(
child: Align(
alignment: AlignmentDirectional.centerStart,
child: Chip(
avatar: Icon(icon),
backgroundColor: backgroundColor,
label: Text(githubStatus.status ?? 'Unknown')),
),
),
onTap: () => window.open(url, '_blank'));
}
}
class BuildStatusWidget extends StatelessWidget {
const BuildStatusWidget();
@override
Widget build(BuildContext context) {
final BuildStatus status = ModelBinding.of<BuildStatus>(context);
IconData icon;
Color backgroundColor;
switch (status.anticipatedBuildStatus) {
case 'Succeeded':
icon = Icons.check;
backgroundColor = Colors.green;
break;
case 'Build Will Fail':
case 'Failed':
icon = Icons.error;
backgroundColor = Colors.redAccent;
break;
default:
icon = Icons.help_outline;
backgroundColor = Colors.grey;
}
return ListTile(
leading: const CircleAvatar(
child: Icon(Icons.devices),
radius: _kAvatarRadius,
),
title: const Text('Last Flutter Commit'),
subtitle: Semantics(
child: Align(
alignment: AlignmentDirectional.centerStart,
child: Chip(
avatar: Icon(icon),
backgroundColor: backgroundColor,
label: Text(status.anticipatedBuildStatus ?? 'Unknown')),
),
hint: 'Build Status',
),
onTap: () => window.open('/build.html', '_blank'));
}
}
class FailingAgentWidget extends StatelessWidget {
const FailingAgentWidget();
@override
Widget build(BuildContext context) {
final BuildStatus status = ModelBinding.of<BuildStatus>(context);
final List<String> failingAgents = status.failingAgents;
if (failingAgents.isEmpty) {
return const SizedBox();
}
return Column(children: <Widget>[
ListTile(
title: Text('Failing Agents',
style: Theme.of(context).textTheme.headline.copyWith(
color: Theme.of(context).primaryColor,
)),
),
for (String agentName in failingAgents)
ListTile(
leading: const CircleAvatar(
child: Icon(Icons.desktop_windows),
radius: _kAvatarRadius,
foregroundColor: Colors.white,
backgroundColor: Colors.redAccent,
),
title: Text(agentName),
onTap: () => window.open('/build.html', '_blank'))
]);
}
}
class CommitResultsWidget extends StatelessWidget {
const CommitResultsWidget();
@override
Widget build(BuildContext context) {
final BuildStatus status = ModelBinding.of<BuildStatus>(context);
final List<CommitTestResult> commitTestResults = status.commitTestResults;
if (commitTestResults.isEmpty) {
return const SizedBox();
}
return Column(children: <Widget>[
for (CommitTestResult commitTestResult in commitTestResults)
_CommitResultWidget(commitTestResult: commitTestResult)
]);
}
}
class _CommitResultWidget extends StatelessWidget {
const _CommitResultWidget({this.commitTestResult});
final CommitTestResult commitTestResult;
@override
Widget build(BuildContext context) {
Widget icon;
Color backgroundColor;
if (commitTestResult.failedTestCount > 0) {
icon = const Icon(Icons.error);
backgroundColor = Colors.redAccent;
} else if (commitTestResult.inProgressTestCount > 0) {
icon = const _PendingIcon(child: const Icon(Icons.sync));
backgroundColor = Colors.grey[600];
} else {
icon = const Icon(Icons.check);
backgroundColor = Colors.green;
}
String displaySha = commitTestResult.sha;
if (displaySha != null && displaySha.length >= 6) {
displaySha = displaySha.substring(0, 6);
}
return ListTile(
leading: CircleAvatar(
child: icon,
radius: _kAvatarRadius,
foregroundColor: Colors.white,
backgroundColor: backgroundColor,
),
title: Text(
'[$displaySha] ${DateFormat.jm().format(commitTestResult.createDateTime)}'),
subtitle: RichText(
text: TextSpan(children: <TextSpan>[
if (commitTestResult.failedTestCount > 0)
TextSpan(
text:
'Fail: ${commitTestResult.failingTests.length == 1 ? commitTestResult.failingTests.first : commitTestResult.failedTestCount}\n',
style: const TextStyle(color: Colors.redAccent)),
if (commitTestResult.failedFlakyTestCount > 0)
TextSpan(
text: 'Flake: ${commitTestResult.failedFlakyTestCount}\n',
style: const TextStyle(color: Colors.orange)),
if (commitTestResult.inProgressTestCount > 0)
TextSpan(
text: 'In progress: ${commitTestResult.inProgressTestCount}',
style: const TextStyle(color: Colors.grey)),
], style: Theme.of(context).textTheme.subtitle),
),
trailing: CircleAvatar(
child: Image.network(commitTestResult.avatarImageURL),
radius: _kAvatarRadius,
),
isThreeLine: true,
onTap: () => window.open('/build.html', '_blank'));
}
}
class _PendingIcon extends StatefulWidget {
const _PendingIcon({@required this.child});
final Widget child;
@override
_PendingIconState createState() => _PendingIconState();
}
class _PendingIconState extends State<_PendingIcon>
with SingleTickerProviderStateMixin {
AnimationController _controller;
Animation<double> _opacity;
@override
void initState() {
_controller =
AnimationController(vsync: this, duration: const Duration(seconds: 2));
_opacity = Tween<double>(begin: 1.0, end: 0.0).animate(_controller);
_controller.repeat(reverse: true);
super.initState();
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return FadeTransition(
opacity: _opacity,
child: widget.child,
);
}
}