From c9b29a7be120f8f5d7420efe525caee5d356939c Mon Sep 17 00:00:00 2001
From: Felix Becker
Date: Wed, 14 Oct 2020 20:47:08 +0200
Subject: [PATCH] Adapt in-app messaging to native integration (#14659)
Co-authored-by: TJ Kandala
---
client/web/src/nav/NavLinks.tsx | 5 +
client/web/src/nav/UserNavItem.story.tsx | 7 +-
client/web/src/nav/UserNavItem.test.tsx | 1 +
client/web/src/nav/UserNavItem.tsx | 28 +-
.../__snapshots__/UserNavItem.test.tsx.snap | 1 +
client/web/src/repo/RepoContainer.tsx | 13 +-
.../InstallBrowserExtensionAlert.story.tsx | 29 +
.../InstallBrowserExtensionAlert.test.tsx | 11 +-
.../actions/InstallBrowserExtensionAlert.tsx | 21 +-
...InstallBrowserExtensionAlert.test.tsx.snap | 528 +++++++++++++++---
doc/admin/external_service/gitlab.md | 14 +-
doc/admin/external_service/phabricator.md | 10 +
doc/integration/bitbucket_server.md | 10 +
schema/schema.go | 2 +
schema/settings.schema.json | 6 +
schema/settings_stringdata.go | 6 +
16 files changed, 609 insertions(+), 83 deletions(-)
diff --git a/client/web/src/nav/NavLinks.tsx b/client/web/src/nav/NavLinks.tsx
index 8c2cecc631d..fbd168de18f 100644
--- a/client/web/src/nav/NavLinks.tsx
+++ b/client/web/src/nav/NavLinks.tsx
@@ -138,6 +138,11 @@ export class NavLinks extends React.PureComponent {
{...this.props}
authenticatedUser={this.props.authenticatedUser}
showDotComMarketing={this.props.showDotComMarketing}
+ codeHostIntegrationMessaging={
+ (!isErrorLike(this.props.settingsCascade.final) &&
+ this.props.settingsCascade.final?.['alerts.codeHostIntegrationMessaging']) ||
+ 'browser-extension'
+ }
keyboardShortcutForSwitchTheme={KEYBOARD_SHORTCUT_SWITCH_THEME}
/>
diff --git a/client/web/src/nav/UserNavItem.story.tsx b/client/web/src/nav/UserNavItem.story.tsx
index d1a6835fa21..701caceb63c 100644
--- a/client/web/src/nav/UserNavItem.story.tsx
+++ b/client/web/src/nav/UserNavItem.story.tsx
@@ -1,5 +1,5 @@
import { action } from '@storybook/addon-actions'
-import { boolean } from '@storybook/addon-knobs'
+import { boolean, select } from '@storybook/addon-knobs'
import { storiesOf } from '@storybook/react'
import React from 'react'
import { ThemePreference } from '../theme'
@@ -47,6 +47,11 @@ add(
onThemePreferenceChange={onThemePreferenceChange}
showDotComMarketing={boolean('showDotComMarketing', true)}
isExtensionAlertAnimating={false}
+ codeHostIntegrationMessaging={select(
+ 'codeHostIntegrationMessaging',
+ ['browser-extension', 'native-integration'] as const,
+ 'browser-extension'
+ )}
/>
)}
diff --git a/client/web/src/nav/UserNavItem.test.tsx b/client/web/src/nav/UserNavItem.test.tsx
index 52bbbc4062f..ed2dd1d5c13 100644
--- a/client/web/src/nav/UserNavItem.test.tsx
+++ b/client/web/src/nav/UserNavItem.test.tsx
@@ -46,6 +46,7 @@ describe('UserNavItem', () => {
authenticatedUser={USER}
showDotComMarketing={true}
isExtensionAlertAnimating={false}
+ codeHostIntegrationMessaging="browser-extension"
/>
)
diff --git a/client/web/src/nav/UserNavItem.tsx b/client/web/src/nav/UserNavItem.tsx
index 8b52fa39b16..9086942d134 100644
--- a/client/web/src/nav/UserNavItem.tsx
+++ b/client/web/src/nav/UserNavItem.tsx
@@ -20,6 +20,7 @@ export interface UserNavItemProps extends ThemeProps, ThemePreferenceProps, Exte
showDotComMarketing: boolean
keyboardShortcutForSwitchTheme?: KeyboardShortcut
testIsOpen?: boolean
+ codeHostIntegrationMessaging: 'browser-extension' | 'native-integration'
}
export interface ExtensionAlertAnimationProps {
@@ -58,7 +59,14 @@ export function useExtensionAlertAnimation(): ExtensionAlertAnimationProps & {
* authenticated viewers.
*/
export const UserNavItem: React.FunctionComponent = props => {
- const { location, themePreference, onThemePreferenceChange, isExtensionAlertAnimating, testIsOpen } = props
+ const {
+ location,
+ themePreference,
+ onThemePreferenceChange,
+ isExtensionAlertAnimating,
+ testIsOpen,
+ codeHostIntegrationMessaging,
+ } = props
const supportsSystemTheme = useMemo(
() => Boolean(window.matchMedia?.('not all and (prefers-color-scheme), (prefers-color-scheme)').matches),
@@ -197,14 +205,16 @@ export const UserNavItem: React.FunctionComponent = props => {
About Sourcegraph
)}
-
- Browser extension
-
+ {codeHostIntegrationMessaging === 'browser-extension' && (
+
+ Browser extension
+
+ )}
)
diff --git a/client/web/src/nav/__snapshots__/UserNavItem.test.tsx.snap b/client/web/src/nav/__snapshots__/UserNavItem.test.tsx.snap
index 210aa33d5e0..77ad4446648 100644
--- a/client/web/src/nav/__snapshots__/UserNavItem.test.tsx.snap
+++ b/client/web/src/nav/__snapshots__/UserNavItem.test.tsx.snap
@@ -35,6 +35,7 @@ exports[`UserNavItem simple 1`] = `
"username": "alice",
}
}
+ codeHostIntegrationMessaging="browser-extension"
isExtensionAlertAnimating={false}
isLightTheme={true}
location="[Location path=/]"
diff --git a/client/web/src/repo/RepoContainer.tsx b/client/web/src/repo/RepoContainer.tsx
index ac027fbdb1b..0289cc286f8 100644
--- a/client/web/src/repo/RepoContainer.tsx
+++ b/client/web/src/repo/RepoContainer.tsx
@@ -59,6 +59,7 @@ import { browserExtensionInstalled } from '../tracking/analyticsUtils'
import { InstallBrowserExtensionAlert } from './actions/InstallBrowserExtensionAlert'
import { IS_CHROME } from '../marketing/util'
import { useLocalStorage } from '../util/useLocalStorage'
+import { Settings } from '../schema/settings.schema'
/**
* Props passed to sub-routes of {@link RepoContainer}.
@@ -100,7 +101,7 @@ const RepoPageNotFound: React.FunctionComponent = () => (
interface RepoContainerProps
extends RouteComponentProps<{ repoRevAndRest: string }>,
- SettingsCascadeProps,
+ SettingsCascadeProps,
PlatformContextProps,
TelemetryProps,
ExtensionsControllerProps,
@@ -334,13 +335,20 @@ export const RepoContainer: React.FunctionComponent = props
])
const isBrowserExtensionInstalled = useObservable(browserExtensionInstalled)
+ const codeHostIntegrationMessaging =
+ (!isErrorLike(props.settingsCascade.final) &&
+ props.settingsCascade.final?.['alerts.codeHostIntegrationMessaging']) ||
+ 'browser-extension'
// Browser extension discoverability features (alert, popover for `GoToCodeHostAction)
const [hasDismissedExtensionAlert, setHasDismissedExtensionAlert] = useLocalStorage(HAS_DISMISSED_ALERT_KEY, false)
const [hasDismissedPopover, setHasDismissedPopover] = useState(false)
const [hoverCount, setHoverCount] = useLocalStorage(HOVER_COUNT_KEY, 0)
const canShowPopover =
- !hasDismissedPopover && isBrowserExtensionInstalled === false && hoverCount >= HOVER_THRESHOLD
+ !hasDismissedPopover &&
+ isBrowserExtensionInstalled === false &&
+ codeHostIntegrationMessaging === 'browser-extension' &&
+ hoverCount >= HOVER_THRESHOLD
const showExtensionAlert = useMemo(
() => isBrowserExtensionInstalled === false && !hasDismissedExtensionAlert && hoverCount >= HOVER_THRESHOLD,
// Intentionally use useMemo() here without a dependency on hoverCount to only show the alert on the next reload,
@@ -412,6 +420,7 @@ export const RepoContainer: React.FunctionComponent = props
isChrome={IS_CHROME}
onAlertDismissed={onAlertDismissed}
externalURLs={repoOrError.externalURLs}
+ codeHostIntegrationMessaging={codeHostIntegrationMessaging}
/>
)}
+ )}
+
+ ),
+ {
+ chromatic: {
+ disable: serviceType !== 'github',
+ },
+ }
+ )
+
+ add(
+ `${serviceType} (native integration installed)`,
+ () => (
+
+ {() => (
+ {
const serviceTypes = ['github', 'gitlab', 'phabricator', 'bitbucketServer'] as const
- const browsers = ['Chrome', 'non-Chrome'] as const
+ const integrationTypes = ['Chrome', 'non-Chrome', 'native integration'] as const
for (const serviceType of serviceTypes) {
- for (const browser of browsers) {
- test(`${serviceType} (${browser})`, () => {
+ for (const integrationType of integrationTypes) {
+ test(`${serviceType} (${integrationType})`, () => {
expect(
mount(
void
externalURLs: GQL.IExternalLink[]
isChrome: boolean
+ codeHostIntegrationMessaging: 'browser-extension' | 'native-integration'
}
// TODO(tj): Add Firefox once the Firefox extension is back
@@ -17,6 +18,7 @@ export const InstallBrowserExtensionAlert: React.FunctionComponent = ({
onAlertDismissed,
externalURLs,
isChrome,
+ codeHostIntegrationMessaging,
}) => {
const { serviceType } = externalURLs[0]
const { displayName, icon } = serviceTypeDisplayNameAndIcon(serviceType)
@@ -38,7 +40,24 @@ export const InstallBrowserExtensionAlert: React.FunctionComponent = ({
- {isChrome ? (
+ {codeHostIntegrationMessaging ? (
+ <>
+ Sourcegraph's code intelligence will follow you to your code host. Your site admin set up
+ the Sourcegraph native integration for {displayName}.{' '}
+
+ Learn more
+ {' '}
+ or{' '}
+
+ try it out
+
+ >
+ ) : isChrome ? (
<>
+ Sourcegraph's code intelligence will follow you to your code host. Your site admin set up the Sourcegraph native integration for
+ Bitbucket Server
+ .
+
- Install the Sourcegraph browser extension
+ Learn more
- to add code intelligence
- to PRs and file views
- on
- Bitbucket Server
- or any other connected code host.
+ or
+
+
+ try it out
+
`;
-exports[`InstallBrowserExtensionAlert bitbucketServer (non-Chrome) 1`] = `
+exports[`InstallBrowserExtensionAlert bitbucketServer (native integration) 1`] = `
- Get code intelligence
- while browsing files and reading PRs
- on
+ Sourcegraph's code intelligence will follow you to your code host. Your site admin set up the Sourcegraph native integration for
Bitbucket Server
- or any other connected code host.
+ .
- Learn more about Sourcegraph Chrome and Firefox extensions
+ Learn more
+
+
+ or
+
+
+ try it out
+
+
+
+
+
+
+
+
+`;
+
+exports[`InstallBrowserExtensionAlert bitbucketServer (non-Chrome) 1`] = `
+
+
+
+
+
+ Sourcegraph's code intelligence will follow you to your code host. Your site admin set up the Sourcegraph native integration for
+ Bitbucket Server
+ .
+
+
+ Learn more
+
+
+ or
+
+
+ try it out
@@ -128,6 +221,7 @@ exports[`InstallBrowserExtensionAlert bitbucketServer (non-Chrome) 1`] = `
exports[`InstallBrowserExtensionAlert github (Chrome) 1`] = `
+ Sourcegraph's code intelligence will follow you to your code host. Your site admin set up the Sourcegraph native integration for
+ GitHub
+ .
+
- Install the Sourcegraph browser extension
+ Learn more
- to add code intelligence
- to PRs and file views
- on
- GitHub
- or any other connected code host.
+ or
+
+
+ try it out
+
`;
-exports[`InstallBrowserExtensionAlert github (non-Chrome) 1`] = `
+exports[`InstallBrowserExtensionAlert github (native integration) 1`] = `
- Get code intelligence
- while browsing files and reading PRs
- on
+ Sourcegraph's code intelligence will follow you to your code host. Your site admin set up the Sourcegraph native integration for
GitHub
- or any other connected code host.
+ .
- Learn more about Sourcegraph Chrome and Firefox extensions
+ Learn more
+
+
+ or
+
+
+ try it out
+
+
+
+
+
+
+
+
+`;
+
+exports[`InstallBrowserExtensionAlert github (non-Chrome) 1`] = `
+
+
+
+
+
+ Sourcegraph's code intelligence will follow you to your code host. Your site admin set up the Sourcegraph native integration for
+ GitHub
+ .
+
+
+ Learn more
+
+
+ or
+
+
+ try it out
@@ -254,6 +440,7 @@ exports[`InstallBrowserExtensionAlert github (non-Chrome) 1`] = `
exports[`InstallBrowserExtensionAlert gitlab (Chrome) 1`] = `
+ Sourcegraph's code intelligence will follow you to your code host. Your site admin set up the Sourcegraph native integration for
+ GitLab
+ .
+
- Install the Sourcegraph browser extension
+ Learn more
- to add code intelligence
- to MRs and file views
- on
- GitLab
- or any other connected code host.
+ or
+
+
+ try it out
+
`;
-exports[`InstallBrowserExtensionAlert gitlab (non-Chrome) 1`] = `
+exports[`InstallBrowserExtensionAlert gitlab (native integration) 1`] = `
- Get code intelligence
- while browsing files and reading MRs
- on
+ Sourcegraph's code intelligence will follow you to your code host. Your site admin set up the Sourcegraph native integration for
GitLab
- or any other connected code host.
+ .
- Learn more about Sourcegraph Chrome and Firefox extensions
+ Learn more
+
+
+ or
+
+
+ try it out
+
+
+
+
+
+
+
+
+`;
+
+exports[`InstallBrowserExtensionAlert gitlab (non-Chrome) 1`] = `
+
+
+
+
+
+ Sourcegraph's code intelligence will follow you to your code host. Your site admin set up the Sourcegraph native integration for
+ GitLab
+ .
+
+
+ Learn more
+
+
+ or
+
+
+ try it out
@@ -380,6 +659,7 @@ exports[`InstallBrowserExtensionAlert gitlab (non-Chrome) 1`] = `
exports[`InstallBrowserExtensionAlert phabricator (Chrome) 1`] = `
+ Sourcegraph's code intelligence will follow you to your code host. Your site admin set up the Sourcegraph native integration for
+ Phabricator
+ .
+
- Install the Sourcegraph browser extension
+ Learn more
- to add code intelligence
- while browsing and reviewing code
- on
- Phabricator
- or any other connected code host.
+ or
+
+
+ try it out
+
`;
-exports[`InstallBrowserExtensionAlert phabricator (non-Chrome) 1`] = `
+exports[`InstallBrowserExtensionAlert phabricator (native integration) 1`] = `
- Get code intelligence
- while browsing and reviewing code
- on
+ Sourcegraph's code intelligence will follow you to your code host. Your site admin set up the Sourcegraph native integration for
Phabricator
- or any other connected code host.
+ .
- Learn more about Sourcegraph Chrome and Firefox extensions
+ Learn more
+
+
+ or
+
+
+ try it out
+
+
+
+
+
+
+
+
+`;
+
+exports[`InstallBrowserExtensionAlert phabricator (non-Chrome) 1`] = `
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Sourcegraph's code intelligence will follow you to your code host. Your site admin set up the Sourcegraph native integration for
+ Phabricator
+ .
+
+
+ Learn more
+
+
+ or
+
+
+ try it out
diff --git a/doc/admin/external_service/gitlab.md b/doc/admin/external_service/gitlab.md
index 9ba0382e13b..879ebba3cb9 100644
--- a/doc/admin/external_service/gitlab.md
+++ b/doc/admin/external_service/gitlab.md
@@ -28,7 +28,7 @@ There are three fields for configuring which projects are mirrored/synchronized:
### Troubleshooting
-You can test your access token's permissions by running a cURL command against the GitLab API. This is the same API and the same project list used by Sourcegraph.
+You can test your access token's permissions by running a cURL command against the GitLab API. This is the same API and the same project list used by Sourcegraph.
Replace `$ACCESS_TOKEN` with the access token you are providing to Sourcegraph, and `$GITLAB_HOSTNAME` with your GitLab hostname:
@@ -49,7 +49,7 @@ To configure GitLab as an authentication provider (which will enable sign-in via
## Internal rate limits
-Internal rate limiting can be configured to limit the rate at which requests are made from Sourcegraph to GitLab.
+Internal rate limiting can be configured to limit the rate at which requests are made from Sourcegraph to GitLab.
If enabled, the default rate is set at 36,000 per hour (10 per second) which can be configured via the `requestsPerHour` field (see below). If rate limiting is configured more than once for the same code host instance, the most restrictive limit will be used.
@@ -74,6 +74,16 @@ The Sourcegraph instance's site admin must [update the `corsOrigin` site config
}
```
+The site admin should also set `alerts.codeHostIntegrationMessaging` in [global settings](../config/settings.md#editing-global-settings-for-site-admins) to ensure informational content for users in the Sourcegraph webapp references the native integration and not the browser extension.
+
+```json
+{
+ // ...
+ "alerts.codeHostIntegrationMessaging": "native-integration"
+ // ...
+}
+```
+
## Access token scopes
Sourcegraph requires an access token with `api` permissions (and `sudo`, if you are using an `external` identity provider type). These permissions are required for the following reasons:
diff --git a/doc/admin/external_service/phabricator.md b/doc/admin/external_service/phabricator.md
index e662ecc8e04..73c01d16f40 100644
--- a/doc/admin/external_service/phabricator.md
+++ b/doc/admin/external_service/phabricator.md
@@ -64,6 +64,16 @@ The Sourcegraph instance's site admin must [update the `corsOrigin` site config
}
```
+The site admin should also set `alerts.codeHostIntegrationMessaging` in [global settings](../config/settings.md#editing-global-settings-for-site-admins) to ensure informational content for users in the Sourcegraph webapp references the native integration and not the browser extension.
+
+```json
+{
+ // ...
+ "alerts.codeHostIntegrationMessaging": "native-integration"
+ // ...
+}
+```
+
## Configuration
[View page on docs.sourcegraph.com](https://docs.sourcegraph.com/admin/external_service/phabricator) to see rendered content.
diff --git a/doc/integration/bitbucket_server.md b/doc/integration/bitbucket_server.md
index 2ad21d21797..094a9c4787b 100644
--- a/doc/integration/bitbucket_server.md
+++ b/doc/integration/bitbucket_server.md
@@ -42,6 +42,16 @@ For the Bitbucket Server plugin to then communicate with the Sourcegraph instanc
}
```
+The site admin should also set `alerts.codeHostIntegrationMessaging` in [global settings](../admin/config/settings.md#editing-global-settings-for-site-admins) to ensure informational content for users in the Sourcegraph webapp references the native integration and not the browser extension.
+
+```json
+{
+ // ...
+ "alerts.codeHostIntegrationMessaging": "native-integration"
+ // ...
+}
+```
+
### Updating
In order to update the plugin, follow the same steps as for installing it, which are described in the [bitbucket-server-plugin](https://github.com/sourcegraph/bitbucket-server-plugin) repository.
diff --git a/schema/schema.go b/schema/schema.go
index e2e38095569..4eee3b76a95 100644
--- a/schema/schema.go
+++ b/schema/schema.go
@@ -1039,6 +1039,8 @@ type Sentry struct {
// Settings description: Configuration settings for users and organizations on Sourcegraph.
type Settings struct {
+ // AlertsCodeHostIntegrationMessaging description: What in-app messaging to use around availability of Sourcegraph's code intelligence on code hosts. If the native code host integration is installed, this should be set to "native-integration" and users won't need to install the Sourcegraph browser extension to get code intelligence on code hosts.
+ AlertsCodeHostIntegrationMessaging string `json:"alerts.codeHostIntegrationMessaging,omitempty"`
// AlertsHideObservabilitySiteAlerts description: Disables observability-related site alert banners.
AlertsHideObservabilitySiteAlerts *bool `json:"alerts.hideObservabilitySiteAlerts,omitempty"`
// AlertsShowPatchUpdates description: Whether to show alerts for patch version updates. Alerts for major and minor version updates will always be shown.
diff --git a/schema/settings.schema.json b/schema/settings.schema.json
index ef866833779..508fc067bc5 100644
--- a/schema/settings.schema.json
+++ b/schema/settings.schema.json
@@ -202,6 +202,12 @@
"default": true,
"!go": { "pointer": true }
},
+ "alerts.codeHostIntegrationMessaging": {
+ "description": "What in-app messaging to use around availability of Sourcegraph's code intelligence on code hosts. If the native code host integration is installed, this should be set to \"native-integration\" and users won't need to install the Sourcegraph browser extension to get code intelligence on code hosts.",
+ "type": "string",
+ "enum": ["browser-extension", "native-integration"],
+ "default": "browser-extension"
+ },
"extensions": {
"description": "The Sourcegraph extensions to use. Enable an extension by adding a property `\"my/extension\": true` (where `my/extension` is the extension ID). Override a previously enabled extension and disable it by setting its value to `false`.",
"type": "object",
diff --git a/schema/settings_stringdata.go b/schema/settings_stringdata.go
index 5910e8632c5..e9c144ab694 100644
--- a/schema/settings_stringdata.go
+++ b/schema/settings_stringdata.go
@@ -207,6 +207,12 @@ const SettingsSchemaJSON = `{
"default": true,
"!go": { "pointer": true }
},
+ "alerts.codeHostIntegrationMessaging": {
+ "description": "What in-app messaging to use around availability of Sourcegraph's code intelligence on code hosts. If the native code host integration is installed, this should be set to \"native-integration\" and users won't need to install the Sourcegraph browser extension to get code intelligence on code hosts.",
+ "type": "string",
+ "enum": ["browser-extension", "native-integration"],
+ "default": "browser-extension"
+ },
"extensions": {
"description": "The Sourcegraph extensions to use. Enable an extension by adding a property ` + "`" + `\"my/extension\": true` + "`" + ` (where ` + "`" + `my/extension` + "`" + ` is the extension ID). Override a previously enabled extension and disable it by setting its value to ` + "`" + `false` + "`" + `.",
"type": "object",