Skip to content

Commit 8d57c45

Browse files
authored
show hipaa check status on the compliance dashboard (#4227)
1 parent 05acfee commit 8d57c45

File tree

14 files changed

+200
-85
lines changed

14 files changed

+200
-85
lines changed

api/server/handlers/cluster/compliance_checks.go

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,8 @@ func NewListComplianceChecksHandler(
3333

3434
// ListComplianceChecksRequest is the expected format for a request to /compliance/checks
3535
type ListComplianceChecksRequest struct {
36-
Vendor compliance.Vendor `schema:"vendor"`
36+
Vendor compliance.Vendor `schema:"vendor"`
37+
Profile compliance.Profile `schema:"profile"`
3738
}
3839

3940
// ListComplianceChecksResponse is the expected format for a response from /compliance/checks
@@ -69,10 +70,25 @@ func (c *ListComplianceChecksHandler) ServeHTTP(w http.ResponseWriter, r *http.R
6970
}
7071
}
7172

73+
var profile porterv1.EnumComplianceProfile
74+
if request.Profile != "" {
75+
switch request.Profile {
76+
case compliance.Profile_SOC2:
77+
profile = porterv1.EnumComplianceProfile_ENUM_COMPLIANCE_PROFILE_SOC2
78+
case compliance.Profile_HIPAA:
79+
profile = porterv1.EnumComplianceProfile_ENUM_COMPLIANCE_PROFILE_HIPAA
80+
default:
81+
err := telemetry.Error(ctx, span, nil, "invalid profile")
82+
c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusBadRequest))
83+
return
84+
}
85+
}
86+
7287
req := connect.NewRequest(&porterv1.ContractComplianceChecksRequest{
7388
ProjectId: int64(project.ID),
7489
ClusterId: int64(cluster.ID),
7590
Vendor: vendor,
91+
Profile: profile,
7692
})
7793

7894
ccpResp, err := c.Config().ClusterControlPlaneClient.ContractComplianceChecks(ctx, req)

dashboard/package-lock.json

Lines changed: 7 additions & 7 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

dashboard/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@
102102
"@babel/preset-typescript": "^7.15.0",
103103
"@ianvs/prettier-plugin-sort-imports": "^4.1.1",
104104
"@pmmmwh/react-refresh-webpack-plugin": "^0.4.3",
105-
"@porter-dev/api-contracts": "^0.2.84",
105+
"@porter-dev/api-contracts": "^0.2.97",
106106
"@testing-library/jest-dom": "^4.2.4",
107107
"@testing-library/react": "^9.3.2",
108108
"@testing-library/user-event": "^7.1.2",

dashboard/src/main/home/compliance-dashboard/ActionBanner.tsx

Lines changed: 26 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import React, { useMemo, type Dispatch, type SetStateAction } from "react";
22
import { useHistory } from "react-router";
3+
import { match } from "ts-pattern";
34

45
import Banner from "components/porter/Banner";
56
import Image from "components/porter/Image";
@@ -20,10 +21,11 @@ export const ActionBanner: React.FC<ActionBannerProps> = ({
2021
}) => {
2122
const history = useHistory();
2223
const {
24+
profile,
2325
updateInProgress,
2426
latestContractDB,
2527
latestContractProto,
26-
updateContractWithSOC2,
28+
updateContractWithProfile,
2729
} = useCompliance();
2830

2931
const provisioningStatus = useMemo(() => {
@@ -60,21 +62,30 @@ export const ActionBanner: React.FC<ActionBannerProps> = ({
6062
return provisioningStatus.state === "pending" || updateInProgress;
6163
}, [provisioningStatus.state, updateInProgress]);
6264

65+
const complianceEnabled = match(profile)
66+
.with("soc2", () => latestContractProto?.complianceProfiles?.soc2)
67+
.with("hipaa", () => latestContractProto?.complianceProfiles?.hipaa)
68+
.exhaustive();
69+
6370
// check if compliance has not been enable or if not all checks have passed
6471
const actionRequredWithoutProvisioningError = useMemo(() => {
6572
return (
66-
provisioningStatus.state === "compliance_error" ||
67-
!latestContractProto?.cluster?.isSoc2Compliant
73+
provisioningStatus.state === "compliance_error" || !complianceEnabled
6874
);
69-
}, [provisioningStatus.state, latestContractProto?.toJsonString()]);
75+
}, [
76+
provisioningStatus.state,
77+
latestContractProto?.toJsonString(),
78+
complianceEnabled,
79+
]);
7080

7181
// check if provisioning error is due to compliance update
7282
const provisioningErrorWithComplianceEnabled = useMemo(() => {
73-
return (
74-
provisioningStatus.state === "failed" &&
75-
latestContractProto?.cluster?.isSoc2Compliant
76-
);
77-
}, [provisioningStatus.state, latestContractProto?.toJsonString()]);
83+
return provisioningStatus.state === "failed" && complianceEnabled;
84+
}, [
85+
provisioningStatus.state,
86+
latestContractProto?.toJsonString(),
87+
complianceEnabled,
88+
]);
7889

