Skip to content

Commit

Permalink
Merge pull request #4912 from systeminit/wendy/eng-2822-new-approval-…
Browse files Browse the repository at this point in the history
…modal

Mock ReBAC Approval Modals
  • Loading branch information
wendybujalski authored Nov 2, 2024
2 parents 929d37d + 507d91b commit 6cc2983
Show file tree
Hide file tree
Showing 12 changed files with 393 additions and 17 deletions.
6 changes: 1 addition & 5 deletions app/web/src/components/Actions/ActionsList.vue
Original file line number Diff line number Diff line change
@@ -1,9 +1,5 @@
<template>
<TreeNode
:enableGroupToggle="displayActions.length > 0"
clickLabelToToggle
styleAsGutter
>
<TreeNode :enableGroupToggle="displayActions.length > 0" styleAsGutter>
<template #label>
<div
:class="
Expand Down
12 changes: 11 additions & 1 deletion app/web/src/components/ApplyChangeSetButton.vue
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@
votingKind="merge"
@completeVoting="applyChangeSet"
/>
<ApprovalFlowModal2 ref="approvalFlowModal2Ref" votingKind="merge" />
</VButton>
</template>

Expand All @@ -79,7 +80,9 @@ import { useAuthStore } from "@/store/auth.store";
import { useFeatureFlagsStore } from "@/store/feature_flags.store";
import RetryApply from "@/components/toasts/RetryApply.vue";
import ApprovalFlowModal from "./ApprovalFlowModal.vue";
import ApprovalFlowModal2 from "./ApprovalFlowModal2.vue";
const featureFlagsStore = useFeatureFlagsStore();
const actionsStore = useActionsStore();
const authStore = useAuthStore();
const changeSetsStore = useChangeSetsStore();
Expand All @@ -96,9 +99,16 @@ const applyChangeSetReqStatus =
const approvalFlowModalRef = ref<InstanceType<typeof ApprovalFlowModal> | null>(
null,
);
const approvalFlowModal2Ref = ref<InstanceType<
typeof ApprovalFlowModal2
> | null>(null);
const openApprovalFlowModal = () => {
approvalFlowModalRef.value?.open();
if (featureFlagsStore.REBAC) {
approvalFlowModal2Ref.value?.open();
} else {
approvalFlowModalRef.value?.open();
}
};
// Applies the current change set3
Expand Down
89 changes: 89 additions & 0 deletions app/web/src/components/ApprovalFlowModal2.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
<template>
<div>
<!-- this modal is for the voting process -->
<Modal ref="modalRef" title="Changes To Be Applied">
<div class="max-h-[70vh] overflow-hidden flex flex-col">
<div class="text-md mb-xs">
Applying this change set may create, modify, or destroy real resources
in the cloud.
</div>
<div class="text-sm mb-sm">
These actions will be applied to the real world:
</div>
<div
class="flex-grow overflow-y-auto mb-sm border border-neutral-100 dark:border-neutral-700"
>
<ActionsList slim kind="proposed" noInteraction />
</div>
<div
class="flex flex-row w-full items-center justify-center gap-sm mt-xs"
>
<VButton
label="Cancel"
icon="x"
variant="ghost"
tone="warning"
@click="closeModalHandler"
/>
<VButton
icon="tools"
loadingText="Merging Changes"
:label="userIsApprover ? 'Approve and Apply' : 'Request Approval'"
class="grow"
@click="applyButtonHandler"
/>
</div>
</div>
</Modal>
</div>
</template>

<script lang="ts" setup>
import * as _ from "lodash-es";
import { VButton, Modal } from "@si/vue-lib/design-system";
import { computed, ref } from "vue";
import { useChangeSetsStore } from "@/store/change_sets.store";
import { useAuthStore } from "@/store/auth.store";
import ActionsList from "./Actions/ActionsList.vue";
const changeSetsStore = useChangeSetsStore();
const authStore = useAuthStore();
const modalRef = ref<InstanceType<typeof Modal> | null>(null);
const changeSet = computed(() => changeSetsStore.selectedChangeSet);
const userIsApprover = ref(false);
async function openModalHandler() {
if (changeSet?.value?.name === "HEAD") return;
const data = await changeSetsStore.FETCH_CHANGE_SETS_V2();
const approvers = data.rawResponseData?.approvers;
const userPk = authStore.user?.pk;
if (approvers && userPk && approvers.includes(userPk)) {
userIsApprover.value = true;
}
modalRef.value?.open();
}
function closeModalHandler() {
modalRef.value?.close();
}
function applyButtonHandler() {
if (userIsApprover.value) {
// TODO(Wendy) - not sure if this is right, need to figure out integration!
if (authStore.user) {
changeSetsStore.FORCE_APPLY_CHANGE_SET(authStore.user.name);
closeModalHandler();
}
} else {
// TODO(Wendy) - not sure if this is right, need to figure out integration!
changeSetsStore.REQUEST_CHANGE_SET_APPROVAL();
closeModalHandler();
}
}
defineExpose({ open: openModalHandler });
</script>
34 changes: 34 additions & 0 deletions app/web/src/components/ApprovalPendingModal.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<template>
<div>
<!-- this modal is for the voting process -->
<Modal ref="modalRef" title="Pending Approvals" size="lg">
<div class="max-h-[70vh] overflow-hidden flex flex-col">
<div class="text-md mb-xs">
These change sets have been submitted for approval to be merged to
HEAD. Select one to approve or reject it.
</div>
</div>
</Modal>
</div>
</template>

