[dashboard] Add copy button for commit shas (#1952)

diff --git a/dashboard/lib/widgets/commit_box.dart b/dashboard/lib/widgets/commit_box.dart
index 5e21a18..60cc949 100644
--- a/dashboard/lib/widgets/commit_box.dart
+++ b/dashboard/lib/widgets/commit_box.dart
@@ -2,8 +2,10 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-import 'package:flutter/gestures.dart';
+import 'dart:async';
+
 import 'package:flutter/material.dart';
+import 'package:flutter/services.dart';
 import 'package:url_launcher/url_launcher.dart';
 
 import '../model/commit.pb.dart';
@@ -124,9 +126,21 @@
                             child: AnimatedDefaultTextStyle(
                               style: theme.textTheme.subtitle1!,
                               duration: kThemeChangeDuration,
-                              child: Hyperlink(
-                                text: commit.sha.substring(0, 7),
-                                onPressed: _openGithub,
+                              child: Row(
+                                children: <Widget>[
+                                  Hyperlink(
+                                    text: commit.sha.substring(0, 7),
+                                    onPressed: _openGithub,
+                                  ),
+                                  IconButton(
+                                    icon: const Icon(Icons.copy),
+                                    onPressed: () => unawaited(
+                                      Clipboard.setData(
+                                        ClipboardData(text: commit.sha),
+                                      ),
+                                    ),
+                                  ),
+                                ],
                               ),
                             ),
                           ),
diff --git a/dashboard/test/goldens/build_dashboard.defaultPropertySheet.dark.png b/dashboard/test/goldens/build_dashboard.defaultPropertySheet.dark.png
index 16d5e15..4c3b5a2 100644
--- a/dashboard/test/goldens/build_dashboard.defaultPropertySheet.dark.png
+++ b/dashboard/test/goldens/build_dashboard.defaultPropertySheet.dark.png
Binary files differ
diff --git a/dashboard/test/goldens/build_dashboard.defaultPropertySheet.png b/dashboard/test/goldens/build_dashboard.defaultPropertySheet.png
index 87ed3be..9d3de2f 100644
--- a/dashboard/test/goldens/build_dashboard.defaultPropertySheet.png
+++ b/dashboard/test/goldens/build_dashboard.defaultPropertySheet.png
Binary files differ
diff --git a/dashboard/test/widgets/commit_box_test.dart b/dashboard/test/widgets/commit_box_test.dart
index a09452c..c0320e8 100644
--- a/dashboard/test/widgets/commit_box_test.dart
+++ b/dashboard/test/widgets/commit_box_test.dart
@@ -3,6 +3,7 @@
 // found in the LICENSE file.
 
 import 'package:flutter/material.dart';
+import 'package:flutter/services.dart';
 import 'package:flutter_dashboard/model/commit.pb.dart';
 import 'package:flutter_dashboard/widgets/commit_box.dart';
 import 'package:flutter_test/flutter_test.dart';
@@ -95,4 +96,25 @@
     expect(urlLauncher.launches, isNotEmpty);
     expect(urlLauncher.launches.single, 'https://github.com/${expectedCommit.repository}/commit/${expectedCommit.sha}');
   });
+
+  testWidgets('clicking copy icon in CommitBox adds sha to clipboard', (WidgetTester tester) async {
+    final List<MethodCall> log = <MethodCall>[];
+    SystemChannels.platform.setMockMethodCallHandler((MethodCall methodCall) async {
+      log.add(methodCall);
+    });
+
+    await tester.pumpWidget(basicApp);
+
+    // Open the overlay
+    await tester.tap(find.byType(CommitBox));
+    await tester.pump();
+
+    // Tap the copy button
+    await tester.tap(find.byIcon(Icons.copy));
+    await tester.pump();
+
+    expect((log.last.arguments as Object) as Map<String, dynamic>, <String, String>{
+      'text': expectedCommit.sha,
+    });
+  });
 }
