Browse Source

ref(tsc): Convert ProjectExpectCtReports to FC (#57746)

Convert to FC and useApiQuery.
Use `useOrganization` and `useParams` instead of receiving them via
props.

- Ref https://github.com/getsentry/frontend-tsc/issues/2
ArthurKnaus 1 year ago
parent
commit
fd1b255660

+ 72 - 73
static/app/views/settings/projectSecurityHeaders/csp.tsx

@@ -56,7 +56,7 @@ export default function ProjectCspReports() {
   const {
     data: keyList,
     isLoading: isLoadingKeyList,
-    error: keyListError,
+    isError: isKeyListError,
     refetch: refetchKeyList,
   } = useApiQuery<ProjectKey[]>([`/projects/${organization.slug}/${projectId}/keys/`], {
     staleTime: 0,
@@ -64,7 +64,7 @@ export default function ProjectCspReports() {
   const {
     data: project,
     isLoading: isLoadingProject,
-    error: projectError,
+    isError: isProjectError,
     refetch: refetchProject,
   } = useApiQuery<Project>([`/projects/${organization.slug}/${projectId}/`], {
     staleTime: 0,
@@ -74,7 +74,7 @@ export default function ProjectCspReports() {
     return <LoadingIndicator />;
   }
 
-  if (keyListError || projectError) {
+  if (isKeyListError || isProjectError) {
     return (
       <LoadingError
         onRetry={() => {
@@ -86,86 +86,85 @@ export default function ProjectCspReports() {
   }
 
   return (
-    <SentryDocumentTitle
-      title={routeTitleGen(t('Content Security Policy (CSP)'), projectId, false)}
-    >
-      <div>
-        <SettingsPageHeader title={t('Content Security Policy')} />
-
-        <PreviewFeature />
-
-        <ReportUri keyList={keyList} orgId={organization.slug} projectId={projectId} />
-
-        <Form
-          saveOnBlur
-          apiMethod="PUT"
-          initialData={project.options}
-          apiEndpoint={`/projects/${organization.slug}/${projectId}/`}
-        >
-          <Access access={['project:write']}>
-            {({hasAccess}) => <JsonForm disabled={!hasAccess} forms={formGroups} />}
-          </Access>
-        </Form>
-
-        <Panel>
-          <PanelHeader>{t('About')}</PanelHeader>
-
-          <PanelBody withPadding>
-            <p>
-              {tct(
-                `[link:Content Security Policy]
+    <div>
+      <SentryDocumentTitle
+        title={routeTitleGen(t('Content Security Policy (CSP)'), projectId, false)}
+      />
+      <SettingsPageHeader title={t('Content Security Policy')} />
+
+      <PreviewFeature />
+
+      <ReportUri keyList={keyList} orgId={organization.slug} projectId={projectId} />
+
+      <Form
+        saveOnBlur
+        apiMethod="PUT"
+        initialData={project.options}
+        apiEndpoint={`/projects/${organization.slug}/${projectId}/`}
+      >
+        <Access access={['project:write']}>
+          {({hasAccess}) => <JsonForm disabled={!hasAccess} forms={formGroups} />}
+        </Access>
+      </Form>
+
+      <Panel>
+        <PanelHeader>{t('About')}</PanelHeader>
+
+        <PanelBody withPadding>
+          <p>
+            {tct(
+              `[link:Content Security Policy]
             (CSP) is a security standard which helps prevent cross-site scripting (XSS),
             clickjacking and other code injection attacks resulting from execution of
             malicious content in the trusted web page context. It's enforced by browser
             vendors, and Sentry supports capturing CSP violations using the standard
             reporting hooks.`,
-                {
-                  link: (
-                    <ExternalLink href="https://en.wikipedia.org/wiki/Content_Security_Policy" />
-                  ),
-                }
-              )}
-            </p>
-
-            <p>
-              {tct(
-                `To configure [csp:CSP] reports
+              {
+                link: (
+                  <ExternalLink href="https://en.wikipedia.org/wiki/Content_Security_Policy" />
+                ),
+              }
+            )}
+          </p>
+
+          <p>
+            {tct(
+              `To configure [csp:CSP] reports
               in Sentry, you'll need to send a header from your server describing your
               policy, as well specifying the authenticated Sentry endpoint.`,
-                {
-                  csp: <abbr title="Content Security Policy" />,
-                }
-              )}
-            </p>
-
-            <p>
-              {t(
-                'For example, in Python you might achieve this via a simple web middleware'
-              )}
-            </p>
-            <pre>{getInstructions(keyList)}</pre>
-
-            <p>
-              {t(`Alternatively you can setup CSP reports to simply send reports rather than
+              {
+                csp: <abbr title="Content Security Policy" />,
+              }
+            )}
+          </p>
+
+          <p>
+            {t(
+              'For example, in Python you might achieve this via a simple web middleware'
+            )}
+          </p>
+          <pre>{getInstructions(keyList)}</pre>
+
+          <p>
+            {t(`Alternatively you can setup CSP reports to simply send reports rather than
               actually enforcing the policy`)}
-            </p>
-            <pre>{getReportOnlyInstructions(keyList)}</pre>
+          </p>
+          <pre>{getReportOnlyInstructions(keyList)}</pre>
 
-            <p>
-              {tct(
-                `We recommend setting this up to only run on a percentage of requests, as
+          <p>
+            {tct(
+              `We recommend setting this up to only run on a percentage of requests, as
               otherwise you may find that you've quickly exhausted your quota. For more
               information, take a look at [link:the article on html5rocks.com].`,
-                {
-                  link: (
-                    <ExternalLink href="http://www.html5rocks.com/en/tutorials/security/content-security-policy/" />
-                  ),
-                }
-              )}
-            </p>
-          </PanelBody>
-        </Panel>
-      </div>
-    </SentryDocumentTitle>
+              {
+                link: (
+                  <ExternalLink href="http://www.html5rocks.com/en/tutorials/security/content-security-policy/" />
+                ),
+              }
+            )}
+          </p>
+        </PanelBody>
+      </Panel>
+    </div>
   );
 }

+ 32 - 12
static/app/views/settings/projectSecurityHeaders/expectCt.spec.tsx

@@ -1,28 +1,48 @@
 import {initializeOrg} from 'sentry-test/initializeOrg';
-import {render} from 'sentry-test/reactTestingLibrary';
+import {render, screen} from 'sentry-test/reactTestingLibrary';
 
 import ProjectExpectCtReports from 'sentry/views/settings/projectSecurityHeaders/expectCt';
 
 describe('ProjectExpectCtReports', function () {
-  const {organization, project, routerProps} = initializeOrg();
-  const url = `/projects/${organization.slug}/${project.slug}/expect-ct/`;
+  const {organization, project} = initializeOrg();
+  const keysUrl = `/projects/${organization.slug}/${project.slug}/keys/`;
 
   beforeEach(function () {
     MockApiClient.clearMockResponses();
     MockApiClient.addMockResponse({
-      url: `/projects/${organization.slug}/${project.slug}/keys/`,
+      url: keysUrl,
       method: 'GET',
       body: [],
     });
   });
 
-  it('renders', function () {
-    render(
-      <ProjectExpectCtReports
-        {...routerProps}
-        location={TestStubs.location({pathname: url})}
-        organization={organization}
-      />
-    );
+  it('renders', async function () {
+    render(<ProjectExpectCtReports />, {
+      organization,
+    });
+
+    // Renders the loading indication initially
+    expect(screen.getByTestId('loading-indicator')).toBeInTheDocument();
+
+    // Heading
+    expect(
+      await screen.findByText('Certificate Transparency', {selector: 'h4'})
+    ).toBeInTheDocument();
+  });
+
+  it('renders loading error', async function () {
+    MockApiClient.addMockResponse({
+      url: keysUrl,
+      method: 'GET',
+      statusCode: 400,
+      body: {},
+    });
+    render(<ProjectExpectCtReports />, {
+      organization,
+    });
+
+    expect(
+      await screen.findByText('There was an error loading data.')
+    ).toBeInTheDocument();
   });
 });

+ 67 - 73
static/app/views/settings/projectSecurityHeaders/expectCt.tsx

@@ -1,100 +1,94 @@
-import {RouteComponentProps} from 'react-router';
-
 import ExternalLink from 'sentry/components/links/externalLink';
+import LoadingError from 'sentry/components/loadingError';
+import LoadingIndicator from 'sentry/components/loadingIndicator';
 import Panel from 'sentry/components/panels/panel';
 import PanelBody from 'sentry/components/panels/panelBody';
 import PanelHeader from 'sentry/components/panels/panelHeader';
 import PreviewFeature from 'sentry/components/previewFeature';
+import SentryDocumentTitle from 'sentry/components/sentryDocumentTitle';
 import {t, tct} from 'sentry/locale';
-import {Organization, ProjectKey} from 'sentry/types';
+import {ProjectKey} from 'sentry/types';
+import {useApiQuery} from 'sentry/utils/queryClient';
 import routeTitleGen from 'sentry/utils/routeTitle';
-import withOrganization from 'sentry/utils/withOrganization';
-import DeprecatedAsyncView from 'sentry/views/deprecatedAsyncView';
+import useOrganization from 'sentry/utils/useOrganization';
+import {useParams} from 'sentry/utils/useParams';
 import SettingsPageHeader from 'sentry/views/settings/components/settingsPageHeader';
 import ReportUri, {
   getSecurityDsn,
 } from 'sentry/views/settings/projectSecurityHeaders/reportUri';
 
-type Props = RouteComponentProps<{projectId: string}, {}> & {
-  organization: Organization;
-};
+function getInstructions(keyList: ProjectKey[]) {
+  return `Expect-CT: report-uri="${getSecurityDsn(keyList)}"`;
+}
 
-type State = {
-  keyList: null | ProjectKey[];
-} & DeprecatedAsyncView['state'];
+function ProjectExpectCtReports() {
+  const organization = useOrganization();
+  const {projectId} = useParams();
 
-class ProjectExpectCtReports extends DeprecatedAsyncView<Props, State> {
-  getEndpoints(): ReturnType<DeprecatedAsyncView['getEndpoints']> {
-    const {organization} = this.props;
-    const {projectId} = this.props.params;
-    return [['keyList', `/projects/${organization.slug}/${projectId}/keys/`]];
-  }
+  const {
+    data: keyList,
+    isLoading,
+    isError,
+    refetch,
+  } = useApiQuery<ProjectKey[]>([`/projects/${organization.slug}/${projectId}/keys/`], {
+    staleTime: 0,
+  });
 
-  getTitle() {
-    const {projectId} = this.props.params;
-    return routeTitleGen(t('Certificate Transparency (Expect-CT)'), projectId, false);
+  if (isLoading) {
+    return <LoadingIndicator />;
   }
 
-  getInstructions(keyList: ProjectKey[]) {
-    return `Expect-CT: report-uri="${getSecurityDsn(keyList)}"`;
+  if (isError) {
+    return <LoadingError onRetry={refetch} />;
   }
 
-  renderBody() {
-    const {organization, params} = this.props;
-    const {keyList} = this.state;
-    if (!keyList) {
-      return null;
-    }
+  return (
+    <div>
+      <SentryDocumentTitle
+        title={routeTitleGen(t('Certificate Transparency (Expect-CT)'), projectId, false)}
+      />
+      <SettingsPageHeader title={t('Certificate Transparency')} />
 
-    return (
-      <div>
-        <SettingsPageHeader title={t('Certificate Transparency')} />
+      <PreviewFeature />
 
-        <PreviewFeature />
+      <ReportUri keyList={keyList} orgId={organization.slug} projectId={projectId} />
 
-        <ReportUri
-          keyList={keyList}
-          orgId={organization.slug}
-          projectId={params.projectId}
-        />
-
-        <Panel>
-          <PanelHeader>{t('About')}</PanelHeader>
-          <PanelBody withPadding>
-            <p>
-              {tct(
-                `[link:Certificate Transparency]
+      <Panel>
+        <PanelHeader>{t('About')}</PanelHeader>
+        <PanelBody withPadding>
+          <p>
+            {tct(
+              `[link:Certificate Transparency]
       (CT) is a security standard which helps track and identify valid certificates, allowing identification of maliciously issued certificates`,
-                {
-                  link: (
-                    <ExternalLink href="https://en.wikipedia.org/wiki/Certificate_Transparency" />
-                  ),
-                }
-              )}
-            </p>
-            <p>
-              {tct(
-                "To configure reports in Sentry, you'll need to configure the [header] a header from your server:",
-                {
-                  header: <code>Expect-CT</code>,
-                }
-              )}
-            </p>
-
-            <pre>{this.getInstructions(keyList)}</pre>
-
-            <p>
-              {tct('For more information, see [link:the article on MDN].', {
+              {
                 link: (
-                  <ExternalLink href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Expect-CT" />
+                  <ExternalLink href="https://en.wikipedia.org/wiki/Certificate_Transparency" />
                 ),
-              })}
-            </p>
-          </PanelBody>
-        </Panel>
-      </div>
-    );
-  }
+              }
+            )}
+          </p>
+          <p>
+            {tct(
+              "To configure reports in Sentry, you'll need to configure the [header] a header from your server:",
+              {
+                header: <code>Expect-CT</code>,
+              }
+            )}
+          </p>
+
+          <pre>{getInstructions(keyList)}</pre>
+
+          <p>
+            {tct('For more information, see [link:the article on MDN].', {
+              link: (
+                <ExternalLink href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Expect-CT" />
+              ),
+            })}
+          </p>
+        </PanelBody>
+      </Panel>
+    </div>
+  );
 }
 
-export default withOrganization(ProjectExpectCtReports);
+export default ProjectExpectCtReports;