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(alerts): add ui #26

Merged
merged 1 commit into from
Jun 16, 2020
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
20 changes: 17 additions & 3 deletions hasura/migrations/1591618615701_alerts/up.sql
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,16 @@ INSERT INTO public.status (name) VALUES ('rejected');

CREATE TABLE "public"."sources"(
"repository" text NOT NULL,
"label" text NOT NULL,
"tag" text NOT NULL,
"created_at" timestamptz NOT NULL DEFAULT now(),
PRIMARY KEY ("repository")
);

COMMENT ON TABLE "public"."sources" IS E'sources are git repository that acts as data sources to track changes';

INSERT INTO public.sources (repository, tag) VALUES ('socialgouv/legi-data', 'v1.9.0');
INSERT INTO public.sources (repository, tag) VALUES ('socialgouv/kali-data', 'v1.60.0');
INSERT INTO public.sources (repository, label, tag) VALUES ('socialgouv/legi-data', 'code du travail', 'v1.9.0');
INSERT INTO public.sources (repository, label, tag) VALUES ('socialgouv/kali-data', 'conventions collectives', 'v1.60.0');

CREATE EXTENSION IF NOT EXISTS pgcrypto;
CREATE TABLE "public"."alerts"(
Expand All @@ -27,5 +28,18 @@ CREATE TABLE "public"."alerts"(
"repository" text NOT NULL,
"ref" text NOT NULL,
"changes" jsonb NOT NULL,
"created_at" timestamptz NULL DEFAULT now(),
"updated_at" timestamptz NULL DEFAULT now();
PRIMARY KEY ("id") ,
FOREIGN KEY ("status") REFERENCES "public"."status"("name") ON UPDATE restrict ON DELETE restrict, FOREIGN KEY ("repository") REFERENCES "public"."sources"("repository") ON UPDATE restrict ON DELETE cascade); COMMENT ON TABLE "public"."alerts" IS E'alerts reprensent a change in a text from a source';
FOREIGN KEY ("status") REFERENCES "public"."status"("name") ON UPDATE restrict ON DELETE restrict, FOREIGN KEY ("repository") REFERENCES "public"."sources"("repository") ON UPDATE restrict ON DELETE cascade);

COMMENT ON TABLE "public"."alerts" IS
E'alerts reprensent a change in a text from a source';

CREATE TRIGGER "set_public_alerts_updated_at"
BEFORE UPDATE ON public.alerts
FOR EACH ROW
EXECUTE PROCEDURE trigger_set_timestamp();

