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: Setup routing, queries, and permissions for /:flow/feeback #3158

Merged
merged 5 commits into from
May 17, 2024
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
4 changes: 2 additions & 2 deletions editor.planx.uk/src/components/Feedback/MoreInfoFeedback.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,11 @@ const MoreInfoFeedback = styled(Box)(({ theme }) => ({
},
}));

export type Sentiment = "helpful" | "unhelpful";

const MoreInfoFeedbackComponent: React.FC = () => {
type View = "yes/no" | "input" | "thanks";

type Sentiment = "helpful" | "unhelpful";

const [currentFeedbackView, setCurrentFeedbackView] =
useState<View>("yes/no");
const [feedbackOption, setFeedbackOption] = useState<Sentiment | null>(null);
Expand Down
4 changes: 2 additions & 2 deletions editor.planx.uk/src/components/Feedback/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -80,9 +80,9 @@ export type FeedbackFormInput = {
id: string;
};

const Feedback: React.FC = () => {
type FeedbackCategory = "issue" | "idea" | "comment" | "inaccuracy";
export type FeedbackCategory = "issue" | "idea" | "comment" | "inaccuracy";

const Feedback: React.FC = () => {
type View = "banner" | "triage" | FeedbackCategory | "thanks";

type ClickEvents = "close" | "back" | "triage" | FeedbackCategory;
Expand Down
10 changes: 8 additions & 2 deletions editor.planx.uk/src/pages/FlowEditor/components/EditorMenu.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import FactCheckIcon from "@mui/icons-material/FactCheck";
import RateReviewIcon from "@mui/icons-material/RateReview";
import TuneIcon from "@mui/icons-material/Tune";
import Box from "@mui/material/Box";
import IconButton from "@mui/material/IconButton";
Expand Down Expand Up @@ -68,10 +69,10 @@ const MenuButton = styled(IconButton, {

function EditorMenu() {
const { navigate } = useNavigation();
const { lastChunk } = useCurrentRoute();
const { url } = useCurrentRoute();
const rootPath = rootFlowPath();

const isActive = (route: string) => lastChunk.url.pathname.endsWith(route);
const isActive = (route: string) => url.pathname.endsWith(route);
Comment on lines +72 to +75
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This change was required as the /feedback route (in its own file) ended with a /

const handleClick = (route: string) =>
!isActive(route) && navigate(rootPath + route);

Expand All @@ -91,6 +92,11 @@ function EditorMenu() {
Icon: FactCheckIcon,
route: "/submissions-log",
},
{
title: "Feedback",
Icon: RateReviewIcon,
route: "/feedback",
},
];

return (
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import Box from "@mui/material/Box";
import Button from "@mui/material/Button";
import gql from "graphql-tag";
import { client } from "lib/graphql";
import React from "react";
import { Feedback } from "routes/feedback";

interface Props {
feedback: Feedback[];
}

const GET_FEEDBACK_BY_ID_QUERY = gql`
query GetFeedbackById($feedbackId: Int!) {
feedback: feedback_summary(where: { feedback_id: { _eq: $feedbackId } }) {
address
createdAt: created_at
device
feedbackId: feedback_id
feedbackType: feedback_type
helpDefinition: help_definition
helpSources: help_sources
helpText: help_text
intersectingConstraints: intersecting_constraints
nodeData: node_data
nodeId: node_id
nodeText: node_text
nodeTitle: node_title
nodeType: node_type
projectType: project_type
serviceSlug: service_slug
teamSlug: team_slug
status
uprn
userComment: user_comment
userContext: user_context
}
}
`;

const getDetailedFeedback = async (feedbackId: number) => {
const {
data: {
feedback: [detailedFeedback],
},
} = await client.query({
query: GET_FEEDBACK_BY_ID_QUERY,
variables: { feedbackId },
});
console.log(detailedFeedback);
};

export const FeedbackPage: React.FC<Props> = ({ feedback }) => {
return (
<Box sx={{ fontSize: 12, overflowY: "auto" }}>
{feedback.map((item) => (
<React.Fragment key={item.id}>
<Box component="pre">{JSON.stringify(item, null, 4)}</Box>
<Button onClick={() => getDetailedFeedback(item.id)}>
Log out detailed info
</Button>
</React.Fragment>
))}
</Box>
);
};
73 changes: 73 additions & 0 deletions editor.planx.uk/src/routes/feedback.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import { ComponentType } from "@opensystemslab/planx-core/types";
import { FeedbackCategory } from "components/Feedback";
import { Sentiment } from "components/Feedback/MoreInfoFeedback";
import gql from "graphql-tag";
import { compose, mount, NotFoundError, route, withData } from "navi";
import { FeedbackPage } from "pages/FlowEditor/components/Flow/FeedbackPage";
import { useStore } from "pages/FlowEditor/lib/store";
import React from "react";

import { client } from "../lib/graphql";
import { makeTitle } from "./utils";

type FeedbackType = Sentiment & FeedbackCategory;

export interface Feedback {
id: number;
type: FeedbackType;
nodeTitle: string | null;
nodeType: keyof typeof ComponentType | null;
userComment: string | null;
userContext: string | null;
createdAt: string;
}

const feedbackRoutes = compose(
withData((req) => ({
mountpath: req.mountpath,
})),

mount({
"/": route(async (req) => {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Here's the / that necessitated the change in EditorMenu.tsx

const { team: teamSlug, flow: flowSlug } = req.params;

const isAuthorised = useStore.getState().canUserEditTeam(teamSlug);
if (!isAuthorised)
throw new NotFoundError(
`User does not have access to ${req.originalUrl}`,
);

const {
data: { feedback },
} = await client.query<{ feedback: Feedback[] }>({
query: gql`
query GetFeebackForFlow($teamSlug: String!, $flowSlug: String!) {
feedback: feedback_summary(
order_by: { created_at: asc }
where: {
team_slug: { _eq: $teamSlug }
service_slug: { _eq: $flowSlug }
}
) {
id: feedback_id
type: feedback_type
nodeTitle: node_title
nodeType: node_type
userComment: user_comment
userContext: user_context
createdAt: created_at
}
}
`,
variables: { teamSlug, flowSlug },
});

return {
title: makeTitle("Flow Feedback"),
view: <FeedbackPage feedback={feedback} />,
};
}),
}),
);

export default feedbackRoutes;
2 changes: 2 additions & 0 deletions editor.planx.uk/src/routes/flow.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,8 @@ const routes = compose(
};
}),

"/feedback": lazy(() => import("./feedback")),

"/nodes": compose(
withView((req) => {
const [flow, ...breadcrumbs] = req.params.flow.split(",");
Expand Down
70 changes: 70 additions & 0 deletions hasura.planx.uk/metadata/tables.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -268,6 +268,76 @@
- table:
name: feedback_summary
schema: public
object_relationships:
- name: team
using:
manual_configuration:
column_mapping:
team_slug: slug
insertion_order: null
remote_table:
name: teams
schema: public
Comment on lines +271 to +280
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This adds a relationship between feedback_summary.team_slug and team.slug.

This is required to correctly scope permissions - teamEditors can only access feedback for their own teams, platformAdmins can access everything.

select_permissions:
- role: platformAdmin
permission:
columns:
- feedback_id
- device
- node_data
- address
- feedback_type
- help_definition
- help_sources
- help_text
- intersecting_constraints
- node_id
- node_text
- node_title
- node_type
- project_type
- service_slug
- status
- team_slug
- uprn
- user_comment
- user_context
- created_at
filter: {}
comment: ""
- role: teamEditor
permission:
columns:
- feedback_id
- device
- node_data
- address
- feedback_type
- help_definition
- help_sources
- help_text
- intersecting_constraints
- node_id
- node_text
- node_title
- node_type
- project_type
- service_slug
- status
- team_slug
- uprn
- user_comment
- user_context
- created_at
filter:
team:
members:
_and:
- user_id:
_eq: x-hasura-user-id
- role:
_eq: teamEditor
Comment on lines +332 to +339
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Permission model copied from team_themes table.

comment: ""
- table:
name: feedback_type_enum
schema: public
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
DROP VIEW "public"."feedback_summary";

CREATE OR REPLACE VIEW "public"."feedback_summary" AS
SELECT
fb.id AS feedback_id,
t.slug AS team,
f.slug AS service_slug,
fb.created_at,
fb.node_id,
fb.device,
fb.user_context,
fb.user_comment,
fb.feedback_type,
fb.status,
fb.node_type,
fb.node_data,
COALESCE(
fb.node_data ->> 'title',
fb.node_data ->> 'text',
fb.node_data ->> 'flagSet'
) AS node_title,
fb.node_data ->> 'description' AS node_text,
fb.node_data ->> 'info' AS help_text,
fb.node_data ->> 'policyRef' AS help_sources,
fb.node_data ->> 'howMeasured' AS help_definition,
COALESCE(
fb.user_data -> 'passport' -> 'data' -> '_address' ->> 'single_line_address',
fb.user_data -> 'passport' -> 'data' -> '_address' ->> 'title'
) AS address,
(fb.user_data -> 'passport' -> 'data' -> '_address' ->> 'uprn') AS uprn,
(fb.user_data -> 'passport' -> 'data' ->> 'proposal.projectType') AS project_type,
(fb.user_data -> 'passport' -> 'data' ->> 'property.constraints.planning') AS intersecting_constraints
FROM
feedback fb
LEFT JOIN
flows f ON f.id = fb.flow_id
LEFT JOIN
teams t ON t.id = fb.team_id;

GRANT SELECT ON public.feedback_summary TO metabase_read_only;
40 changes: 40 additions & 0 deletions hasura.planx.uk/migrations/1715929881942_run_sql_migration/up.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
DROP VIEW "public"."feedback_summary";

CREATE OR REPLACE VIEW "public"."feedback_summary" AS
SELECT
fb.id AS feedback_id,
t.slug AS team_slug,
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Apologies for the churn in this view recently, I've renamed team to the more explicit and correct team.slug so that the relationship can just be called team in Hasura.

f.slug AS service_slug,
fb.created_at,
fb.node_id,
fb.device,
fb.user_context,
fb.user_comment,
fb.feedback_type,
fb.status,
fb.node_type,
fb.node_data,
COALESCE(
fb.node_data ->> 'title',
fb.node_data ->> 'text',
fb.node_data ->> 'flagSet'
) AS node_title,
fb.node_data ->> 'description' AS node_text,
fb.node_data ->> 'info' AS help_text,
fb.node_data ->> 'policyRef' AS help_sources,
fb.node_data ->> 'howMeasured' AS help_definition,
COALESCE(
fb.user_data -> 'passport' -> 'data' -> '_address' ->> 'single_line_address',
fb.user_data -> 'passport' -> 'data' -> '_address' ->> 'title'
) AS address,
(fb.user_data -> 'passport' -> 'data' -> '_address' ->> 'uprn') AS uprn,
(fb.user_data -> 'passport' -> 'data' ->> 'proposal.projectType') AS project_type,
(fb.user_data -> 'passport' -> 'data' ->> 'property.constraints.planning') AS intersecting_constraints
FROM
feedback fb
LEFT JOIN
flows f ON f.id = fb.flow_id
LEFT JOIN
teams t ON t.id = fb.team_id;

GRANT SELECT ON public.feedback_summary TO metabase_read_only;
Loading