123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202 |
- import {Fragment} from 'react';
- import type {WithRouterProps} from 'react-router';
- import styled from '@emotion/styled';
- import DeprecatedAsyncComponent from 'sentry/components/deprecatedAsyncComponent';
- import EmptyMessage from 'sentry/components/emptyMessage';
- import Form from 'sentry/components/forms/form';
- import JsonForm from 'sentry/components/forms/jsonForm';
- import Pagination from 'sentry/components/pagination';
- import Panel from 'sentry/components/panels/panel';
- import PanelBody from 'sentry/components/panels/panelBody';
- import PanelHeader from 'sentry/components/panels/panelHeader';
- import {t} from 'sentry/locale';
- import ConfigStore from 'sentry/stores/configStore';
- import {space} from 'sentry/styles/space';
- import {Organization, Project} from 'sentry/types';
- import {sortProjects} from 'sentry/utils';
- import parseLinkHeader from 'sentry/utils/parseLinkHeader';
- import withSentryRouter from 'sentry/utils/withSentryRouter';
- import {
- MIN_PROJECTS_FOR_SEARCH,
- NotificationSettingsByProviderObject,
- NotificationSettingsObject,
- } from 'sentry/views/settings/account/notifications/constants';
- import {OrganizationSelectHeader} from 'sentry/views/settings/account/notifications/organizationSelectHeader';
- import {
- getParentData,
- getParentField,
- groupByOrganization,
- } from 'sentry/views/settings/account/notifications/utils';
- import {RenderSearch} from 'sentry/views/settings/components/defaultSearchBar';
- export type NotificationSettingsByProjectsBaseProps = {
- notificationSettings: NotificationSettingsObject;
- notificationType: string;
- onChange: (
- changedData: NotificationSettingsByProviderObject,
- parentId: string
- ) => NotificationSettingsObject;
- onSubmitSuccess: () => void;
- };
- type Props = {
- organizations: Organization[];
- } & NotificationSettingsByProjectsBaseProps &
- DeprecatedAsyncComponent['props'] &
- WithRouterProps;
- type State = {
- projects: Project[];
- } & DeprecatedAsyncComponent['state'];
- class NotificationSettingsByProjects extends DeprecatedAsyncComponent<Props, State> {
- getDefaultState(): State {
- return {
- ...super.getDefaultState(),
- projects: [],
- };
- }
- getEndpoints(): ReturnType<DeprecatedAsyncComponent['getEndpoints']> {
- const organizationId = this.getOrganizationId();
- return [
- [
- 'projects',
- `/projects/`,
- {
- query: {
- organizationId,
- cursor: this.props.location.query.cursor,
- },
- },
- ],
- ];
- }
- getOrganizationId(): string | undefined {
- const {location, organizations} = this.props;
- const customerDomain = ConfigStore.get('customerDomain');
- const orgFromSubdomain = organizations.find(
- ({slug}) => slug === customerDomain?.subdomain
- )?.id;
- return location?.query?.organizationId ?? orgFromSubdomain ?? organizations[0]?.id;
- }
- /**
- * Check the notification settings for how many projects there are.
- */
- getProjectCount = (): number => {
- const {notificationType, notificationSettings} = this.props;
- return Object.values(notificationSettings[notificationType]?.project || {}).length;
- };
- /**
- * The UI expects projects to be grouped by organization but can also use
- * this function to make a single group with all organizations.
- */
- getGroupedProjects = (): {[key: string]: Project[]} => {
- const {projects: stateProjects} = this.state;
- return Object.fromEntries(
- Object.values(groupByOrganization(sortProjects(stateProjects))).map(
- ({organization, projects}) => [`${organization.name} Projects`, projects]
- )
- );
- };
- handleOrgChange = (organizationId: string) => {
- this.props.router.replace({
- ...this.props.location,
- query: {organizationId},
- });
- };
- renderBody() {
- const {notificationType, notificationSettings, onChange, onSubmitSuccess} =
- this.props;
- const {projects, projectsPageLinks} = this.state;
- const canSearch = this.getProjectCount() >= MIN_PROJECTS_FOR_SEARCH;
- const paginationObject = parseLinkHeader(projectsPageLinks ?? '');
- const hasMore = paginationObject?.next?.results;
- const hasPrevious = paginationObject?.previous?.results;
- const renderSearch: RenderSearch = ({defaultSearchBar}) => defaultSearchBar;
- const orgId = this.getOrganizationId();
- return (
- <Fragment>
- <Panel>
- <StyledPanelHeader>
- <OrganizationSelectHeader
- organizations={this.props.organizations}
- organizationId={orgId}
- handleOrgChange={this.handleOrgChange}
- />
- {canSearch &&
- this.renderSearchInput({
- stateKey: 'projects',
- url: `/projects/?organizationId=${orgId}`,
- placeholder: t('Search Projects'),
- children: renderSearch,
- })}
- </StyledPanelHeader>
- <PanelBody>
- <Form
- saveOnBlur
- apiMethod="PUT"
- apiEndpoint="/users/me/notification-settings/"
- initialData={getParentData(
- notificationType,
- notificationSettings,
- projects
- )}
- onSubmitSuccess={onSubmitSuccess}
- >
- {projects.length === 0 ? (
- <EmptyMessage>{t('No projects found')}</EmptyMessage>
- ) : (
- Object.entries(this.getGroupedProjects()).map(([groupTitle, parents]) => (
- <StyledJsonForm
- collapsible
- key={groupTitle}
- // title={groupTitle}
- fields={parents.map(parent =>
- getParentField(
- notificationType,
- notificationSettings,
- parent,
- onChange
- )
- )}
- />
- ))
- )}
- </Form>
- </PanelBody>
- </Panel>
- {canSearch && (hasMore || hasPrevious) && (
- <Pagination pageLinks={projectsPageLinks} />
- )}
- </Fragment>
- );
- }
- }
- export default withSentryRouter(NotificationSettingsByProjects);
- const StyledPanelHeader = styled(PanelHeader)`
- flex-wrap: wrap;
- gap: ${space(1)};
- & > form:last-child {
- flex-grow: 1;
- }
- `;
- export const StyledJsonForm = styled(JsonForm)`
- ${Panel} {
- border: 0;
- margin-bottom: 0;
- }
- `;
|