Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
77 changes: 72 additions & 5 deletions packages/devtools_app/lib/src/shared/managers/survey.dart
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,12 @@ import 'package:devtools_shared/devtools_shared.dart';
import 'package:flutter/widgets.dart';
import 'package:http/http.dart';
import 'package:logging/logging.dart';
import 'package:unified_analytics/unified_analytics.dart' as ua;

import '../analytics/analytics.dart' as ga;
import '../development_helpers.dart';
import '../globals.dart';
import '../primitives/query_parameters.dart';
import '../primitives/utils.dart';
import '../server/server.dart' as server;
import '../utils/utils.dart';
Expand Down Expand Up @@ -232,7 +234,12 @@ class DevToolsSurvey {
/// If [devEnvironments] is null, the survey can be shown to any platform.
///
/// The possible values for this list correspond to the possible values of
/// `_ideLaunched` from [shared/analytics/_analytics_web.dart].
/// `_ideLaunched` from [shared/analytics/_analytics_web.dart]. They can also
/// be the labels of the Dash tools that IDEs pass in the `dashTool` query
/// parameter (e.g. 'vscode-plugins', 'intellij-plugins'), which are shared by
/// every variant of an IDE (e.g. VS Code, Cursor and Antigravity). Versions
/// of DevTools that predate the `dashTool` query parameter only recognize the
/// former.
final List<String>? devEnvironments;
}

Expand All @@ -250,10 +257,70 @@ extension ShowSurveyExtension on DevToolsSurvey {
devToolsVersion,
).isSupported(minSupportedVersion: minDevToolsVersion!);

bool get meetsEnvironmentRequirement =>
devEnvironments == null ||
ga.ideLaunched.isEmpty ||
devEnvironments!.contains(ga.ideLaunched);
/// Whether this survey should be shown in the development environment that
/// the current DevTools session was launched from.
///
/// See [meetsEnvironmentRequirementFor] for how [devEnvironments] are matched.
bool get meetsEnvironmentRequirement => meetsEnvironmentRequirementFor(
ideLaunched: ga.ideLaunched,
queryParams: DevToolsQueryParams.load(),
);

/// Whether this survey should be shown in the development environment that
/// DevTools was launched from, which is described by [ideLaunched] and
/// [queryParams].
///
/// A survey that lists [devEnvironments] is shown if it lists the legacy
/// [ideLaunched] value, or the `dashTool` of the IDE integration that
/// launched DevTools. The legacy and `dashTool` environment names for VS Code
/// ('VSCode' and 'vscode-plugins') and for IntelliJ/Android Studio
/// ('Android-Studio', 'IntelliJ-IDEA', 'intellij-plugins' and
/// 'android-studio-plugins') match bidirectionally, including for VS Code
/// forks (such as Cursor or Antigravity) that do not pass 'VSCode' as their
/// legacy IDE value.
@visibleForTesting
bool meetsEnvironmentRequirementFor({
required String ideLaunched,
required DevToolsQueryParams queryParams,
}) {
final environments = devEnvironments;
if (environments == null || ideLaunched.isEmpty) return true;

final dashTool = queryParams.dashTool;
final isVsCode =
queryParams.isVsCode ||
ideLaunched == DevToolsQueryParams.legacyVsCodeIdeValue;
if (isVsCode &&
(environments.contains(DevToolsQueryParams.legacyVsCodeIdeValue) ||
environments.contains(ua.DashTool.vscodePlugins.label))) {
return true;
}

final isIntelliJOrAndroidStudio =
Comment thread
srawlins marked this conversation as resolved.
dashTool == ua.DashTool.intellijPlugins.label ||
dashTool == ua.DashTool.androidStudioPlugins.label ||
ideLaunched == DevToolsQueryParams.legacyAndroidStudioIdeValue ||
ideLaunched == DevToolsQueryParams.legacyIntelliJIdeValue;
if (isIntelliJOrAndroidStudio &&
(environments.contains(
DevToolsQueryParams.legacyAndroidStudioIdeValue,
) ||
environments.contains(DevToolsQueryParams.legacyIntelliJIdeValue) ||
environments.contains(ua.DashTool.intellijPlugins.label) ||
environments.contains(ua.DashTool.androidStudioPlugins.label))) {
return true;
}

// When an IDE integration passes `dashTool` without the legacy `ide`
// parameter, `ga.ideLaunched` defaults to 'CLI'. Ignore that fallback so
// IDE launches do not match 'CLI' surveys.
final isIdeDashTool = isVsCode || isIntelliJOrAndroidStudio;
final matchesIdeLaunched =
(!isIdeDashTool || queryParams.ide != null) &&
environments.contains(ideLaunched);
return matchesIdeLaunched ||
(dashTool != null && environments.contains(dashTool));
}

