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

feat: prevent users from editing locked plan #228

Merged
merged 3 commits into from
Nov 10, 2022
Merged
Show file tree
Hide file tree
Changes from all 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
10 changes: 7 additions & 3 deletions src/components/modals/ModalHeader.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,18 @@
import XIcon from 'bootstrap-icons/icons/x.svg?component';
import { createEventDispatcher } from 'svelte';

export let showClose: boolean = true;

const dispatch = createEventDispatcher();
</script>

<div class="modal-header st-typography-header">
<slot />
<button class="st-button icon fs-6" on:click|stopPropagation={() => dispatch('close')}>
<XIcon />
</button>
{#if showClose}
<button class="st-button icon fs-6" on:click|stopPropagation={() => dispatch('close')}>
<XIcon />
</button>
{/if}
</div>

<style>
Expand Down
49 changes: 49 additions & 0 deletions src/components/modals/PlanLockedModal.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
<svelte:options immutable={true} />

<script lang="ts">
import { goto } from '$app/navigation';
import { base } from '$app/paths';
import { createEventDispatcher } from 'svelte';
import Modal from './Modal.svelte';
import ModalContent from './ModalContent.svelte';
import ModalFooter from './ModalFooter.svelte';
import ModalHeader from './ModalHeader.svelte';

export let planId: number = -1;

const dispatch = createEventDispatcher();

function gotoReview() {
goto(`${base}/plans/${planId}/merge`);
dispatch('close');
}

function gotoPlans() {
goto(`${base}/plans`);
dispatch('close');
}

function onKeydown(event: KeyboardEvent) {
const { key } = event;
if (key === 'Enter') {
event.preventDefault();
gotoReview();
}
}
</script>

<svelte:window on:keydown={onKeydown} />

<Modal height={150} width={380}>
<ModalHeader showClose={false}>Plan Locked</ModalHeader>
<ModalContent>
<div>
The plan you are viewing is locked and under review. Once the review has ended this plan will become editable
again.
</div>
</ModalContent>
<ModalFooter>
<button class="st-button secondary" on:click={gotoPlans}>View all plans</button>
<button class="st-button" on:click={gotoReview}>Take me to the review</button>
</ModalFooter>
</Modal>
12 changes: 12 additions & 0 deletions src/routes/plans/[id]/+page.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
maxTimeRange,
plan,
planEndTimeMs,
planLocked,
planStartTimeMs,
resetPlanStores,
viewTimeRange,
Expand All @@ -53,6 +54,7 @@
import { createActivitiesMap } from '../../../utilities/activities';
import effects from '../../../utilities/effects';
import { setQueryParam } from '../../../utilities/generic';
import { closeActiveModal, showPlanLockedModal } from '../../../utilities/modal';
import { getUnixEpochTime } from '../../../utilities/time';
import type { PageData } from './$types';

Expand All @@ -74,6 +76,8 @@
ViewsPanel,
};

let planHasBeenLocked = false;

$: if (data.initialPlan) {
$modelParametersMap = data.initialPlan.model.parameters.parameters;
$plan = data.initialPlan;
Expand All @@ -91,6 +95,14 @@

$: $activitiesMap = createActivitiesMap($plan, $activityDirectives, $simulationSpans);

$: if ($planLocked) {
planHasBeenLocked = true;
showPlanLockedModal($plan.id);
} else if (planHasBeenLocked) {
closeActiveModal();
planHasBeenLocked = false;
}

onMount(() => {
if ($view) {
setQueryParam('viewId', `${$view.id}`);
Expand Down
7 changes: 7 additions & 0 deletions src/stores/plan.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,13 @@ export const planId: Readable<number> = derived(plan, $plan => ($plan ? $plan.id

export const models = gqlSubscribable<ModelSlim[]>(gql.SUB_MODELS, {}, []);

export const planLocked = gqlSubscribable<boolean>(
gql.SUB_PLAN_LOCKED,
{ planId },
false,
({ is_locked }) => is_locked,
);

export const planMergeRequestsIncoming = gqlSubscribable<PlanMergeRequest[]>(
gql.SUB_PLAN_MERGE_REQUESTS_INCOMING,
{ planId },
Expand Down
8 changes: 8 additions & 0 deletions src/utilities/gql.ts
Original file line number Diff line number Diff line change
Expand Up @@ -900,6 +900,14 @@ const gql = {
}
`,

SUB_PLAN_LOCKED: `#graphql
subscription SubPlanLocked($planId: Int!) {
planLocked: plan_by_pk(id: $planId) {
is_locked
}
}
`,

SUB_PLAN_MERGE_CONFLICTING_ACTIVITIES: `#graphql
subscription SubPlanMergeConflictingActivities($merge_request_id: Int!) {
conflictingActivities: get_conflicting_activities(args: { merge_request_id: $merge_request_id } ) {
Expand Down
43 changes: 41 additions & 2 deletions src/utilities/modal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,15 @@ import CreateViewModal from '../components/modals/CreateViewModal.svelte';
import ExpansionSequenceModal from '../components/modals/ExpansionSequenceModal.svelte';
import PlanBranchesModal from '../components/modals/PlanBranchesModal.svelte';
import PlanBranchRequestModal from '../components/modals/PlanBranchRequestModal.svelte';
import PlanLockedModal from '../components/modals/PlanLockedModal.svelte';
import PlanMergeRequestsModal from '../components/modals/PlanMergeRequestsModal.svelte';

/**
* Listens for clicks on the document body and removes the modal children.
*/
export function modalBodyClickListener(): void {
const target: ModalElement = document.querySelector('#svelte-modal');
if (target && target.resolve) {
if (target && target.resolve && target.getAttribute('data-dismissible') !== 'false') {
target.replaceChildren();
target.resolve({ confirm: false });
target.resolve = null;
Expand All @@ -24,13 +25,25 @@ export function modalBodyClickListener(): void {
*/
export function modalBodyKeyListener(event: KeyboardEvent): void {
const target: ModalElement = document.querySelector('#svelte-modal');
if (target && target.resolve && event.key == 'Escape') {
if (target && target.resolve && event.key == 'Escape' && target.getAttribute('data-dismissible') !== 'false') {
target.replaceChildren();
target.resolve({ confirm: false });
target.resolve = null;
}
}

/**
* Closes the active modal if found and resolve nothing
*/
export function closeActiveModal(): void {
const target: ModalElement = document.querySelector('#svelte-modal');
if (target && target.resolve) {
target.removeAttribute('data-dismissible');
target.replaceChildren();
target.resolve = null;
}
}

/**
* Shows an AboutModal component.
*/
Expand Down Expand Up @@ -85,6 +98,32 @@ export async function showConfirmModal(
});
}

/**
* Shows a ConfirmModal component with the supplied arguments.
*/
export async function showPlanLockedModal(planId: number): Promise<ModalElementValue> {
return new Promise(resolve => {
const target: ModalElement = document.querySelector('#svelte-modal');

if (target) {
const planLockedModal = new PlanLockedModal({
props: { planId },
target,
});
target.resolve = resolve;

// Do not allow users to dismiss this modal
target.setAttribute('data-dismissible', 'false');

planLockedModal.$on('close', () => {
target.replaceChildren();
target.resolve = null;
target.removeAttribute('data-dismissible');
});
}
});
}

/**
* Shows a CreatePlanBranchModal with the supplied arguments.
*/
Expand Down