7990
if (isInfraPending) {
8091
return (
@@ -83,8 +94,8 @@ export const ActionBanner: React.FC<ActionBannerProps> = ({
8394
<Image src={loading_img} style={{ height: "16px", width: "16px" }} />
8495
}
8596
>
86-
SOC 2 infrastructure controls are being enabled. Note: This may take up
87-
to 30 minutes.
97+
Infrastructure controls are being enabled. Note: This may take up to 30
98+
minutes.
8899
</Banner>
89100
);
90101
}
@@ -101,7 +112,7 @@ export const ActionBanner: React.FC<ActionBannerProps> = ({
101112
cursor: "pointer",
102113
}}
103114
onClick={() => {
104-
void updateContractWithSOC2();
115+
void updateContractWithProfile();
105116
}}
106117
>
107118
Re-run infrastructure controls
@@ -116,7 +127,8 @@ export const ActionBanner: React.FC<ActionBannerProps> = ({
116127
setShowCostConsentModal(true);
117128
}}
118129
>
119-
Enable SOC 2 infrastructure controls
130+
Enable {profile === "soc2" ? "SOC2" : "HIPAA"} infrastructure
131+
controls
120132
</Text>
121133
)}
122134
</Banner>
@@ -146,7 +158,7 @@ export const ActionBanner: React.FC<ActionBannerProps> = ({
146158
cursor: "pointer",
147159
}}
148160
onClick={() => {
149-
void updateContractWithSOC2();
161+
void updateContractWithProfile();
150162
}}
151163
>
152164
<Image src={refresh} size={12} style={{ marginBottom: "-2px" }} />

dashboard/src/main/home/compliance-dashboard/ComplianceContext.tsx

Lines changed: 33 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,17 @@
1-
import React, { createContext, useContext, useMemo, useState } from "react";
2-
import { Contract, EKS, EKSLogging } from "@porter-dev/api-contracts";
1+
import React, {
2+
createContext,
3+
useContext,
4+
useMemo,
5+
useState,
6+
type Dispatch,
7+
type SetStateAction,
8+
} from "react";
9+
import {
10+
ComplianceProfile,
11+
Contract,
12+
EKS,
13+
EKSLogging,
14+
} from "@porter-dev/api-contracts";
315
import { useQuery, useQueryClient } from "@tanstack/react-query";
416
import { match } from "ts-pattern";
517
import { z } from "zod";
@@ -15,6 +27,8 @@ import {
1527
type VendorCheck,
1628
} from "./types";
1729

30+
type ComplianceProfileType = "soc2" | "hipaa";
31+
1832
type ProjectComplianceContextType = {
1933
projectId: number;
2034
clusterId: number;
@@ -25,7 +39,9 @@ type ProjectComplianceContextType = {
2539
checksLoading: boolean;
2640
contractLoading: boolean;
2741
updateInProgress: boolean;
28-
updateContractWithSOC2: () => Promise<void>;
42+
profile: ComplianceProfileType;
43+
setProfile: Dispatch<SetStateAction<ComplianceProfileType>>;
44+
updateContractWithProfile: () => Promise<void>;
2945
};
3046

3147
const ProjectComplianceContext =
@@ -52,6 +68,7 @@ export const ProjectComplianceProvider: React.FC<
5268
> = ({ projectId, clusterId, children }) => {
5369
const queryClient = useQueryClient();
5470
const [updateInProgress, setUpdateInProgress] = useState(false);
71+
const [profile, setProfile] = useState<ComplianceProfileType>("soc2");
5572

5673
const { data: baseContract, isLoading: contractLoading } = useQuery(
5774
[projectId, clusterId, "getContracts"],
@@ -80,13 +97,14 @@ export const ProjectComplianceProvider: React.FC<
8097
projectId,
8198
clusterId,
8299
condition: baseContract?.condition ?? "",
100+
profile,
83101
name: "getComplianceChecks",
84102
},
85103
],
86104
async () => {
87105
const res = await api.getComplianceChecks(
88106
"<token>",
89-
{ vendor: "vanta" },
107+
{ vendor: "vanta", profile },
90108
{ projectId, clusterId }
91109
);
92110

@@ -114,7 +132,7 @@ export const ProjectComplianceProvider: React.FC<
114132
});
115133
}, [baseContract?.base64_contract]);
116134