<script lang="ts" setup>
// TODO(WENDY) - this mock modal still needs code for -
// Listing the pending approvals
// Selecting and displaying an individual approval with Reject/Approve buttons
import * as _ from "lodash-es";
import { Modal } from "@si/vue-lib/design-system";
import { ref } from "vue";
const modalRef = ref<InstanceType<typeof Modal> | null>(null);
function openModalHandler() {
modalRef.value?.open();
}
// function closeVotingModalHandler() {
// modalRef.value?.close();
// }
defineExpose({ open: openModalHandler });
</script>
180 changes: 180 additions & 0 deletions app/web/src/components/InsetApprovalModal.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
<template>
<div
:class="
clsx(
'max-w-md flex flex-col gap-sm p-sm',
themeClasses('bg-neutral-100 border', 'bg-neutral-900'),
)
"
>
<div class="flex flex-col gap-2xs">
<div class="font-bold">{{ modalData.title }}</div>
<div class="text-sm italic">
<Timestamp :date="modalData.date" showTimeIfToday size="extended" />
</div>
</div>
<ErrorMessage
:tone="modalData.messageTone"
:icon="modalData.messageIcon"
variant="block"
class="rounded"
>
<template v-if="mode === 'requested'">
This change set is currently locked until the approval is accepted or
rejected.
<template v-if="userIsApprover"
>You can approve or reject this change set, or you
</template>
<template v-else>
{{
`${
requesterIsYou
? "You can withdraw the approval request to make more changes and then request approval again, or you"
: "You"
} `
}}
</template>
can switch to a different change set using the dropdown at the top of
the screen.
</template>
<template v-else-if="mode === 'approved' || mode === 'rejected'">
{{ requesterIsYou ? "Your" : "The" }} request to
<span class="font-bold">Apply</span> change set
<span class="font-bold">{{ changeSetName }}</span> was {{ mode }} by
<span class="font-bold">{{ approverName + " " }}</span>
{{
modalData.date.toDateString() === new Date().toDateString()
? ""
: "on"
}}
<span class="font-bold">
<Timestamp :date="modalData.date" showTimeIfToday size="extended" />
</span>
<div
v-if="!requesterIsYou && !userIsApprover && mode === 'approved'"
class="pt-xs"
>
<span class="font-bold">{{ requesterName }}</span> requested this
<span class="font-bold">Apply</span> and can merge this change set.
You can switch to a different change set using the dropdown at the top
of the screen.
</div>
</template>
<template v-else>
ERROR - this message should not ever show. Something has gone wrong!
</template>
</ErrorMessage>
<div
v-if="requesterIsYou || mode === 'rejected' || userIsApprover"
class="flex flex-row gap-sm"
>
<VButton
v-if="userIsApprover && mode === 'requested'"
label="Reject Request"
tone="destructive"
variant="ghost"
@click="rejectHandler"
/>
<VButton
:label="modalData.buttonText"
:tone="modalData.buttonTone"
class="grow"
@click="confirmHandler"
/>
</div>
</div>
</template>

<script lang="ts" setup>
import {
VButton,
Timestamp,
Tones,
ErrorMessage,
IconNames,
themeClasses,
} from "@si/vue-lib/design-system";
import { computed } from "vue";
import clsx from "clsx";
import { useChangeSetsStore } from "@/store/change_sets.store";
export type InsetApprovalModalMode = "requested" | "approved" | "rejected";
const changeSetsStore = useChangeSetsStore();
const changeSetName = computed(() => changeSetsStore.selectedChangeSet?.name);
// TODO(Wendy) - Mock data we need to replace with real data!
const mode = computed(() => "requested" as InsetApprovalModalMode);
const requesterName = computed(() => "Paul");
const requestDate = computed(() => new Date());
const requesterIsYou = computed(() => false);
const approverName = computed(() => "Wendy");
const approveDate = computed(() => new Date());
const userIsApprover = computed(() => true);
// END MOCK DATA
const modalData = computed(() => {
if (mode.value === "requested") {
return {
title: `Approval Requested by ${
requesterIsYou.value ? "You" : requesterName.value
}`,
date: requestDate.value,
buttonText: userIsApprover.value
? "Approve Request"
: "Withdraw Approval Request",
buttonTone: (userIsApprover.value ? "success" : "action") as Tones,
messageTone: "warning" as Tones,
messageIcon: "exclamation-circle" as IconNames,
};
} else if (mode.value === "approved") {
return {
title: `Approval Granted by ${approverName.value}`,
date: approveDate.value,
buttonText: "Apply Change Set",
buttonTone: "success" as Tones,
messageTone: "success" as Tones,
messageIcon: "check-circle" as IconNames,
};
} else if (mode.value === "rejected") {
return {
title: `Approval Rejected by ${approverName.value}`,
date: approveDate.value,
buttonText: "Make Edits",
buttonTone: "action" as Tones,
messageTone: "destructive" as Tones,
};
}
return {
title: "ERROR!",
date: new Date(),
buttonText: "Error!",
buttonTone: "destructive" as Tones,
messageTone: "destructive" as Tones,
};
});
const confirmHandler = () => {
// eslint-disable-next-line no-console
console.log(
"TODO - write logic to handle all possible primary button press actions here!",
);
if (mode.value === "requested") {
if (userIsApprover.value) {
// TODO - this is where the logic for approving a request goes!
} else if (requesterIsYou.value) {
/// TODO - this is where the logic for withdrawing a request goes!
}
} else if (mode.value === "approved") {
// TODO - this is where the logic for applying an approved change set goes!
} else if (mode.value === "rejected") {
// TODO - this is where the logic for returning the change set to open goes!
}
};
const rejectHandler = () => {
// eslint-disable-next-line no-console
console.log("TODO - write logic to handle rejecting a request here!");
};
</script>
Loading

0 comments on commit 6cc2983

Please sign in to comment.