Skip to content
This repository was archived by the owner on Feb 6, 2026. It is now read-only.

Commit 0dfa169

Browse files
authored
Merge pull request #8228 from systeminit/jobelenus/view-policies
See policy results in the web app
2 parents 2f3f6e4 + b57c503 commit 0dfa169

17 files changed

Lines changed: 553 additions & 7 deletions

File tree

app/web/src/newhotness/Explore.vue

Lines changed: 55 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -511,6 +511,23 @@
511511
>
512512
<FuncRunList :limit="25" />
513513
</CollapsingGridItem>
514+
<CollapsingGridItem
515+
v-if="ffStore.SHOW_POLICIES"
516+
ref="policyRef"
517+
disableScroll
518+
>
519+
<template #header
520+
><span class="text-sm">Policy history</span></template
521+
>
522+
<PolicyList
523+
:policies="policyReports"
524+
:page="page"
525+
:maxPages="maxPages"
526+
@pageBack="pageBack"
527+
@pageForward="pageForward"
528+
@select="(p) => navigateToPolicy(p)"
529+
/>
530+
</CollapsingGridItem>
514531
</div>
515532
<div
516533
:class="
@@ -640,6 +657,7 @@ import ExploreGridSkeleton from "@/newhotness/skeletons/ExploreGridSkeleton.vue"
640657
import ExploreRightColumnSkeleton from "@/newhotness/skeletons/ExploreRightColumnSkeleton.vue";
641658
import { ChangeSet } from "@/api/sdf/dal/change_set";
642659
import WelcomeBanner from "@/newhotness/WelcomeBanner.vue";
660+
import { useFeatureFlagsStore } from "@/store/feature_flags.store";
643661
import MapComponent from "./Map.vue";
644662
import {
645663
collapsingGridStyles,
@@ -683,6 +701,8 @@ import { ExploreGridRowData } from "./explore_grid/ExploreGridRow.vue";
683701
import { useDefaultSubscription } from "./logic_composables/default_subscriptions";
684702
import { useContext } from "./logic_composables/context";
685703
import { generateMockActions } from "./logic_composables/mock_data";
704+
import PolicyList from "./layout_components/PolicyList.vue";
705+
import { Policy, usePolicy } from "./logic_composables/policy";
686706
687707
const router = useRouter();
688708
const route = useRoute();
@@ -729,22 +749,51 @@ const retrieveFilterAndGroup = (): SelectionsInQueryString => {
729749
return qString ? (JSON.parse(qString) as SelectionsInQueryString) : {};
730750
};
731751
752+
const ffStore = useFeatureFlagsStore();
753+
732754
const defaultSubscriptions = useDefaultSubscription();
733755
734756
const groupRef = ref<InstanceType<typeof TabGroupToggle>>();
735757
const actionsRef = ref<typeof CollapsingGridItem>();
736758
const historyRef = ref<typeof CollapsingGridItem>();
759+
const policyRef = ref<typeof CollapsingGridItem>();
737760
const mapRef = ref<InstanceType<typeof MapComponent>>();
738761
const exploreGridRef = ref<InstanceType<typeof ExploreGrid>>();
739762
const componentContextMenuRef =
740763
ref<InstanceType<typeof ComponentContextMenu>>();
741764
742-
const collapsingStyles = computed(() =>
743-
collapsingGridStyles([
744-
actionsRef.value?.openState,
745-
historyRef.value?.openState,
746-
]),
747-
);
765+
const collapsingStyles = computed(() => {
766+
const grids = [actionsRef.value?.openState, historyRef.value?.openState];
767+
if (ffStore.SHOW_POLICIES) grids.push(policyRef.value?.openState);
768+
769+
return collapsingGridStyles(grids);
770+
});
771+
772+
const { policyReports, page, maxPages } = usePolicy();
773+
774+
const pageBack = () => {
775+
if (page.value === 1) page.value = maxPages.value;
776+
else page.value -= 1;
777+
};
778+
const pageForward = () => {
779+
if (page.value === maxPages.value) page.value = 1;
780+
else page.value += 1;
781+
};
782+
783+
const navigateToPolicy = (policy: Policy) => {
784+
const params = {
785+
workspacePk: route.params.workspacePk,
786+
changeSetId: route.params.changeSetId,
787+
policyId: policy.id,
788+
};
789+
router.push({
790+
name: "new-hotness-policy",
791+
params,
792+
query: {
793+
page: page.value,
794+
},
795+
});
796+
};
748797
749798
const queryOnlyDiff = computed(() => {
750799
const query: SelectionsInQueryString = {
Lines changed: 217 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,217 @@
1+
<template>
2+
<div class="h-full overflow-y-auto pb-md">
3+
<header
4+
:class="
5+
clsx(
6+
'flex flex-row items-center gap-xs px-sm py-xs border-t border-b border-neutral-600',
7+
themeClasses('bg-neutral-200', 'bg-neutral-800'),
8+
)
9+
"
10+
>
11+
<NewButton
12+
tooltip="Close (Esc)"
13+
tooltipPlacement="top"
14+
icon="x"
15+
tone="empty"
16+
:class="
17+
clsx(
18+
'active:bg-white active:text-black',
19+
themeClasses('hover:bg-neutral-200', 'hover:bg-neutral-600'),
20+
)
21+
"
22+
@click="navigateBack"
23+
/>
24+
<template v-if="policy">
25+
<div
26+
class="shrink ml-auto mr-auto flex flex-row gap-sm items-center justify-center min-w-[32vw]"
27+
>
28+
<Icon
29+
class="shrink"
30+
size="xs"
31+
:name="policy.result === 'Fail' ? 'triangle' : 'check-square'"
32+
:tone="policy.result === 'Fail' ? 'destructive' : 'success'"
33+
/>
34+
<span class="grow"
35+
><TruncateWithTooltip class="py-2xs">{{
36+
policy.name
37+
}}</TruncateWithTooltip></span
38+
>
39+
<span class="shrink">
40+
<Timestamp
41+
refresh
42+
size="normal"
43+
relative="standard"
44+
showTimeIfToday
45+
:date="policy.createdAt"
46+
/>
47+
</span>
48+
</div>
49+
</template>
50+
</header>
51+
<div class="w-[70vw] ml-auto mr-auto flex flex-row">
52+
<div
53+
:class="
54+
clsx(
55+
'w-[25vw] pt-md pr-md border-r-[1px]',
56+
themeClasses('border-neutral-200', 'border-neutral-600'),
57+
)
58+
"
59+
>
60+
<h5 class="mb-sm text-sm ml-xs">Policy history</h5>
61+
<PolicyList
62+
:policies="policyReports"
63+
:page="page"
64+
:maxPages="maxPages"
65+
@pageBack="pageBack"
66+
@pageForward="pageForward"
67+
@select="(p) => navigateToPolicy(p)"
68+
/>
69+
</div>
70+
<div v-if="!policy" class="w-full">
71+
<EmptyStateCard
72+
iconName="no-changes"
73+
primaryText="Could not find this Policy Report"
74+
secondaryText="Please select another from the list of policy reports."
75+
/>
76+
</div>
77+
<section v-else class="w-full p-md">
78+
<div class="mb-lg">
79+
<MarkdownRender :source="policy.policy" />
80+
</div>
81+
<div
82+
:class="
83+
clsx(
84+
'border-t-[1px] pt-lg',
85+
themeClasses('border-neutral-200', 'border-neutral-600'),
86+
)
87+
"
88+
>
89+
<MarkdownRender :source="policy.report" />
90+
</div>
91+
</section>
92+
</div>
93+
</div>
94+
</template>
95+
96+
<script setup lang="ts">
97+
import { clsx } from "clsx";
98+
import { useRoute, useRouter } from "vue-router";
99+
import { computed, watch } from "vue";
100+
import {
101+
themeClasses,
102+
NewButton,
103+
Icon,
104+
TruncateWithTooltip,
105+
Timestamp,
106+
} from "@si/vue-lib/design-system";
107+
import { useQuery } from "@tanstack/vue-query";
108+
import EmptyStateCard from "@/components/EmptyStateCard.vue";
109+
import { Policy, usePolicy } from "./logic_composables/policy";
110+
import PolicyList from "./layout_components/PolicyList.vue";
111+
import MarkdownRender from "./MarkdownRender.vue";
112+
import { routes, useApi } from "./api_composables";
113+
import { useContext } from "./logic_composables/context";
114+
import { prevPage } from "./logic_composables/navigation_stack";
115+
116+
const router = useRouter();
117+
const route = useRoute();
118+
119+
// Navigate back to explore_grid view
120+
const navigateBack = () => {
121+
const lastPage = prevPage();
122+
// if we aren't coming from new-hotness, go to where we came from
123+
// usually component details
124+
if (lastPage && lastPage.name !== "new-hotness") {
125+
router.push({
126+
name: lastPage.name,
127+
params: lastPage.params,
128+
});
129+
} else {
130+
router.push({
131+
name: "new-hotness",
132+
params: {
133+
workspacePk: route.params.workspacePk,
134+
changeSetId: route.params.changeSetId,
135+
},
136+
query: { retainSessionState: 1 },
137+
});
138+
}
139+
};
140+
141+
const props = defineProps<{
142+
policyId: string;
143+
}>();
144+
145+
const { policyReports, page, maxPages } = usePolicy();
146+
147+
watch(
148+
route.query,
149+
() => {
150+
if (!route.query.page) return;
151+
152+
const urlPage = parseInt(route.query.page?.toString() || "1");
153+
if (urlPage && urlPage !== page.value) page.value = urlPage;
154+
},
155+
{ immediate: true },
156+
);
157+
158+
watch(
159+
page,
160+
() => {
161+
const urlPage = parseInt(route.query.page?.toString() || "1");
162+
if (urlPage !== page.value) {
163+
router.push({
164+
...route.params,
165+
query: { page: page.value },
166+
});
167+
}
168+
},
169+
{ immediate: true },
170+
);
171+
172+
const pageBack = () => {
173+
if (page.value === 1) page.value = maxPages.value;
174+
else page.value -= 1;
175+
};
176+
const pageForward = () => {
177+
if (page.value === maxPages.value) page.value = 1;
178+
else page.value += 1;
179+
};
180+
181+
const navigateToPolicy = (policy: Policy) => {
182+
router.push({
183+
name: "new-hotness-policy",
184+
params: {
185+
workspacePk: route.params.workspacePk,
186+
changeSetId: route.params.changeSetId,
187+
policyId: policy.id,
188+
},
189+
query: {
190+
page: page.value,
191+
},
192+
});
193+
};
194+
195+
const ctx = useContext();
196+
const api = useApi(ctx);
197+
const queryKey = computed(() => ["policies", props.policyId]);
198+
const policyQuery = useQuery<Policy | null>({
199+
enabled: true,
200+
queryKey,
201+
staleTime: 5000,
202+
queryFn: async () => {
203+
const call = api.endpoint<{ report: Policy | null }>(routes.PolicyReport, {
204+
policyId: props.policyId,
205+
});
206+
const response = await call.get();
207+
if (api.ok(response)) {
208+
return response.data.report;
209+
}
210+
return null;
211+
},
212+
});
213+
214+
const policy = computed<Policy | null>(() => {
215+
return policyQuery.data.value || null;
216+
});
217+
</script>

app/web/src/newhotness/Workspace.vue

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,7 @@
140140
<main v-else class="grow min-h-0">
141141
<ComponentPage v-if="componentId" :componentId="componentId" />
142142
<FuncRunDetails v-else-if="funcRunId" :funcRunId="funcRunId" />
143+
<PolicyDetails v-else-if="policyId" :policyId="policyId" />
143144
<LatestFuncRunDetails
144145
v-else-if="actionId"
145146
:functionKind="FunctionKind.Action"
@@ -189,6 +190,7 @@ import NavbarPanelRight from "./nav/NavbarPanelRight.vue";
189190
import Lobby from "./Lobby.vue";
190191
import Explore, { GroupByUrlQuery, SortByUrlQuery } from "./Explore.vue";
191192
import FuncRunDetails from "./FuncRunDetails.vue";
193+
import PolicyDetails from "./PolicyDetail.vue";
192194
import LatestFuncRunDetails from "./LatestFuncRunDetails.vue";
193195
import { Context, FunctionKind, AuthApiWorkspace } from "./types";
194196
import {
@@ -226,6 +228,7 @@ const props = defineProps<{
226228
secretId?: string;
227229
funcRunId?: string;
228230
actionId?: string;
231+
policyId?: string;
229232
}>();
230233
231234
const authStore = useAuthStore();
@@ -930,6 +933,12 @@ realtimeStore.subscribe(
930933
queryClient.invalidateQueries({ queryKey: ["changesets"] });
931934
},
932935
},
936+
{
937+
eventType: "PolicyUploaded",
938+
callback: () => {
939+
queryClient.invalidateQueries({ queryKey: ["policies"] });
940+
},
941+
},
933942
{
934943
eventType: "ChangeSetApprovalStatusChanged",
935944
callback: (changeSetId) => {

app/web/src/newhotness/api_composables/index.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,8 @@ export enum routes {
5555
RestoreComponents = "RestoreComponents",
5656
SetDefaultSubscriptionSource = "SetDefaultSubscriptionSource",
5757
MgmtFuncRun = "MgmtFuncRun",
58+
PolicyReports = "PolicyReports",
59+
PolicyReport = "PolicyReport",
5860
MgmtFuncGetJobState = "MgmtFuncGetJobState",
5961
MgmtFuncGetLatest = "MgmtFuncGetLatest",
6062
UpdateComponentAttributes = "UpdateComponentAttributes",
@@ -131,6 +133,8 @@ const _routes: Record<routes, string> = {
131133
MgmtFuncGetJobState: "/management/state/<funcRunId>",
132134
MgmtFuncGetLatest: "/management/component/<componentId>/latest",
133135
MgmtFuncRun: "/management/prototype/<prototypeId>/<componentId>/<viewId>",
136+
PolicyReports: "/policy-reports",
137+
PolicyReport: "/policy-reports/<policyId>",
134138
RefreshAction: "/action/refresh/<componentId>",
135139
RestoreComponents: "/components/restore",
136140
SetDefaultSubscriptionSource: "/components/<id>/attributes/default_source",

0 commit comments

Comments
 (0)