Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: Add override policy to env #295

Merged
merged 8 commits into from
Jan 29, 2025
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -48,10 +48,10 @@ export const DeploymentResourceDrawer: React.FC = () => {

const releaseFilter =
environment?.releaseChannels.find((rc) => rc.deploymentId === deploymentId)
?.filter ??
environment?.policy?.releaseChannels.find(
?.releaseFilter ??
environment?.policy.releaseChannels.find(
(rc) => rc.deploymentId === deploymentId,
)?.filter ??
)?.releaseFilter ??
undefined;

const jobFilter: JobCondition = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,15 @@
import React from "react";
import { useParams, useRouter, useSearchParams } from "next/navigation";
import {
IconCalendar,
IconChecklist,
IconDeviceRemote,
IconDotsVertical,
IconFilter,
IconInfoCircle,
IconLoader2,
IconLock,
IconPlant,
IconRocket,
IconTarget,
} from "@tabler/icons-react";

Expand All @@ -19,13 +23,8 @@ import { TabButton } from "../TabButton";
import { EnvironmentDropdownMenu } from "./EnvironmentDropdownMenu";
import { EditFilterForm } from "./Filter";
import { Overview } from "./Overview";
import { ReleaseChannels } from "./ReleaseChannels";

export enum EnvironmentDrawerTab {
Overview = "overview",
Resources = "resources",
ReleaseChannels = "release-channels",
}
import { UpdateOverridePolicy } from "./policy-override/UpdateOverride";
import { EnvironmentDrawerTab } from "./tabs";

const tabParam = "tab";
const useEnvironmentDrawerTab = () => {
Expand All @@ -44,7 +43,9 @@ const useEnvironmentDrawerTab = () => {
router.replace(`${url.pathname}?${url.searchParams.toString()}`);
};

return { tab, setTab };
const typedTab = tab != null ? (tab as EnvironmentDrawerTab) : null;

return { tab: typedTab, setTab };
};

const param = "environment_id";
Expand Down Expand Up @@ -97,6 +98,8 @@ export const EnvironmentDrawer: React.FC = () => {
const loading =
environmentQ.isLoading || workspaceQ.isLoading || deploymentsQ.isLoading;

const isUsingOverridePolicy = environment?.policy.isOverride ?? false;

return (
<Drawer open={isOpen} onOpenChange={setIsOpen}>
<DrawerContent
Expand Down Expand Up @@ -132,26 +135,78 @@ export const EnvironmentDrawer: React.FC = () => {

{!loading && (
<div className="flex w-full gap-6 p-6">
<div className="space-y-1">
<TabButton
active={tab === EnvironmentDrawerTab.Overview || tab == null}
onClick={() => setTab(EnvironmentDrawerTab.Overview)}
icon={<IconInfoCircle className="h-4 w-4" />}
label="Overview"
/>
<TabButton
active={tab === EnvironmentDrawerTab.Resources}
onClick={() => setTab(EnvironmentDrawerTab.Resources)}
icon={<IconTarget className="h-4 w-4" />}
label="Resources"
/>
<TabButton
active={tab === EnvironmentDrawerTab.ReleaseChannels}
onClick={() => setTab(EnvironmentDrawerTab.ReleaseChannels)}
icon={<IconFilter className="h-4 w-4" />}
label="Release Channels"
/>
</div>
{isUsingOverridePolicy && (
<div className="space-y-8">
<div className="space-y-1">
<h1 className="mb-2 text-sm font-medium">General</h1>
<TabButton
active={
tab === EnvironmentDrawerTab.Overview || tab == null
}
onClick={() => setTab(EnvironmentDrawerTab.Overview)}
icon={<IconInfoCircle className="h-4 w-4" />}
label="Overview"
/>
<TabButton
active={tab === EnvironmentDrawerTab.Resources}
onClick={() => setTab(EnvironmentDrawerTab.Resources)}
icon={<IconTarget className="h-4 w-4" />}
label="Resources"
/>
</div>

<div className="space-y-1">
<h1 className="mb-2 text-sm font-medium">Policy Settings</h1>
<TabButton
active={tab === EnvironmentDrawerTab.Approval}
onClick={() => setTab(EnvironmentDrawerTab.Approval)}
icon={<IconChecklist className="h-4 w-4" />}
label="Approval & Governance"
/>
<TabButton
active={tab === EnvironmentDrawerTab.Concurrency}
onClick={() => setTab(EnvironmentDrawerTab.Concurrency)}
icon={<IconLock className="h-4 w-4" />}
label="Deployment Control"
/>
<TabButton
active={tab === EnvironmentDrawerTab.Management}
onClick={() => setTab(EnvironmentDrawerTab.Management)}
icon={<IconRocket className="h-4 w-4" />}
label="Release Management"
/>
<TabButton
active={tab === EnvironmentDrawerTab.ReleaseChannels}
onClick={() => setTab(EnvironmentDrawerTab.ReleaseChannels)}
icon={<IconDeviceRemote className="h-4 w-4" />}
label="Release Channels"
/>
<TabButton
active={tab === EnvironmentDrawerTab.Rollout}
onClick={() => setTab(EnvironmentDrawerTab.Rollout)}
icon={<IconCalendar className="h-4 w-4" />}
label="Rollout and Timing"
/>
</div>
</div>
)}

{!isUsingOverridePolicy && (
<div className="space-y-1">
<TabButton
active={tab === EnvironmentDrawerTab.Overview || tab == null}
onClick={() => setTab(EnvironmentDrawerTab.Overview)}
icon={<IconInfoCircle className="h-4 w-4" />}
label="Overview"
/>
<TabButton
active={tab === EnvironmentDrawerTab.Resources}
onClick={() => setTab(EnvironmentDrawerTab.Resources)}
icon={<IconTarget className="h-4 w-4" />}
label="Resources"
/>
</div>
)}

{environment != null && (
<div className="w-full overflow-auto">
Expand All @@ -165,13 +220,14 @@ export const EnvironmentDrawer: React.FC = () => {
workspaceId={workspace.id}
/>
)}
{tab === EnvironmentDrawerTab.ReleaseChannels &&
deployments != null && (
<ReleaseChannels
environment={environment}
deployments={deployments}
/>
)}
{environment.policy.isOverride && (
<UpdateOverridePolicy
environment={environment}
environmentPolicy={environment.policy}
activeTab={tab ?? EnvironmentDrawerTab.Approval}
deployments={deployments ?? []}
/>
)}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Add error boundary for policy override component.

Consider wrapping the UpdateOverridePolicy component with an error boundary to handle potential rendering errors gracefully.

+import { ErrorBoundary } from 'react-error-boundary';

+const PolicyErrorFallback = () => (
+  <div className="p-4 text-red-500">
+    Failed to load policy settings. Please try again.
+  </div>
+);

 {environment.policy.isOverride && (
+  <ErrorBoundary FallbackComponent={PolicyErrorFallback}>
     <UpdateOverridePolicy
       environment={environment}
       environmentPolicy={environment.policy}
       activeTab={tab ?? EnvironmentDrawerTab.Approval}
       deployments={deployments ?? []}
     />
+  </ErrorBoundary>
 )}

Committable suggestion skipped: line range outside the PR's diff.

</div>
)}
</div>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import type { RouterOutputs } from "@ctrlplane/api";
import type * as SCHEMA from "@ctrlplane/db/schema";

import { ApprovalAndGovernance } from "../../policy-form-components/ApprovalAndGovernance";
import { DeploymentControl } from "../../policy-form-components/DeploymentControl";
import { ReleaseChannels } from "../../policy-form-components/ReleaseChannels";
import { ReleaseManagement } from "../../policy-form-components/ReleaseManagement";
import { RolloutAndTiming } from "../../policy-form-components/RolloutAndTiming";
import { EnvironmentDrawerTab } from "../tabs";
import { useUpdateOverridePolicy } from "./useOverridePolicy";

type Deployment = SCHEMA.Deployment & {
releaseChannels: SCHEMA.ReleaseChannel[];
};

type Policy = NonNullable<
NonNullable<RouterOutputs["environment"]["byId"]>["policy"]
>;

type UpdateOverridePolicyProps = {
environment: SCHEMA.Environment;
environmentPolicy: Policy;
activeTab: EnvironmentDrawerTab;
deployments: Deployment[];
};

export const UpdateOverridePolicy: React.FC<UpdateOverridePolicyProps> = ({
environment,
environmentPolicy,
activeTab,
deployments,
}) => {
const { onUpdate, isUpdating } = useUpdateOverridePolicy(
environment,
environmentPolicy,
);

return (
<>
{activeTab === EnvironmentDrawerTab.Approval && (
<ApprovalAndGovernance
environmentPolicy={environmentPolicy}
onUpdate={onUpdate}
isLoading={isUpdating}
/>
)}
{activeTab === EnvironmentDrawerTab.Concurrency && (
<DeploymentControl
environmentPolicy={environmentPolicy}
onUpdate={onUpdate}
isLoading={isUpdating}
/>
)}
{activeTab === EnvironmentDrawerTab.Management && (
<ReleaseManagement
environmentPolicy={environmentPolicy}
onUpdate={onUpdate}
isLoading={isUpdating}
/>
)}
{activeTab === EnvironmentDrawerTab.ReleaseChannels && (
<ReleaseChannels
environmentPolicy={environmentPolicy}
onUpdate={onUpdate}
isLoading={isUpdating}
deployments={deployments}
/>
)}
{activeTab === EnvironmentDrawerTab.Rollout && (
<RolloutAndTiming
environmentPolicy={environmentPolicy}
onUpdate={onUpdate}
isLoading={isUpdating}
/>
)}
</>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import type * as SCHEMA from "@ctrlplane/db/schema";

import { api } from "~/trpc/react";

const useInvalidateQueries = (
environmentId: string,
systemId: string,
policyId?: string,
) => {
const utils = api.useUtils();
const invalidateQueries = () => {
utils.environment.policy.byId.invalidate(policyId);
utils.environment.policy.bySystemId.invalidate(systemId);
utils.environment.byId.invalidate(environmentId);
};
return invalidateQueries;
};

export const useUpdateOverridePolicy = (
environment: SCHEMA.Environment,
policy: SCHEMA.EnvironmentPolicy,
) => {
const updatePolicy = api.environment.policy.update.useMutation();
const invalidateQueries = useInvalidateQueries(
environment.id,
environment.systemId,
policy.id,
);

const onUpdate = (data: SCHEMA.UpdateEnvironmentPolicy) =>
updatePolicy
.mutateAsync({
id: policy.id,
data,
})
.then(invalidateQueries);

return {
onUpdate,
isUpdating: updatePolicy.isPending,
};
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
export enum EnvironmentDrawerTab {
Overview = "overview",
Resources = "resources",
Approval = "approval",
Concurrency = "concurrency",
Management = "management",
ReleaseChannels = "release-channels",
Rollout = "rollout",
}
Loading
Loading