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

Production deploy #2298

Merged
merged 4 commits into from
Oct 12, 2023
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
14 changes: 14 additions & 0 deletions e2e/tests/api-driven/src/hasuraTriggers/hasuraTriggers.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
Feature: Database triggers

@regression @add-user-trigger
Scenario: Adding a user to Planx - with Templates team
Given the Templates team exists
When a new user is added
Then they are granted access to the Templates team
And have the teamEditor role

@regression @add-user-trigger
Scenario: Adding a user to Planx - without Templates team
Given the Templates team does not exist
When a new user is added
Then they are not granted access to the Templates team
6 changes: 6 additions & 0 deletions e2e/tests/api-driven/src/hasuraTriggers/helpers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { $admin } from "../client";

export const cleanup = async () => {
await $admin.user._destroyAll();
await $admin.team._destroyAll();
};
62 changes: 62 additions & 0 deletions e2e/tests/api-driven/src/hasuraTriggers/steps.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import { After, Given, Then, When, World } from "@cucumber/cucumber";
import { cleanup } from "./helpers";
import { User } from "@opensystemslab/planx-core/types";
import { $admin } from "../client";
import assert from "assert";
import { createTeam, createUser } from "../globalHelpers";

export class CustomWorld extends World {
user!: User;
templatesTeamId!: number;
}

After("@add-user-trigger", async function () {
await cleanup();
});

Given("the Templates team exists", async function (this) {
const templatesTeamId = await createTeam({ slug: "templates" });

assert.ok(templatesTeamId, "Templates team is not defined");

this.templatesTeamId = templatesTeamId;
});

Given("the Templates team does not exist", async function (this) {
const templatesTeam = await $admin.team.getBySlug("templates");

assert.equal(
templatesTeam,
undefined,
"Templates team exists but should not be defined",
);
});

When<CustomWorld>("a new user is added", async function (this) {
const userId = await createUser();
const user = await $admin.user.getById(userId);

assert.ok(user, "User is not defined");

this.user = user;
});

Then<CustomWorld>(
"they are granted access to the Templates team",
async function (this) {
assert.strictEqual(this.user.teams.length, 1);
assert.strictEqual(this.user.teams[0].team.slug, "templates");
assert.strictEqual(this.user.teams[0].team.id, this.templatesTeamId);
},
);

Then<CustomWorld>("have the teamEditor role", async function (this) {
assert.strictEqual(this.user.teams[0].role, "teamEditor");
});

Then<CustomWorld>(
"they are not granted access to the Templates team",
async function (this) {
assert.strictEqual(this.user.teams.length, 0);
},
);
6 changes: 5 additions & 1 deletion editor.planx.uk/src/@planx/components/NextSteps/Public.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { PublicProps } from "@planx/components/ui";

import React from "react";
import NextStepsList from "ui/NextStepsList";

Expand All @@ -18,7 +19,10 @@ const NextStepsComponent: React.FC<Props> = (props) => {
policyRef={props.policyRef}
howMeasured={props.howMeasured}
/>
<NextStepsList steps={props.steps} handleSubmit={props.handleSubmit} />
<NextStepsList
steps={props.steps}
handleSubmit={props.handleSubmit}
/>
</Card>
);
};
Expand Down
5 changes: 1 addition & 4 deletions editor.planx.uk/src/components/Header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -482,10 +482,7 @@ const EditorToolbar: React.FC<{
<ListItemText>
{user.isPlatformAdmin
? `All teams`
: user.teams
.map((team) => team.team.name)
.concat(["Templates"])
.join(", ")}
: user.teams.map((team) => team.team.name).join(", ")}
</ListItemText>
</MenuItem>
)}
Expand Down
101 changes: 101 additions & 0 deletions editor.planx.uk/src/pages/FlowEditor/lib/__tests__/user.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
import { User } from "@opensystemslab/planx-core/types";

import { FullStore, vanillaStore } from "../store";

const { getState, setState } = vanillaStore;
const { canUserEditTeam } = getState();

