|
@@ -5,39 +5,83 @@ import {motion} from 'framer-motion';
|
|
|
import {openInviteMembersModal} from 'sentry/actionCreators/modal';
|
|
|
import {Alert} from 'sentry/components/alert';
|
|
|
import {Button} from 'sentry/components/button';
|
|
|
-import ButtonBar from 'sentry/components/buttonBar';
|
|
|
import ExternalLink from 'sentry/components/links/externalLink';
|
|
|
import LoadingError from 'sentry/components/loadingError';
|
|
|
-import platforms from 'sentry/data/platforms';
|
|
|
+import LoadingIndicator from 'sentry/components/loadingIndicator';
|
|
|
+import type {
|
|
|
+ BasePlatformOptions,
|
|
|
+ DocsParams,
|
|
|
+} from 'sentry/components/onboarding/gettingStartedDoc/types';
|
|
|
+import {useLoadGettingStarted} from 'sentry/components/onboarding/gettingStartedDoc/utils/useLoadGettingStarted';
|
|
|
+import {
|
|
|
+ PlatformOptionsControl,
|
|
|
+ useUrlPlatformOptions,
|
|
|
+} from 'sentry/components/onboarding/platformOptionsControl';
|
|
|
import {t, tct} from 'sentry/locale';
|
|
|
+import ConfigStore from 'sentry/stores/configStore';
|
|
|
+import {useLegacyStore} from 'sentry/stores/useLegacyStore';
|
|
|
import {space} from 'sentry/styles/space';
|
|
|
import type {IntegrationProvider} from 'sentry/types/integrations';
|
|
|
-import type {Project} from 'sentry/types/project';
|
|
|
+import type {PlatformIntegration, Project} from 'sentry/types/project';
|
|
|
import {trackAnalytics} from 'sentry/utils/analytics';
|
|
|
import getDynamicText from 'sentry/utils/getDynamicText';
|
|
|
-import {trackIntegrationAnalytics} from 'sentry/utils/integrationUtil';
|
|
|
import useApi from 'sentry/utils/useApi';
|
|
|
import useOrganization from 'sentry/utils/useOrganization';
|
|
|
+import SetupIntroduction from 'sentry/views/onboarding/components/setupIntroduction';
|
|
|
import {AddIntegrationButton} from 'sentry/views/settings/organizationIntegrations/addIntegrationButton';
|
|
|
|
|
|
import AddInstallationInstructions from './components/integrations/addInstallationInstructions';
|
|
|
import PostInstallCodeSnippet from './components/integrations/postInstallCodeSnippet';
|
|
|
-import SetupIntroduction from './components/setupIntroduction';
|
|
|
+
|
|
|
+export enum InstallationMode {
|
|
|
+ AUTO = 'auto',
|
|
|
+ MANUAL = 'manual',
|
|
|
+}
|
|
|
+
|
|
|
+export const platformOptions = {
|
|
|
+ installationMode: {
|
|
|
+ label: t('Installation Mode'),
|
|
|
+ items: [
|
|
|
+ {
|
|
|
+ label: t('Auto'),
|
|
|
+ value: InstallationMode.AUTO,
|
|
|
+ },
|
|
|
+ {
|
|
|
+ label: t('Manual'),
|
|
|
+ value: InstallationMode.MANUAL,
|
|
|
+ },
|
|
|
+ ],
|
|
|
+ defaultValue: InstallationMode.AUTO,
|
|
|
+ },
|
|
|
+} satisfies BasePlatformOptions;
|
|
|
|
|
|
type Props = {
|
|
|
integrationSlug: string;
|
|
|
- project: Project | null;
|
|
|
- onClickManualSetup?: () => void;
|
|
|
+ platform: PlatformIntegration;
|
|
|
+ project: Project;
|
|
|
};
|
|
|
|
|
|
-function IntegrationSetup(props: Props) {
|
|
|
+function IntegrationSetup({project, integrationSlug, platform}: Props) {
|
|
|
const [hasError, setHasError] = useState(false);
|
|
|
const [installed, setInstalled] = useState(false);
|
|
|
const [provider, setProvider] = useState<IntegrationProvider | null>(null);
|
|
|
|
|
|
const organization = useOrganization();
|
|
|
+ const {isSelfHosted, urlPrefix} = useLegacyStore(ConfigStore);
|
|
|
+
|
|
|
+ const {
|
|
|
+ isLoading,
|
|
|
+ docs: docsConfig,
|
|
|
+ dsn,
|
|
|
+ projectKeyId,
|
|
|
+ refetch,
|
|
|
+ } = useLoadGettingStarted({
|
|
|
+ orgSlug: organization.slug,
|
|
|
+ projSlug: project.slug,
|
|
|
+ platform,
|
|
|
+ });
|
|
|
|
|
|
- const {project, integrationSlug} = props;
|
|
|
+ const selectedPlatformOptions = useUrlPlatformOptions(docsConfig?.platformOptions);
|
|
|
|
|
|
const api = useApi();
|
|
|
const fetchData = useCallback(() => {
|
|
@@ -64,10 +108,7 @@ function IntegrationSetup(props: Props) {
|
|
|
|
|
|
const loadingError = (
|
|
|
<LoadingError
|
|
|
- message={t(
|
|
|
- 'Failed to load the integration for the %s platform.',
|
|
|
- project?.platform ?? 'other'
|
|
|
- )}
|
|
|
+ message={t('Failed to load the integration for the %s platform.', platform.name)}
|
|
|
onRetry={fetchData}
|
|
|
/>
|
|
|
);
|
|
@@ -78,26 +119,13 @@ function IntegrationSetup(props: Props) {
|
|
|
</Alert>
|
|
|
);
|
|
|
|
|
|
- const renderSetupInstructions = () => {
|
|
|
- const currentPlatform = project?.platform ?? 'other';
|
|
|
- return (
|
|
|
- <SetupIntroduction
|
|
|
- stepHeaderText={t(
|
|
|
- 'Automatically instrument %s',
|
|
|
- platforms.find(p => p.id === currentPlatform)?.name ?? ''
|
|
|
- )}
|
|
|
- platform={currentPlatform}
|
|
|
- />
|
|
|
- );
|
|
|
- };
|
|
|
const renderIntegrationInstructions = () => {
|
|
|
- if (!provider || !project) {
|
|
|
+ if (!provider) {
|
|
|
return null;
|
|
|
}
|
|
|
|
|
|
return (
|
|
|
<Fragment>
|
|
|
- {renderSetupInstructions()}
|
|
|
<motion.p
|
|
|
variants={{
|
|
|
initial: {opacity: 0},
|
|
@@ -131,43 +159,26 @@ function IntegrationSetup(props: Props) {
|
|
|
</motion.div>
|
|
|
|
|
|
<DocsWrapper>
|
|
|
- <StyledButtonBar gap={1}>
|
|
|
- <AddIntegrationButton
|
|
|
- provider={provider}
|
|
|
- onAddIntegration={() => setInstalled(true)}
|
|
|
- organization={organization}
|
|
|
- priority="primary"
|
|
|
- size="sm"
|
|
|
- analyticsParams={{view: 'onboarding', already_installed: false}}
|
|
|
- modalParams={{projectId: project.id}}
|
|
|
- />
|
|
|
- <Button
|
|
|
- size="sm"
|
|
|
- onClick={() => {
|
|
|
- props.onClickManualSetup?.();
|
|
|
- trackIntegrationAnalytics('integrations.switch_manual_sdk_setup', {
|
|
|
- integration_type: 'first_party',
|
|
|
- integration: integrationSlug,
|
|
|
- view: 'onboarding',
|
|
|
- organization,
|
|
|
- });
|
|
|
- }}
|
|
|
- >
|
|
|
- {t('Manual Setup')}
|
|
|
- </Button>
|
|
|
- </StyledButtonBar>
|
|
|
+ <AddIntegrationButton
|
|
|
+ provider={provider}
|
|
|
+ onAddIntegration={() => setInstalled(true)}
|
|
|
+ organization={organization}
|
|
|
+ priority="primary"
|
|
|
+ size="sm"
|
|
|
+ analyticsParams={{view: 'onboarding', already_installed: false}}
|
|
|
+ modalParams={{projectId: project.id}}
|
|
|
+ />
|
|
|
</DocsWrapper>
|
|
|
</Fragment>
|
|
|
);
|
|
|
};
|
|
|
|
|
|
const renderPostInstallInstructions = () => {
|
|
|
- if (!project || !provider) {
|
|
|
+ if (!provider) {
|
|
|
return null;
|
|
|
}
|
|
|
return (
|
|
|
<Fragment>
|
|
|
- {renderSetupInstructions()}
|
|
|
<PostInstallCodeSnippet
|
|
|
provider={provider}
|
|
|
platform={project.platform}
|
|
@@ -187,8 +198,53 @@ function IntegrationSetup(props: Props) {
|
|
|
);
|
|
|
};
|
|
|
|
|
|
+ if (isLoading) {
|
|
|
+ return <LoadingIndicator />;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!docsConfig || !dsn || !projectKeyId) {
|
|
|
+ return (
|
|
|
+ <LoadingError
|
|
|
+ message={t(
|
|
|
+ 'The getting started documentation for this platform is currently unavailable.'
|
|
|
+ )}
|
|
|
+ onRetry={refetch}
|
|
|
+ />
|
|
|
+ );
|
|
|
+ }
|
|
|
+
|
|
|
+ const docParams: DocsParams<any> = {
|
|
|
+ api,
|
|
|
+ projectKeyId,
|
|
|
+ dsn,
|
|
|
+ organization,
|
|
|
+ platformKey: platform.id,
|
|
|
+ projectId: project.id,
|
|
|
+ projectSlug: project.slug,
|
|
|
+ isFeedbackSelected: false,
|
|
|
+ isPerformanceSelected: false,
|
|
|
+ isProfilingSelected: false,
|
|
|
+ isReplaySelected: false,
|
|
|
+ isSelfHosted,
|
|
|
+ platformOptions: selectedPlatformOptions,
|
|
|
+ sourcePackageRegistries: {
|
|
|
+ isLoading: false,
|
|
|
+ data: undefined,
|
|
|
+ },
|
|
|
+ urlPrefix,
|
|
|
+ };
|
|
|
+
|
|
|
return (
|
|
|
<Fragment>
|
|
|
+ <SetupIntroduction
|
|
|
+ stepHeaderText={t('Automatically instrument %s SDK', platform.name)}
|
|
|
+ platform={platform.id}
|
|
|
+ />
|
|
|
+ <PlatformOptionsControl
|
|
|
+ platformOptions={platformOptions}
|
|
|
+ onChange={docsConfig.onboarding.onPlatformOptionsChange?.(docParams)}
|
|
|
+ />
|
|
|
+ <Divider />
|
|
|
{installed ? renderPostInstallInstructions() : renderIntegrationInstructions()}
|
|
|
{getDynamicText({
|
|
|
value: !hasError ? null : loadingError,
|
|
@@ -206,15 +262,12 @@ DocsWrapper.defaultProps = {
|
|
|
exit: {opacity: 0},
|
|
|
};
|
|
|
|
|
|
-const StyledButtonBar = styled(ButtonBar)`
|
|
|
- margin-top: ${space(3)};
|
|
|
- width: max-content;
|
|
|
-
|
|
|
- @media (max-width: ${p => p.theme.breakpoints.small}) {
|
|
|
- width: auto;
|
|
|
- grid-row-gap: ${space(1)};
|
|
|
- grid-auto-flow: row;
|
|
|
- }
|
|
|
+const Divider = styled('hr')`
|
|
|
+ height: 1px;
|
|
|
+ width: 100%;
|
|
|
+ background: ${p => p.theme.border};
|
|
|
+ border: none;
|
|
|
+ margin-bottom: ${space(3)};
|
|
|
`;
|
|
|
|
|
|
export default IntegrationSetup;
|