blob: 460299880da720473bddb6bcbb0d968773479bca [file] [log] [blame]
// Copyright (c) 2020, the Dart project authors. Please see the AUTHORS file
// for details. 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:clippy/browser.dart' as clippy;
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'dart:html' as html;
import 'filter.dart';
import 'query.dart';
import 'results.dart';
void main() {
runApp(Providers());
}
class CurrentResultsApp extends StatelessWidget {
const CurrentResultsApp({Key key}) : super(key: key);
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Current Results',
theme: ThemeData(
primarySwatch: Colors.blue,
visualDensity: VisualDensity.compact,
),
initialRoute: '/',
onGenerateRoute: (RouteSettings settings) {
final parameters = settings.name.substring(1).split('&');
final terms = parameters
.firstWhere((parameter) => parameter.startsWith('filter='),
orElse: () => 'filter=')
.split('=')[1];
final filter = Filter(terms);
final showAll = parameters.contains('showAll');
final failures = parameters.contains('flaky');
final tab = showAll
? 0
: failures
? 2
: 1;
return NoTransitionPageRoute(
builder: (context) {
Provider.of<QueryResults>(context, listen: false).fetch(filter);
// Not allowed to set state of tab controller in this builder.
WidgetsBinding.instance.addPostFrameCallback((_) {
Provider.of<TabController>(context, listen: false).index = tab;
});
return const CurrentResultsScaffold();
},
settings: settings,
maintainState: false);
},
);
}
}
/// Provides access to an QueryResults object which notifies when new
/// results are fetched, a DefaultTabController widget used by the
/// TabBar, and to the TabController object created by that
/// DefaultTabController widget.
class Providers extends StatelessWidget {
const Providers({Key key}) : super(key: key);
@override
Widget build(BuildContext context) {
return ChangeNotifierProvider(
create: (context) => QueryResults(),
child: DefaultTabController(
length: 3,
initialIndex: 1,
child: Builder(
// ChangeNotifierProvider.value in a Builder is needed to make
// the TabController available for widgets to observe.
builder: (context) => ChangeNotifierProvider<TabController>.value(
value: DefaultTabController.of(context),
child: const CurrentResultsApp(),
),
),
),
);
}
}
class CurrentResultsScaffold extends StatelessWidget {
const CurrentResultsScaffold({Key key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Align(
alignment: Alignment.topLeft,
child: Scaffold(
appBar: AppBar(
leading: Align(
alignment: Alignment.center,
child: Image.asset('assets/dart_64.png', width: 40.0, height: 40.0),
),
title: const Text('Current Results',
style: TextStyle(
fontSize: 24.0, color: Color.fromARGB(255, 63, 81, 181))),
backgroundColor: Colors.white,
bottom: TabBar(
tabs: [
Tab(text: 'ALL'),
Tab(text: 'FAILURES'),
Tab(text: 'FLAKY'),
],
indicatorColor: Color.fromARGB(255, 63, 81, 181),
labelColor: Color.fromARGB(255, 63, 81, 181),
onTap: (tab) {
// We cannot compare to the previous value, it is gone.
pushRoute(context, tab: tab);
},
),
),
persistentFooterButtons: [
const TestSummary(),
const ResultsSummary(),
const ApiPortalLink(),
const JsonLink(),
const TextPopup(),
],
body: Align(
alignment: Alignment.topCenter,
child: Container(
constraints: BoxConstraints(maxWidth: 1000.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
FilterUI(),
Divider(
color: Colors.grey[300],
height: 20,
thickness: 2,
),
Expanded(
child: ResultsPanel(),
),
],
),
),
),
),
);
}
}
class ApiPortalLink extends StatelessWidget {
const ApiPortalLink();
@override
Widget build(BuildContext context) {
return TextButton(
child: Text('API portal'),
onPressed: () => html.window.open(
'https://endpointsportal.dart-ci-staging.cloud.goog'
'/docs/current-results-qvyo5rktwa-uc.a.run.app/g'
'/routes/v1/results/get',
'_blank'),
);
}
}
class JsonLink extends StatelessWidget {
const JsonLink();
@override
Widget build(BuildContext context) {
return Consumer<QueryResults>(
builder: (context, results, child) {
return TextButton(
child: Text('json'),
onPressed: () => html.window.open(
Uri.https(apiHost, 'v1/results', {
'filter': results.filter.terms.join(','),
'pageSize': '4000'
}).toString(),
'_blank'),
);
},
);
}
}
class TextPopup extends StatelessWidget {
const TextPopup();
@override
Widget build(BuildContext context) {
return Consumer<QueryResults>(
builder: (context, QueryResults results, child) {
return TextButton(
child: Text('text'),
onPressed: () => showDialog(
context: context,
builder: (BuildContext context) {
final text = [resultTextHeader]
.followedBy(results.names
.expand((name) => results.grouped[name].values)
.expand((list) => list)
.map(resultAsCommaSeparated))
.join('\n');
return AlertDialog(
title: Text('Results query as text'),
content: SelectableText(text),
actions: <Widget>[
FlatButton(
child: Text('Copy and dismiss'),
onPressed: () {
clippy.write(text);
Navigator.of(context).pop();
},
),
FlatButton(
child: Text('Dismiss'),
onPressed: () => Navigator.of(context).pop(),
),
],
);
},
),
);
},
);
}
}
class NoTransitionPageRoute extends MaterialPageRoute {
NoTransitionPageRoute({
@required WidgetBuilder builder,
RouteSettings settings,
bool maintainState = true,
}) : super(
builder: builder,
settings: settings,
maintainState: maintainState,
);
@override
Widget buildTransitions(BuildContext context, Animation<double> animation,
Animation<double> secondaryAnimation, Widget child) {
return child;
}
}
void pushRoute(context, {Iterable<String> terms, int tab}) {
if (terms == null && tab == null) {
throw ArgumentError('pushRoute calls must have a named argument');
}
if (tab == null) {
tab = Provider.of<TabController>(context, listen: false).index;
}
if (terms == null) {
terms = Provider.of<QueryResults>(context, listen: false).filter.terms;
}
final tabItems = [if (tab == 0) 'showAll', if (tab == 2) 'flaky'];
Navigator.pushNamed(
context,
[
'/filter=${terms.join(',')}',
...tabItems,
].join('&'),
);
}