COMMENT ON TRIGGER "set_public_alerts_updated_at" ON public.alerts
IS 'trigger to set value of column "updated_at" to current timestamp on row update';
8 changes: 6 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
"dependencies": {
"@hapi/boom": "^9.1.0",
"@hapi/joi": "^17.1.1",
"@reach/accordion": "^0.10.3",
"@reach/dialog": "^0.10.3",
"@reach/menu-button": "^0.10.3",
"@reach/visually-hidden": "^0.10.2",
Expand All @@ -17,6 +18,7 @@
"@zeit/next-source-maps": "0.0.4-canary.1",
"argon2": "^0.26.2",
"cookie": "^0.4.1",
"diff": "^4.0.2",
"graphql": "^15.0.0",
"http-proxy-middleware": "^1.0.4",
"isomorphic-unfetch": "^3.0.0",
Expand Down Expand Up @@ -54,10 +56,12 @@
"scripts": {
"dev": "next dev",
"build": "next build",
"prestart": "node scripts/update-alert.js",
"start": "next start",
"alert": " node scripts/update-alert.js",
"alert:dev": "GRAPHQL_ENDPOINT=http://localhost:8080/v1/graphql HASURA_GRAPHQL_ADMIN_SECRET=admin1 node scripts/update-alert.js",
"alert:dump": "GRAPHQL_ENDPOINT=http://localhost:8080/v1/graphql HASURA_GRAPHQL_ADMIN_SECRET=admin1 DUMP=true node scripts/update-alert.js > data/dump.json",
"alert:dev": "GRAPHQL_ENDPOINT=http://localhost:8080/v1/graphql HASURA_GRAPHQL_ADMIN_SECRET=admin1 node scripts/update-alerts.js",
"alert:dump": "GRAPHQL_ENDPOINT=http://localhost:8080/v1/graphql HASURA_GRAPHQL_ADMIN_SECRET=admin1 DUMP=true node scripts/update-alerts.js > data/dump.json",
"alert:populate": "GRAPHQL_ENDPOINT=http://localhost:8080/v1/graphql HASURA_GRAPHQL_ADMIN_SECRET=admin1 node scripts/add-alerts.js",
"lint": "eslint src/*",
"test": "jest"
},
Expand Down
28 changes: 28 additions & 0 deletions scripts/add-alerts.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
const { promises: fs } = require("fs");
const path = require("path");
const filename =
process.env.DUMP || path.join(__dirname, "..", "data", "dump.json");
const { updateSource, insertAlert } = require("./update-alerts");

async function main() {
const fileContent = (await fs.readFile(filename)).toString();
const data = JSON.parse(fileContent);

for (const result of data) {
if (result.changes.length === 0) {
console.log(`no update for ${result.repository}`);
continue;
}
const inserts = await Promise.all(
result.changes.map((diff) => insertAlert(result.repository, diff))
);
inserts.forEach((insert) => {
console.log("insert alert", insert.returning[0]);
});
const update = await updateSource(result.repository, result.newRef);
console.log(`update source ${update.repository} to ${update.tag}`);
}
return Promise.resolve();
}

main().catch(console.error);
14 changes: 10 additions & 4 deletions scripts/update-alert.js → scripts/update-alerts.js
Original file line number Diff line number Diff line change
Expand Up @@ -84,16 +84,16 @@ async function getSources() {
return result.data.sources;
}

async function insertAlert(changes) {
async function insertAlert(repository, changes) {
const data = {
repository,
info: {
num: changes.num,
title: changes.title,
id: changes.id,
file: changes.file,
},
ref: changes.ref,
repository: changes.repository,
changes: {
added: changes.added,
removed: changes.removed,
Expand Down Expand Up @@ -250,9 +250,9 @@ async function main() {
continue;
}
const inserts = await Promise.all(
result.changes.map((diff) => insertAlert(diff))
result.changes.map((diff) => insertAlert(result.repository, diff))
);
inserts.forEeach((insert) => {
inserts.forEach((insert) => {
console.log("insert alert", insert.returning[0]);
});
const update = await updateSource(result.repository, result.newRef);
Expand All @@ -262,3 +262,9 @@ async function main() {
}

main().catch(console.error);

module.exports = {
getSources,
insertAlert,
updateSource,
};
18 changes: 18 additions & 0 deletions src/components/alerts/AlertTitle.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
/** @jsx jsx */

import { jsx, Flex, Box } from "theme-ui";
import PropTypes from "prop-types";
import { AlertStatus } from "./Status";

export function AlertTitle({ alertId, ...props }) {
return (
<Flex sx={{ my: "large", alignItems: "center" }}>
<AlertStatus alertId={alertId} />
<Box as="h2" sx={{ flex: "1 1 auto" }} {...props} />
</Flex>
);
}

AlertTitle.propTypes = {
alertId: PropTypes.string.isRequired,
};
45 changes: 45 additions & 0 deletions src/components/alerts/Status.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
/** @jsx jsx */

import { IoIosCheckmark, IoIosClose } from "react-icons/io";
import { MenuButton, MenuItem } from "../button";
import { jsx } from "theme-ui";
import PropTypes from "prop-types";
import { useMutation } from "urql";

export const alertMutation = `
mutation updateAlertStatus($id:uuid!, $status:String!) {
update_alerts_by_pk(
pk_columns: {
id: $id
}
_set: { status: $status }
){
__typename
}
}
`;

export function AlertStatus({ alertId }) {
const [, executeUpdate] = useMutation(alertMutation);
function updateStatus(status) {
console.log("update statys", alertId, status);
executeUpdate({ id: alertId, status });
}
return (
<MenuButton variant="secondary">
<MenuItem onSelect={() => updateStatus("doing")}>
<span sx={{ display: "inline-block", width: "1.5em" }} />
En cours
</MenuItem>
<MenuItem onSelect={() => updateStatus("done")}>
<IoIosCheckmark style={{ width: "1.5em", height: "1.5em" }} /> Traité
</MenuItem>
<MenuItem onSelect={() => updateStatus("rejected")}>
<IoIosClose style={{ width: "1.5em", height: "1.5em" }} /> Rejeté
</MenuItem>
</MenuButton>
);
}
AlertStatus.propTypes = {
alertId: PropTypes.string.isRequired,
};
33 changes: 32 additions & 1 deletion src/components/button/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,13 @@ import {
MenuList,
MenuItem as ReachMenuItem,
} from "@reach/menu-button";
import { IoMdMore } from "react-icons/io";

import {
AccordionButton as ReachAccordionButton,
useAccordionItemContext,
} from "@reach/accordion";

import { IoMdMore, IoIosArrowForward, IoIosArrowDown } from "react-icons/io";

const buttonPropTypes = {
variant: PropTypes.oneOf(["secondary", "primary", "link"]),
Expand Down Expand Up @@ -188,3 +194,28 @@ export function MenuItem(props) {
/>
);
}

export function AccordionButton({ children, ...props }) {
return (
<ReachAccordionButton
{...props}
sx={{
...defaultButtonStyles,
color: "text",
bg: "white",
border: "none",
"&[aria-expanded=true]": {
color: "accent",
},
}}
>
<ExpandedIcon />
{children}
</ReachAccordionButton>
);
}

export function ExpandedIcon() {
const { isExpanded } = useAccordionItemContext();
return isExpanded ? <IoIosArrowDown /> : <IoIosArrowForward />;
}
27 changes: 27 additions & 0 deletions src/components/changes/ChangeGroup.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import React from "react";
import PropTypes from "prop-types";
import { AccordionButton } from "src/components/button";
import { AccordionItem, AccordionPanel } from "@reach/accordion";

export const ChangesGroup = ({ changes, label, renderChange }) => {
return changes.length > 0 ? (
<AccordionItem>
<AccordionButton>{label}</AccordionButton>
<AccordionPanel>
<ul>{changes.map(renderChange)}</ul>
</AccordionPanel>
</AccordionItem>
) : null;
};

ChangesGroup.propTypes = {
label: PropTypes.string.isRequired,
renderChange: PropTypes.func.isRequired,
changes: PropTypes.arrayOf(
PropTypes.shape({
added: PropTypes.object,
removed: PropTypes.object,
modified: PropTypes.object,
})
),
};
69 changes: 69 additions & 0 deletions src/components/changes/ViewDiff.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
// adapted from https://github.com/davidmason/react-stylable-diff/blob/master/lib/react-diff.js
/** @jsx jsx */

import { useState, useMemo } from "react";
import { jsx } from "theme-ui";
var jsdiff = require("diff");

const fnMap = {
chars: jsdiff.diffChars,
words: jsdiff.diffWords,
sentences: jsdiff.diffSentences,
json: jsdiff.diffJson,
};

export const ViewDiff = ({ sx, type, inputA, inputB }) => {
const [mode, setMode] = useState(type);
const diff = fnMap[mode](inputA, inputB);

const groupName = useMemo(() => Math.random());

const result = diff.map((part, index) => {
if (part.added) {
return (
<ins sx={{ bg: "positive" }} key={index}>
{part.value}
</ins>
);
}
if (part.removed) {
return (
<del sx={{ bg: "critical" }} key={index}>
{part.value}
</del>
);
}
return <span key={index}>{part.value}</span>;
});
return (
<div sx={sx}>
<div sx={{ marginBottom: 20 }}>
Diff mode :
<input
type="radio"
name={groupName}
onClick={() => setMode("words")}
style={{ marginLeft: 10 }}
checked={mode === "words"}
/>{" "}
Mots
<input
type="radio"
name={groupName}
onClick={() => setMode("sentences")}
style={{ marginLeft: 10 }}
checked={mode === "sentences"}
/>{" "}
Phrases
</div>
{result}
</div>
);
};

ViewDiff.defaultProps = {
inputA: "",
inputB: "",
type: "chars",
className: "Difference",
};
Loading