[conductor] Added a generic tooltip widget (#92187)
* Added a generic tooltip widget * added the header
This commit is contained in:
parent
ce4d635aeb
commit
ec429e0196
30
dev/conductor/ui/lib/widgets/common/tooltip.dart
Normal file
30
dev/conductor/ui/lib/widgets/common/tooltip.dart
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
// Copyright 2014 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 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
/// Displays detailed info message in a tooltip widget.
|
||||||
|
class InfoTooltip extends StatelessWidget {
|
||||||
|
const InfoTooltip({
|
||||||
|
Key? key,
|
||||||
|
required this.tooltipName,
|
||||||
|
required this.tooltipMessage,
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
|
final String tooltipName;
|
||||||
|
final String tooltipMessage;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Tooltip(
|
||||||
|
padding: const EdgeInsets.all(10.0),
|
||||||
|
message: tooltipMessage,
|
||||||
|
child: Icon(
|
||||||
|
Icons.info,
|
||||||
|
size: 16.0,
|
||||||
|
key: Key('${tooltipName}Tooltip'),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -6,6 +6,8 @@ import 'package:conductor_core/conductor_core.dart';
|
|||||||
import 'package:conductor_core/proto.dart' as pb;
|
import 'package:conductor_core/proto.dart' as pb;
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
import 'common/tooltip.dart';
|
||||||
|
|
||||||
/// Displays the current conductor state.
|
/// Displays the current conductor state.
|
||||||
class ConductorStatus extends StatefulWidget {
|
class ConductorStatus extends StatefulWidget {
|
||||||
const ConductorStatus({
|
const ConductorStatus({
|
||||||
@ -142,44 +144,6 @@ class ConductorStatusState extends State<ConductorStatus> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Displays explanations for each status type as a tooltip.
|
|
||||||
class StatusTooltip extends StatefulWidget {
|
|
||||||
const StatusTooltip({
|
|
||||||
Key? key,
|
|
||||||
this.engineOrFramework,
|
|
||||||
}) : super(key: key);
|
|
||||||
|
|
||||||
final String? engineOrFramework;
|
|
||||||
|
|
||||||
@override
|
|
||||||
State<StatusTooltip> createState() => _StatusTooltipState();
|
|
||||||
}
|
|
||||||
|
|
||||||
class _StatusTooltipState extends State<StatusTooltip> {
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return Row(
|
|
||||||
children: <Widget>[
|
|
||||||
const Text('Status'),
|
|
||||||
const SizedBox(width: 10.0),
|
|
||||||
Tooltip(
|
|
||||||
padding: const EdgeInsets.all(10.0),
|
|
||||||
message: '''
|
|
||||||
PENDING: The cherrypick has not yet been applied.
|
|
||||||
PENDING_WITH_CONFLICT: The cherrypick has not been applied and will require manual resolution.
|
|
||||||
COMPLETED: The cherrypick has been successfully applied to the local checkout.
|
|
||||||
ABANDONED: The cherrypick will NOT be applied in this release.''',
|
|
||||||
child: Icon(
|
|
||||||
Icons.info,
|
|
||||||
size: 16.0,
|
|
||||||
key: Key('${widget.engineOrFramework}ConductorStatusTooltip'),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Widget for showing the engine and framework cherrypicks applied to the current release.
|
/// Widget for showing the engine and framework cherrypicks applied to the current release.
|
||||||
///
|
///
|
||||||
/// Shows the cherrypicks' SHA and status in two separate table DataRow cells.
|
/// Shows the cherrypicks' SHA and status in two separate table DataRow cells.
|
||||||
@ -210,7 +174,22 @@ class CherrypickTableState extends State<CherrypickTable> {
|
|||||||
decoration: BoxDecoration(border: Border.all(color: Colors.grey)),
|
decoration: BoxDecoration(border: Border.all(color: Colors.grey)),
|
||||||
columns: <DataColumn>[
|
columns: <DataColumn>[
|
||||||
DataColumn(label: Text('${widget.engineOrFramework == 'engine' ? 'Engine' : 'Framework'} Cherrypicks')),
|
DataColumn(label: Text('${widget.engineOrFramework == 'engine' ? 'Engine' : 'Framework'} Cherrypicks')),
|
||||||
DataColumn(label: StatusTooltip(engineOrFramework: widget.engineOrFramework)),
|
DataColumn(
|
||||||
|
label: Row(
|
||||||
|
children: <Widget>[
|
||||||
|
const Text('Status'),
|
||||||
|
const SizedBox(width: 10.0),
|
||||||
|
InfoTooltip(
|
||||||
|
tooltipName: widget.engineOrFramework,
|
||||||
|
tooltipMessage: '''
|
||||||
|
PENDING: The cherrypick has not yet been applied.
|
||||||
|
PENDING_WITH_CONFLICT: The cherrypick has not been applied and will require manual resolution.
|
||||||
|
COMPLETED: The cherrypick has been successfully applied to the local checkout.
|
||||||
|
ABANDONED: The cherrypick will NOT be applied in this release.''',
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
rows: cherrypicks.map((Map<String, String> cherrypick) {
|
rows: cherrypicks.map((Map<String, String> cherrypick) {
|
||||||
return DataRow(
|
return DataRow(
|
||||||
|
@ -4,6 +4,8 @@
|
|||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
import 'common/tooltip.dart';
|
||||||
|
|
||||||
/// Displays all substeps related to the 1st step.
|
/// Displays all substeps related to the 1st step.
|
||||||
///
|
///
|
||||||
/// Uses input fields and dropdowns to capture all the parameters of the conductor start command.
|
/// Uses input fields and dropdowns to capture all the parameters of the conductor start command.
|
||||||
@ -162,6 +164,21 @@ class CheckboxListTileDropdown extends StatelessWidget {
|
|||||||
CreateReleaseSubsteps.substepTitles[index],
|
CreateReleaseSubsteps.substepTitles[index],
|
||||||
style: Theme.of(context).textTheme.subtitle1!.copyWith(color: Colors.grey[700]),
|
style: Theme.of(context).textTheme.subtitle1!.copyWith(color: Colors.grey[700]),
|
||||||
),
|
),
|
||||||
|
// Only add a tooltip for the increment dropdown
|
||||||
|
if (index == 7)
|
||||||
|
const Padding(
|
||||||
|
padding: EdgeInsets.fromLTRB(10.0, 0, 0, 0),
|
||||||
|
child: InfoTooltip(
|
||||||
|
tooltipName: 'ReleaseIncrement',
|
||||||
|
// m: has one less space than the other lines, because otherwise,
|
||||||
|
// it would display on the app one more space than the other lines
|
||||||
|
tooltipMessage: '''
|
||||||
|
m: Indicates a standard dev release.
|
||||||
|
n: Indicates a hotfix to a dev or beta release.
|
||||||
|
y: Indicates the first dev release after a beta release.
|
||||||
|
z: Indicates a hotfix to a stable release.''',
|
||||||
|
),
|
||||||
|
),
|
||||||
const SizedBox(width: 20.0),
|
const SizedBox(width: 20.0),
|
||||||
DropdownButton<String>(
|
DropdownButton<String>(
|
||||||
hint: const Text('-'), // Dropdown initially displays the hint when no option is selected.
|
hint: const Text('-'), // Dropdown initially displays the hint when no option is selected.
|
||||||
|
44
dev/conductor/ui/test/widgets/common/tooltip_test.dart
Normal file
44
dev/conductor/ui/test/widgets/common/tooltip_test.dart
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
// Copyright 2014 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 'package:conductor_ui/widgets/common/tooltip.dart';
|
||||||
|
import 'package:flutter/gestures.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
testWidgets('When the cursor hovers over the tooltip, it displays the message.', (WidgetTester tester) async {
|
||||||
|
await tester.pumpWidget(
|
||||||
|
StatefulBuilder(
|
||||||
|
builder: (BuildContext context, StateSetter setState) {
|
||||||
|
return MaterialApp(
|
||||||
|
home: Material(
|
||||||
|
child: Column(
|
||||||
|
children: const <Widget>[
|
||||||
|
InfoTooltip(tooltipName: 'tooltipTest', tooltipMessage: 'tooltipTestMessage'),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(find.byType(InfoTooltip), findsOneWidget);
|
||||||
|
|
||||||
|
final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse);
|
||||||
|
addTearDown(gesture.removePointer);
|
||||||
|
await gesture.addPointer(location: Offset.zero);
|
||||||
|
|
||||||
|
/// Tests if the tooltip is displaying the message upon cursor hovering.
|
||||||
|
///
|
||||||
|
/// Before hovering, the message is not found.
|
||||||
|
/// When the cursor hovers over the icon, the message is displayed and found.
|
||||||
|
expect(find.textContaining('tooltipTestMessage'), findsNothing);
|
||||||
|
await tester.pump();
|
||||||
|
await gesture.moveTo(tester.getCenter(find.byKey(const Key('tooltipTestTooltip'))));
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
expect(find.textContaining('tooltipTestMessage'), findsOneWidget);
|
||||||
|
});
|
||||||
|
}
|
@ -5,7 +5,6 @@
|
|||||||
import 'package:conductor_core/conductor_core.dart';
|
import 'package:conductor_core/conductor_core.dart';
|
||||||
import 'package:conductor_core/proto.dart' as pb;
|
import 'package:conductor_core/proto.dart' as pb;
|
||||||
import 'package:conductor_ui/widgets/conductor_status.dart';
|
import 'package:conductor_ui/widgets/conductor_status.dart';
|
||||||
import 'package:flutter/gestures.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
|
||||||
@ -119,20 +118,6 @@ void main() {
|
|||||||
expect(find.text(engineCherrypick1), findsOneWidget);
|
expect(find.text(engineCherrypick1), findsOneWidget);
|
||||||
expect(find.text(engineCherrypick2), findsOneWidget);
|
expect(find.text(engineCherrypick2), findsOneWidget);
|
||||||
expect(find.text(frameworkCherrypick), findsOneWidget);
|
expect(find.text(frameworkCherrypick), findsOneWidget);
|
||||||
|
|
||||||
final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse);
|
|
||||||
addTearDown(gesture.removePointer);
|
|
||||||
await gesture.addPointer(location: Offset.zero);
|
|
||||||
|
|
||||||
/// Tests the tooltip is displaying status explanations upon cursor hovering.
|
|
||||||
///
|
|
||||||
/// Before hovering, status explanations are not found.
|
|
||||||
/// When the cursor hovers over the info icon, the explanations are displayed and found.
|
|
||||||
expect(find.textContaining('PENDING: The cherrypick has not yet been applied.'), findsNothing);
|
|
||||||
await tester.pump();
|
|
||||||
await gesture.moveTo(tester.getCenter(find.byKey(const Key('engineConductorStatusTooltip'))));
|
|
||||||
await tester.pumpAndSettle();
|
|
||||||
expect(find.textContaining('PENDING: The cherrypick has not yet been applied.'), findsOneWidget);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
testWidgets('Conductor_status displays correct status with a null state file except a releaseChannel',
|
testWidgets('Conductor_status displays correct status with a null state file except a releaseChannel',
|
||||||
@ -166,20 +151,6 @@ void main() {
|
|||||||
}
|
}
|
||||||
expect(find.text(releaseChannel), findsOneWidget);
|
expect(find.text(releaseChannel), findsOneWidget);
|
||||||
expect(find.text('Unknown'), findsNWidgets(11));
|
expect(find.text('Unknown'), findsNWidgets(11));
|
||||||
|
|
||||||
final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse);
|
|
||||||
addTearDown(gesture.removePointer);
|
|
||||||
await gesture.addPointer(location: Offset.zero);
|
|
||||||
|
|
||||||
/// Tests the tooltip is displaying status explanations upon cursor hovering.
|
|
||||||
///
|
|
||||||
/// Before hovering, status explanations are not found.
|
|
||||||
/// When the cursor hovers over the info icon, the explanations are displayed and found.
|
|
||||||
expect(find.textContaining('PENDING: The cherrypick has not yet been applied.'), findsNothing);
|
|
||||||
await tester.pump();
|
|
||||||
await gesture.moveTo(tester.getCenter(find.byKey(const Key('engineConductorStatusTooltip'))));
|
|
||||||
await tester.pumpAndSettle();
|
|
||||||
expect(find.textContaining('PENDING: The cherrypick has not yet been applied.'), findsOneWidget);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
testWidgets('Repo Info section displays corresponding info in a dropdown fashion', (WidgetTester tester) async {
|
testWidgets('Repo Info section displays corresponding info in a dropdown fashion', (WidgetTester tester) async {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user