| // 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, |
| ); |
| } |
| } |