const redUser: User = {
id: 1,
isPlatformAdmin: false,
firstName: "Red",
lastName: "Reddison",
email: "[email protected]",
teams: [
{
role: "teamEditor",
team: {
name: "Red Team",
slug: "red-team",
id: 1,
},
},
{
role: "teamViewer",
team: {
name: "Blue Team",
slug: "blue-team",
id: 1,
},
},
],
};

const blueUser: User = {
id: 2,
isPlatformAdmin: false,
firstName: "Blue",
lastName: "Bluey",
email: "[email protected]",
teams: [
{
role: "teamEditor",
team: {
name: "Blue Team",
slug: "blue-team",
id: 1,
},
},
],
};

const readOnlyUser: User = {
id: 3,
isPlatformAdmin: false,
firstName: "Read",
lastName: "Only",
email: "[email protected]",
teams: [],
};

const adminUser: User = {
id: 4,
isPlatformAdmin: true,
firstName: "Platform",
lastName: "Admin",
email: "[email protected]",
teams: [],
};

let initialState: FullStore;

beforeEach(() => {
initialState = getState();
});

afterEach(() => setState(initialState));

describe("canUserEditTeam helper function", () => {
it("returns true when a user has teamEditor permission for a team", () => {
setState({ user: redUser });
expect(canUserEditTeam("red-team")).toBe(true);
expect(canUserEditTeam("blue-team")).toBe(false);
});

it("returns false when a user does not have permission for a team", () => {
setState({ user: blueUser });
expect(canUserEditTeam("red-team")).toBe(false);
});

it("returns false when a user does not have any permissions", () => {
setState({ user: readOnlyUser });
expect(canUserEditTeam("red-team")).toBe(false);
expect(canUserEditTeam("blue-team")).toBe(false);
});

it("returns true when a user is has the platformAdmin role", () => {
setState({ user: adminUser });
expect(canUserEditTeam("red-team")).toBe(true);
expect(canUserEditTeam("blue-team")).toBe(true);
});
});
28 changes: 28 additions & 0 deletions editor.planx.uk/src/pages/FlowEditor/lib/analyticsProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,19 @@ export type AnalyticsType = "init" | "resume";
type AnalyticsLogDirection = AnalyticsType | "forwards" | "backwards";

export type HelpClickMetadata = Record<string, string>;
export type SelectedUrlsMetadata = Record<'selectedUrls', string[]>;

let lastAnalyticsLogId: number | undefined = undefined;