diff --git a/dashboard/test/widgets/failures/commit_box_test.open_isolatedDiff.png b/dashboard/test/widgets/failures/commit_box_test.open_isolatedDiff.png
new file mode 100644
index 0000000..5fb66b5
--- /dev/null
+++ b/dashboard/test/widgets/failures/commit_box_test.open_isolatedDiff.png
Binary files differ
diff --git a/dashboard/test/widgets/failures/commit_box_test.open_maskedDiff.png b/dashboard/test/widgets/failures/commit_box_test.open_maskedDiff.png
new file mode 100644
index 0000000..9b664df
--- /dev/null
+++ b/dashboard/test/widgets/failures/commit_box_test.open_maskedDiff.png
Binary files differ
diff --git a/dashboard/test/widgets/failures/commit_box_test.open_masterImage.png b/dashboard/test/widgets/failures/commit_box_test.open_masterImage.png
new file mode 100644
index 0000000..03aeed8
--- /dev/null
+++ b/dashboard/test/widgets/failures/commit_box_test.open_masterImage.png
Binary files differ
diff --git a/dashboard/test/widgets/failures/commit_box_test.open_testImage.png b/dashboard/test/widgets/failures/commit_box_test.open_testImage.png
new file mode 100644
index 0000000..fb86eee
--- /dev/null
+++ b/dashboard/test/widgets/failures/commit_box_test.open_testImage.png
Binary files differ
diff --git a/dashboard/test/widgets/goldens/commit_box_test.idle.png b/dashboard/test/widgets/goldens/commit_box_test.idle.png
index 895e18a..d3f01b8 100644
--- a/dashboard/test/widgets/goldens/commit_box_test.idle.png
+++ b/dashboard/test/widgets/goldens/commit_box_test.idle.png
Binary files differ
diff --git a/dashboard/test/widgets/goldens/commit_box_test.open.png b/dashboard/test/widgets/goldens/commit_box_test.open.png
index 68b6eb8..fb86eee 100644
--- a/dashboard/test/widgets/goldens/commit_box_test.open.png
+++ b/dashboard/test/widgets/goldens/commit_box_test.open.png
Binary files differ
diff --git a/dashboard/test/widgets/goldens/task_overlay_test.flaky_overlay_closed.png b/dashboard/test/widgets/goldens/task_overlay_test.flaky_overlay_closed.png
index f1ca02b..2f4b2c5 100644
--- a/dashboard/test/widgets/goldens/task_overlay_test.flaky_overlay_closed.png
+++ b/dashboard/test/widgets/goldens/task_overlay_test.flaky_overlay_closed.png
Binary files differ
diff --git a/dashboard/test/widgets/goldens/task_overlay_test.flaky_overlay_open.png b/dashboard/test/widgets/goldens/task_overlay_test.flaky_overlay_open.png
index f67a737..d9565d3 100644
--- a/dashboard/test/widgets/goldens/task_overlay_test.flaky_overlay_open.png
+++ b/dashboard/test/widgets/goldens/task_overlay_test.flaky_overlay_open.png
Binary files differ
diff --git a/dashboard/test/widgets/goldens/task_overlay_test.nondevicelab_closed.png b/dashboard/test/widgets/goldens/task_overlay_test.nondevicelab_closed.png
index 8e4eb77..b6bd35f 100644
--- a/dashboard/test/widgets/goldens/task_overlay_test.nondevicelab_closed.png
+++ b/dashboard/test/widgets/goldens/task_overlay_test.nondevicelab_closed.png
Binary files differ
diff --git a/dashboard/test/widgets/goldens/task_overlay_test.nondevicelab_open.png b/dashboard/test/widgets/goldens/task_overlay_test.nondevicelab_open.png
index 263c8aa..e61e886 100644
--- a/dashboard/test/widgets/goldens/task_overlay_test.nondevicelab_open.png
+++ b/dashboard/test/widgets/goldens/task_overlay_test.nondevicelab_open.png
Binary files differ
diff --git a/dashboard/test/widgets/goldens/task_overlay_test.normal_overlay_closed.png b/dashboard/test/widgets/goldens/task_overlay_test.normal_overlay_closed.png
index f84811b..044022d 100644
--- a/dashboard/test/widgets/goldens/task_overlay_test.normal_overlay_closed.png
+++ b/dashboard/test/widgets/goldens/task_overlay_test.normal_overlay_closed.png
Binary files differ
diff --git a/dashboard/test/widgets/goldens/task_overlay_test.normal_overlay_open.png b/dashboard/test/widgets/goldens/task_overlay_test.normal_overlay_open.png
index 647bf45..c2b213f 100644
--- a/dashboard/test/widgets/goldens/task_overlay_test.normal_overlay_open.png
+++ b/dashboard/test/widgets/goldens/task_overlay_test.normal_overlay_open.png
Binary files differ