117-
const updateContractWithSOC2 = async (): Promise<void> => {
135+
const updateContractWithProfile = async (): Promise<void> => {
118136
try {
119137
setUpdateInProgress(true);
120138

@@ -141,13 +159,20 @@ export const ProjectComplianceProvider: React.FC<
141159
}))
142160
.otherwise((kind) => kind);
143161

162+
const complianceProfiles = new ComplianceProfile({
163+
...latestContract.complianceProfiles,
164+
...(profile === "soc2" && { soc2: true }),
165+
...(profile === "hipaa" && { hipaa: true }),
166+
});
167+
144168
const updatedContract = new Contract({
145169
...latestContract,
146170
cluster: {
147171
...latestContract.cluster,
148172
kindValues: updatedKindValues,
149173
isSoc2Compliant: true,
150174
},
175+
complianceProfiles,
151176
});
152177

153178
await api.createContract("<token>", updatedContract, {
@@ -176,7 +201,9 @@ export const ProjectComplianceProvider: React.FC<
176201
checksLoading,
177202
contractLoading,
178203
updateInProgress,
179-
updateContractWithSOC2,
204+
profile,
205+
setProfile,
206+
updateContractWithProfile,
180207
}}
181208
>
182209
{children}

dashboard/src/main/home/compliance-dashboard/ComplianceDashboard.tsx

Lines changed: 2 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -2,20 +2,16 @@ import React, { useContext, useState } from "react";
22
import styled from "styled-components";
33

44
import ClusterProvisioningPlaceholder from "components/ClusterProvisioningPlaceholder";
5-
import Container from "components/porter/Container";
6-
import Image from "components/porter/Image";
75
import Spacer from "components/porter/Spacer";
8-
import Text from "components/porter/Text";
96
import DashboardHeader from "main/home/cluster-dashboard/DashboardHeader";
107

118
import { Context } from "shared/Context";
129
import compliance from "assets/compliance.svg";
13-
import linkExternal from "assets/link-external.svg";
14-
import vanta from "assets/vanta.svg";
1510

1611
import { ActionBanner } from "./ActionBanner";
1712
import { ProjectComplianceProvider } from "./ComplianceContext";
1813
import { ConfigSelectors } from "./ConfigSelectors";
14+
import { ProfileHeader } from "./ProfileHeader";
1915
import { SOC2CostConsent } from "./SOC2CostConsent";
2016
import { VendorChecksList } from "./VendorChecksList";
2117

@@ -46,29 +42,8 @@ const ComplianceDashboard: React.FC = () => {
4642
<>
4743
<ConfigSelectors />
4844
<Spacer y={1} />
49-
<Container row>
50-
<Image src={vanta} size={25} />
51-
<Spacer inline x={1} />
52-
<Text
53-
size={21}
54-
additionalStyles=":hover { text-decoration: underline } cursor: pointer;"
55-
onClick={() => {
56-
window.open(
57-
"https://app.vanta.com/tests?framework=soc2&service=aws&taskType=TEST",
58-
"_blank"
59-
);
60-
}}
61-
>
62-
AWS SOC 2 Controls (Vanta)
63-
<Spacer inline x={0.5} />
64-
<Image
65-
src={linkExternal}
66-
size={16}
67-
additionalStyles="margin-bottom: -2px"
68-
/>
69-
</Text>
70-
</Container>
7145

46+
<ProfileHeader />
7247
<Spacer y={1} />
7348

7449
<ActionBanner setShowCostConsentModal={setShowCostConsentModal} />

dashboard/src/main/home/compliance-dashboard/ConfigSelectors.tsx

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,22 +11,30 @@ import provider from "assets/provider.svg";
1111
import typeSvg from "assets/type.svg";
1212
import vanta from "assets/vanta.svg";
1313

14+
import { useCompliance } from "./ComplianceContext";
15+
1416
export const ConfigSelectors: React.FC = () => {
15-
// to be made selectable with state living in context
17+
const { profile, setProfile } = useCompliance();
1618
return (
1719
<Container row>
1820
<Select
1921
options={[
20-
{ value: "soc-2", label: "SOC 2" },
22+
{ value: "soc2", label: "SOC 2" },
2123
{
2224
value: "hipaa",
23-
label: "HIPAA (request access)",
24-
disabled: true,
25+
label: "HIPAA",
2526
},
2627
]}
2728
width="200px"
28-
value={"soc-2"}
29-
setValue={() => {}}
29+
value={profile}
30+
setValue={(value) => {
31+
if (value === "soc2") {
32+
setProfile("soc2");
33+
return;
34+
}
35+
36+
setProfile("hipaa");
37+
}}
3038
prefix={
3139
<Container row>
3240
<Image src={framework} size={15} opacity={0.6} />

0 commit comments

Comments
 (0)