|
1 |
| -import {Component, Fragment} from 'react'; |
| 1 | +import {Component, type Dispatch, Fragment, type SetStateAction, useState} from 'react'; |
2 | 2 | import {components} from 'react-select';
|
3 | 3 | import styled from '@emotion/styled';
|
4 | 4 | import type {Query} from 'history';
|
5 | 5 |
|
6 | 6 | import type {ModalRenderProps} from 'sentry/actionCreators/modal';
|
7 |
| -import DeprecatedAsyncComponent from 'sentry/components/deprecatedAsyncComponent'; |
8 | 7 | import type {StylesConfig} from 'sentry/components/forms/controls/selectControl';
|
9 | 8 | import SelectControl from 'sentry/components/forms/controls/selectControl';
|
10 | 9 | import IdBadge from 'sentry/components/idBadge';
|
11 | 10 | import Link from 'sentry/components/links/link';
|
| 11 | +import LoadingError from 'sentry/components/loadingError'; |
12 | 12 | import LoadingIndicator from 'sentry/components/loadingIndicator';
|
13 | 13 | import {t, tct} from 'sentry/locale';
|
14 | 14 | import ConfigStore from 'sentry/stores/configStore';
|
15 | 15 | import OrganizationsStore from 'sentry/stores/organizationsStore';
|
16 | 16 | import OrganizationStore from 'sentry/stores/organizationStore';
|
| 17 | +import {useLegacyStore} from 'sentry/stores/useLegacyStore'; |
17 | 18 | import {space} from 'sentry/styles/space';
|
18 | 19 | import type {Integration} from 'sentry/types/integrations';
|
19 | 20 | import type {Organization} from 'sentry/types/organization';
|
20 | 21 | import type {Project} from 'sentry/types/project';
|
21 | 22 | import Projects from 'sentry/utils/projects';
|
| 23 | +import {useApiQuery} from 'sentry/utils/queryClient'; |
22 | 24 | import replaceRouterParams from 'sentry/utils/replaceRouterParams';
|
23 | 25 | import IntegrationIcon from 'sentry/views/settings/organizationIntegrations/integrationIcon';
|
24 | 26 |
|
@@ -61,7 +63,7 @@ type Props = SharedProps & {
|
61 | 63 | /**
|
62 | 64 | * Organization slug
|
63 | 65 | */
|
64 |
| - organization: string; |
| 66 | + organization: string | undefined; |
65 | 67 |
|
66 | 68 | /**
|
67 | 69 | * List of available organizations
|
@@ -128,7 +130,7 @@ class ContextPickerModal extends Component<Props> {
|
128 | 130 | navigateIfFinish = (
|
129 | 131 | organizations: Array<{slug: string}>,
|
130 | 132 | projects: Array<{slug: string}>,
|
131 |
| - latestOrg: string = this.props.organization |
| 133 | + latestOrg = this.props.organization |
132 | 134 | ) => {
|
133 | 135 | const {needProject, onFinish, nextPath, integrationConfigs} = this.props;
|
134 | 136 | const {isSuperuser} = ConfigStore.get('user') || {};
|
@@ -411,103 +413,98 @@ type ContainerProps = SharedProps & {
|
411 | 413 | * List of slugs we want to be able to choose from
|
412 | 414 | */
|
413 | 415 | projectSlugs?: string[];
|
414 |
| -} & DeprecatedAsyncComponent['props']; |
| 416 | +}; |
415 | 417 |
|
416 |
| -type ContainerState = { |
417 |
| - organizations: Organization[]; |
418 |
| - integrationConfigs?: Integration[]; |
419 |
| - selectedOrganization?: string; |
420 |
| -} & DeprecatedAsyncComponent['state']; |
421 |
| - |
422 |
| -class ContextPickerModalContainer extends DeprecatedAsyncComponent< |
423 |
| - ContainerProps, |
424 |
| - ContainerState |
425 |
| -> { |
426 |
| - getDefaultState() { |
427 |
| - const storeState = OrganizationStore.get(); |
428 |
| - return { |
429 |
| - ...super.getDefaultState(), |
430 |
| - organizations: OrganizationsStore.getAll(), |
431 |
| - selectedOrganization: storeState.organization?.slug, |
432 |
| - }; |
433 |
| - } |
| 418 | +export default function ContextPickerModalContainer(props: ContainerProps) { |
| 419 | + const {configUrl, projectSlugs, ...sharedProps} = props; |
434 | 420 |
|
435 |
| - getEndpoints(): ReturnType<DeprecatedAsyncComponent['getEndpoints']> { |
436 |
| - const {configUrl} = this.props; |
437 |
| - if (configUrl) { |
438 |
| - return [['integrationConfigs', configUrl]]; |
439 |
| - } |
440 |
| - return []; |
441 |
| - } |
| 421 | + const {organizations} = useLegacyStore(OrganizationsStore); |
442 | 422 |
|
443 |
| - componentWillUnmount() { |
444 |
| - this.unlistener?.(); |
445 |
| - } |
| 423 | + const {organization} = useLegacyStore(OrganizationStore); |
| 424 | + const [selectedOrgSlug, setSelectedOrgSlug] = useState(organization?.slug); |
446 | 425 |
|
447 |
| - unlistener = OrganizationsStore.listen( |
448 |
| - (organizations: Organization[]) => this.setState({organizations}), |
449 |
| - undefined |
450 |
| - ); |
451 |
| - |
452 |
| - handleSelectOrganization = (organizationSlug: string) => { |
453 |
| - this.setState({selectedOrganization: organizationSlug}); |
454 |
| - }; |
455 |
| - |
456 |
| - renderModal({ |
457 |
| - projects, |
458 |
| - initiallyLoaded, |
459 |
| - integrationConfigs, |
460 |
| - }: { |
461 |
| - initiallyLoaded?: boolean; |
462 |
| - integrationConfigs?: Integration[]; |
463 |
| - projects?: Project[]; |
464 |
| - }) { |
| 426 | + if (configUrl) { |
465 | 427 | return (
|
466 |
| - <ContextPickerModal |
467 |
| - {...this.props} |
468 |
| - projects={projects || []} |
469 |
| - loading={!initiallyLoaded} |
470 |
| - organizations={this.state.organizations} |
471 |
| - organization={this.state.selectedOrganization!} |
472 |
| - onSelectOrganization={this.handleSelectOrganization} |
473 |
| - integrationConfigs={integrationConfigs || []} |
474 |
| - allowAllProjectsSelection={this.props.allowAllProjectsSelection} |
| 428 | + <ConfigUrlContainer |
| 429 | + configUrl={configUrl} |
| 430 | + selectedOrgSlug={selectedOrgSlug} |
| 431 | + setSelectedOrgSlug={setSelectedOrgSlug} |
| 432 | + {...sharedProps} |
475 | 433 | />
|
476 | 434 | );
|
477 | 435 | }
|
| 436 | + if (selectedOrgSlug) { |
| 437 | + return ( |
| 438 | + <Projects |
| 439 | + orgId={selectedOrgSlug} |
| 440 | + allProjects={!projectSlugs?.length} |
| 441 | + slugs={projectSlugs} |
| 442 | + > |
| 443 | + {({projects, initiallyLoaded}) => ( |
| 444 | + <ContextPickerModal |
| 445 | + {...sharedProps} |
| 446 | + projects={projects as Project[]} |
| 447 | + loading={!initiallyLoaded} |
| 448 | + organizations={organizations} |
| 449 | + organization={selectedOrgSlug} |
| 450 | + onSelectOrganization={setSelectedOrgSlug} |
| 451 | + integrationConfigs={[]} |
| 452 | + /> |
| 453 | + )} |
| 454 | + </Projects> |
| 455 | + ); |
| 456 | + } |
478 | 457 |
|
479 |
| - render() { |
480 |
| - const {projectSlugs, configUrl} = this.props; |
| 458 | + return ( |
| 459 | + <ContextPickerModal |
| 460 | + {...sharedProps} |
| 461 | + projects={[]} |
| 462 | + loading |
| 463 | + organizations={organizations} |
| 464 | + organization={selectedOrgSlug!} |
| 465 | + onSelectOrganization={setSelectedOrgSlug} |
| 466 | + integrationConfigs={[]} |
| 467 | + /> |
| 468 | + ); |
| 469 | +} |
481 | 470 |
|
482 |
| - if (configUrl && this.state.loading) { |
483 |
| - return <LoadingIndicator />; |
484 |
| - } |
485 |
| - if (this.state.integrationConfigs?.length) { |
486 |
| - return this.renderModal({ |
487 |
| - integrationConfigs: this.state.integrationConfigs, |
488 |
| - initiallyLoaded: !this.state.loading, |
489 |
| - }); |
490 |
| - } |
491 |
| - if (this.state.selectedOrganization) { |
492 |
| - return ( |
493 |
| - <Projects |
494 |
| - orgId={this.state.selectedOrganization} |
495 |
| - allProjects={!projectSlugs?.length} |
496 |
| - slugs={projectSlugs} |
497 |
| - > |
498 |
| - {({projects, initiallyLoaded}) => |
499 |
| - this.renderModal({projects: projects as Project[], initiallyLoaded}) |
500 |
| - } |
501 |
| - </Projects> |
502 |
| - ); |
503 |
| - } |
| 471 | +function ConfigUrlContainer( |
| 472 | + props: SharedProps & { |
| 473 | + configUrl: string; |
| 474 | + selectedOrgSlug: string | undefined; |
| 475 | + setSelectedOrgSlug: Dispatch<SetStateAction<string | undefined>>; |
| 476 | + } |
| 477 | +) { |
| 478 | + const {configUrl, selectedOrgSlug, setSelectedOrgSlug, ...sharedProps} = props; |
| 479 | + |
| 480 | + const {organizations} = useLegacyStore(OrganizationsStore); |
504 | 481 |
|
505 |
| - return this.renderModal({}); |
| 482 | + const {data, isError, isPending, refetch} = useApiQuery<Integration[]>([configUrl], { |
| 483 | + staleTime: Infinity, |
| 484 | + }); |
| 485 | + |
| 486 | + if (isPending) { |
| 487 | + return <LoadingIndicator />; |
| 488 | + } |
| 489 | + if (isError) { |
| 490 | + return <LoadingError onRetry={refetch} />; |
506 | 491 | }
|
| 492 | + if (!data.length) { |
| 493 | + sharedProps.onFinish(sharedProps.nextPath); |
| 494 | + } |
| 495 | + return ( |
| 496 | + <ContextPickerModal |
| 497 | + {...sharedProps} |
| 498 | + projects={[]} |
| 499 | + loading={isPending} |
| 500 | + organizations={organizations} |
| 501 | + organization={selectedOrgSlug!} |
| 502 | + onSelectOrganization={setSelectedOrgSlug} |
| 503 | + integrationConfigs={data} |
| 504 | + /> |
| 505 | + ); |
507 | 506 | }
|
508 | 507 |
|
509 |
| -export default ContextPickerModalContainer; |
510 |
| - |
511 | 508 | const StyledSelectControl = styled(SelectControl)`
|
512 | 509 | margin-top: ${space(1)};
|
513 | 510 | `;
|
|
0 commit comments