bool get shouldShow =>
meetsDateRequirement &&
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -231,7 +231,7 @@ class PreferencesController extends DisposableController
// default. This is because cross-origin-isolation is disabled by VS
// Code. See https://github.com/microsoft/vscode/issues/186614.
final embeddedInVsCode =
queryParams.embedMode.embedded && queryParams.ide == 'VSCode';
queryParams.embedMode.embedded && queryParams.isVsCode;
if (!embeddedInVsCode) {
notificationService.push(
'Something went wrong when trying to load DevTools with WebAssembly. '
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import 'package:devtools_app_shared/shared.dart';
import 'package:devtools_app_shared/ui.dart';
import 'package:devtools_app_shared/utils.dart';
import 'package:devtools_shared/devtools_shared.dart';
import 'package:unified_analytics/unified_analytics.dart' as ua;

extension type DevToolsQueryParams(Map<String, String?> params) {
static DevToolsQueryParams empty() => DevToolsQueryParams({});
Expand Down Expand Up @@ -50,12 +51,58 @@ extension type DevToolsQueryParams(Map<String, String?> params) {
String? get appSizeTestFilePath =>
params[AppSizeApi.testAppSizeFilePropertyName];

// TODO(https://github.com/flutter/devtools/issues/9811): Remove this legacy
// parameter once IDE integrations have been passing dashTool and dashIdeName
// for long enough (at least two years) to allow users ample time to upgrade.
/// The IDE that DevTools is embedded in or was launched from.
///
/// This is a legacy value (for example `VSCode`, `Android-Studio` or
/// `IntelliJ-IDEA`) that IDE integrations continue to pass for the benefit of
/// versions of DevTools that predate [dashTool] and [dashIdeName]. Prefer
/// those parameters, and use [isVsCode] to check for VS Code.
String? get ide => params[ideKey];

/// The feature of the IDE that DevTools was opened from.
String? get ideFeature => params[ideFeatureKey];

/// The canonical label of the Dash tool, such as an IDE plugin, that DevTools
/// is embedded in or was launched from (for example `vscode-plugins` or
/// `intellij-plugins`).
///
/// This corresponds to the `DASH__TOOL` environment variable that IDEs set
/// for the processes they spawn. It is shared by every variant of an IDE
/// that uses the same plugin (for example VS Code, Cursor and Antigravity).
/// See [ua.DashTool] for the labels of the known tools.
String? get dashTool => params[dashToolKey];

/// The name of the IDE that DevTools is embedded in or was launched from (for
/// example `Visual Studio Code`, `Cursor`, `Antigravity` or `Android Studio`).
///
/// This corresponds to the `DASH__IDE_NAME` environment variable that IDEs
/// set for the processes they spawn. The possible values are open-ended,
/// because new IDEs, and forks of existing IDEs, choose their own names. So
/// this should only be used for display and diagnostics, and not to decide
/// how DevTools behaves. Use [dashTool] or [isVsCode] for that.
String? get dashIdeName => params[dashIdeNameKey];

/// The best available name of the IDE that DevTools is embedded in or was
/// launched from, for display and diagnostics (for example in bug reports).
///
/// This is [dashIdeName], which distinguishes forks of an IDE (like Cursor)
/// from the IDE itself, falling back to the legacy [ide] for IDE integrations
/// that do not pass [dashIdeName].
String? get ideDisplayName => dashIdeName ?? ide;

/// Whether DevTools is embedded in or was launched from VS Code, or a fork of
/// VS Code such as Cursor or Antigravity.
///
/// This is determined by [dashTool], which is the same for every variant of
/// VS Code. The legacy [ide] is checked as well, for IDE integrations that do
/// not pass [dashTool].
bool get isVsCode =>
dashTool == ua.DashTool.vscodePlugins.label ||
ide == legacyVsCodeIdeValue;

/// Keys for theming values that an IDE may pass in the embedded DevTools URI.
IdeThemeQueryParams get ideThemeParams => IdeThemeQueryParams(params);

Expand All @@ -81,6 +128,18 @@ extension type DevToolsQueryParams(Map<String, String?> params) {
static const inspectorRefKey = 'inspectorRef';
static const ideKey = 'ide';
static const ideFeatureKey = 'ideFeature';
static const dashToolKey = 'dashTool';
static const dashIdeNameKey = 'dashIdeName';

/// The value of the legacy [ide] parameter that VS Code, and the forks of VS
/// Code that use the same extension, pass.
static const legacyVsCodeIdeValue = 'VSCode';

/// The value of the legacy [ide] parameter that Android Studio passes.
static const legacyAndroidStudioIdeValue = 'Android-Studio';

/// The value of the legacy [ide] parameter that IntelliJ IDEA passes.
static const legacyIntelliJIdeValue = 'IntelliJ-IDEA';

/// Query parameter key to determine whether to use dart2wasm or dart2js.
///
Expand Down
2 changes: 1 addition & 1 deletion packages/devtools_app/lib/src/shared/utils/utils.dart
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ List<ConnectionDescription> generateDeviceDescription(

/// This method should be public, because it is used by g3 specific code.
List<String> issueLinkDetails() {
final ide = DevToolsQueryParams.load().ide;
final ide = DevToolsQueryParams.load().ideDisplayName;
final issueDescriptionItems = [
'<-- Please describe your problem here. Be sure to include repro steps. -->',
'___', // This will create a separator in the rendered markdown.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,9 @@ class _PropertyEditorFooter extends StatelessWidget {
Widget build(BuildContext context) {
final theme = Theme.of(context);
final colorScheme = theme.colorScheme;
final documentationLink = _documentationLink();
final documentationLink = propertyEditorDocumentationLink(
DevToolsQueryParams.load(),
);
return Container(
decoration: BoxDecoration(
color: colorScheme.surface,
Expand All @@ -165,15 +167,17 @@ class _PropertyEditorFooter extends StatelessWidget {
),
);
}
}

String? _documentationLink() {
final queryParams = DevToolsQueryParams.load();
final isEmbedded = queryParams.embedMode.embedded;
if (!isEmbedded) return null;
const uriPrefix = 'https://docs.flutter.dev/tools/';
const uriHash = '#property-editor';
return '$uriPrefix${queryParams.ide == 'VSCode' ? 'vs-code' : 'android-studio'}$uriHash';
}
/// Returns the Property Editor documentation link for the IDE described by
/// [queryParams], or `null` if DevTools is not embedded in an IDE.
@visibleForTesting
String? propertyEditorDocumentationLink(DevToolsQueryParams queryParams) {
final isEmbedded = queryParams.embedMode.embedded;
if (!isEmbedded) return null;
const uriPrefix = 'https://docs.flutter.dev/tools/';
const uriHash = '#property-editor';
return '$uriPrefix${queryParams.isVsCode ? 'vs-code' : 'android-studio'}$uriHash';
}

class _DocsLink extends StatelessWidget {
Expand Down
Loading
Loading