const analyticsContext = createContext<{
createAnalytics: (type: AnalyticsType) => Promise<void>;
trackHelpClick: (metadata?: HelpClickMetadata) => Promise<void>;
trackNextStepsLinkClick: (metadata?: SelectedUrlsMetadata) => Promise<void>;
node: Store.node | null;
}>({
createAnalytics: () => Promise.resolve(),
trackHelpClick: () => Promise.resolve(),
trackNextStepsLinkClick: () => Promise.resolve(),
node: null,
});
const { Provider } = analyticsContext;
Expand Down Expand Up @@ -100,6 +103,7 @@ export const AnalyticsProvider: React.FC<{ children: React.ReactNode }> = ({
value={{
createAnalytics,
trackHelpClick,
trackNextStepsLinkClick,
node,
}}
>
Expand Down Expand Up @@ -171,6 +175,30 @@ export const AnalyticsProvider: React.FC<{ children: React.ReactNode }> = ({
}
}

async function trackNextStepsLinkClick(metadata?: SelectedUrlsMetadata) {
if (shouldTrackAnalytics && lastAnalyticsLogId) {
await publicClient.mutate({
mutation: gql`
mutation UpdateHasClickNextStepsLink(
$id: bigint!
$metadata: jsonb = {}
) {
update_analytics_logs_by_pk(
pk_columns: { id: $id }
_append: { metadata: $metadata }
) {
id
}
}
`,
variables: {
id: lastAnalyticsLogId,
metadata,
},
});
}
}

async function createAnalytics(type: AnalyticsType) {
if (shouldTrackAnalytics) {
const response = await publicClient.mutate({
Expand Down
15 changes: 6 additions & 9 deletions editor.planx.uk/src/pages/FlowEditor/lib/store/user.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { User } from "@opensystemslab/planx-core/types";
import { User, UserTeams } from "@opensystemslab/planx-core/types";
import { _client } from "client";
import jwtDecode from "jwt-decode";
import { Team } from "types";
Expand All @@ -22,16 +22,13 @@ export const userStore: StateCreator<UserStore, [], [], UserStore> = (
getUser: () => get().user,

canUserEditTeam(teamSlug) {
const user = this.getUser();
const user = get().getUser();
if (!user) return false;

return (
user.isPlatformAdmin ||
teamSlug === "templates" ||
user.teams.filter(
(team) => team.role === "teamEditor" && team.team.slug === teamSlug,
).length > 0
);
const hasTeamEditorRole = (team: UserTeams) =>
team.role === "teamEditor" && team.team.slug === teamSlug;

return user.isPlatformAdmin || user.teams.some(hasTeamEditorRole);
},

async initUserStore(jwt: string) {
Expand Down
2 changes: 1 addition & 1 deletion editor.planx.uk/src/pages/Preview/StatusPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ const StatusPage: React.FC<Props> = ({
onClick={removeSessionIdSearchParam}
sx={contentFlowSpacing}
>
<Typography variant="body1">Start new application</Typography>
<Typography variant="body1" align="left">Start new application</Typography>
</Link>
</>
)}
Expand Down
31 changes: 24 additions & 7 deletions editor.planx.uk/src/ui/NextStepsList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,9 @@ import Link from "@mui/material/Link";
import { styled, Theme } from "@mui/material/styles";
import Typography from "@mui/material/Typography";
import type { Step as StyledListItem } from "@planx/components/NextSteps/model";
import { useAnalyticsTracking } from "pages/FlowEditor/lib/analyticsProvider";
import { handleSubmit } from "pages/Preview/Node";
import React from "react";
import React, { useState } from "react";

interface NextStepsListProps {
steps: StyledListItem[];
Expand All @@ -15,6 +16,7 @@ interface NextStepsListProps {

interface ListItemProps extends StyledListItem {
handleSubmit?: handleSubmit;
handleSelectingUrl?: (url: string) => void;
}

const Root = styled("ul")(({ theme }) => ({
Expand Down Expand Up @@ -72,11 +74,18 @@ const ArrowButton = styled("span")(({ theme }) => ({
flexShrink: "0",
}));

const LinkStep = (props: ListItemProps) => (
<InnerLink href={props.url} target="_blank" rel="noopener">
<Step {...props} />
</InnerLink>
);
function LinkStep(props: ListItemProps) {
return (
<InnerLink
href={props.url}
target="_blank"
rel="noopener"
onClick={() => props.handleSelectingUrl && props.url && props.handleSelectingUrl(props.url)}
>
<Step {...props} />
</InnerLink>
);
}

const ContinueStep = (props: ListItemProps) => (
<InnerButton onClick={() => props.handleSubmit && props.handleSubmit()}>
Expand Down Expand Up @@ -109,12 +118,20 @@ const Step = ({ title, description, url }: ListItemProps) => (
);

function NextStepsList(props: NextStepsListProps) {
const [selectedUrls, setSelectedUrls] = useState<string[]>([]);
const { trackNextStepsLinkClick } = useAnalyticsTracking()

const handleSelectingUrl = (newUrl: string) => {
setSelectedUrls(prevSelectedUrls => [...prevSelectedUrls, newUrl]);
trackNextStepsLinkClick({'selectedUrls': [...selectedUrls, newUrl]})
}

return (
<Root>
{props.steps?.map((step, i) => (
<StyledListItem key={i}>
{step.url ? (
<LinkStep {...step} />
<LinkStep {...step} handleSelectingUrl={handleSelectingUrl}/>
) : (
<ContinueStep {...step} handleSubmit={props.handleSubmit} />
)}
Expand Down
Loading
Loading