Skip to content

Commit

Permalink
feat: config details page API integration - fetch JSON config
Browse files Browse the repository at this point in the history
  • Loading branch information
johnflank committed Mar 30, 2022
1 parent 4c9657a commit f8f6baf
Show file tree
Hide file tree
Showing 3 changed files with 111 additions and 61 deletions.
8 changes: 8 additions & 0 deletions src/components/ConfigViewer/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,3 +23,11 @@ export function hasStringMatch(pattern, text) {
}
return text.indexOf(pattern) >= 0;
}

export function escapeQuotes(string) {
return string.replace(/"/g, '\\"');
}

export function unescapeQuotes(string) {
return string.replace(/\\"/g, '"');
}
163 changes: 103 additions & 60 deletions src/pages/config/config-details.js
Original file line number Diff line number Diff line change
@@ -1,66 +1,75 @@
import { useEffect, useState } from "react";
import toast from "react-hot-toast";
import { useNavigate } from "react-router-dom";
import { useNavigate, useParams } from "react-router-dom";
import {
decodeUrlSearchParams,
updateParams
} from "../../components/Canary/url";

import { SearchLayout } from "../../components/Layout";
import { toastError } from "../../components/Toast/toast";
import jsonString from "../../data/sampleConfig.json";

const mockName = "package.json"; // TODO: get actual name from API call
const linesArray = JSON.stringify(jsonString, undefined, 4).split("\n");
const linesArrayWithIndex = linesArray.reduce((acc, currLine, idx) => {
acc[idx] = currLine;
return acc;
}, {});
import { Modal } from "../../components/Modal";
import { IncidentCreate } from "../../components/Incidents/IncidentCreate";
import { getConfig } from "../../api/services/configs";
import { Loading } from "../../components/Loading";

export function ConfigDetailsPage() {
const navigate = useNavigate();
const { id } = useParams();
const [isLoading, setIsLoading] = useState(true);
const [showIncidentModal, setShowIncidentModal] = useState(false);
const [checked, setChecked] = useState({});
const [configDetails, setConfigDetails] = useState();
const [jsonLines, setJsonLines] = useState([]);

useEffect(() => {
const { selected } = decodeUrlSearchParams(window.location.search);
if (selected) {
const decoded = selected.split(",").map((str) => parseInt(str, 10));
const newChecked = {};
decoded.forEach((lineNum) => {
newChecked[lineNum] = linesArrayWithIndex[lineNum];
getConfig(id)
.then((res) => {
setConfigDetails(res?.data[0]);
})
.catch((err) => toastError(err))
.finally(() => {
setIsLoading(false);
});
setChecked(newChecked);
}
}, []);

const getSelectedEncodedURL = () => {
const selectedArr = Object.keys(checked);
const encoded = encodeURIComponent(selectedArr);
return encoded;
};
useEffect(() => {
const json = configDetails?.config;
if (json) {
const jsonLines = json.split("\n");
setJsonLines(
jsonLines.reduce((acc, currLine, idx) => {
acc[idx] = currLine;
return acc;
}, {})
);
const { selected } = decodeUrlSearchParams(window.location.search);
if (selected) {
const newChecked = selected.reduce((acc, lineNum) => {
acc[lineNum] = true;
return acc;
}, {});
setChecked(newChecked);
}
}
}, [configDetails]);

const handleCheck = (index, value) => {
const handleClick = (index) => {
const selected = !checked[index];
const newChecked = { ...checked };
if (value) {
newChecked[index] = linesArrayWithIndex[index];
setChecked(newChecked);
} else {
const newChecked = { ...checked };
newChecked[index] = selected;
if (!selected) {
delete newChecked[index];
setChecked(newChecked);
}

setChecked(newChecked);
updateParams({
selected: Object.keys(newChecked).reduce(
(acc, curr) => `${acc}${acc.length > 0 ? "," : ""}${curr}`,
""
)
selected: Object.keys(newChecked).map((key) => parseInt(key, 10))
});
};

const handleShare = () => {
const { origin, pathname } = window.location;
const copyString = `${origin}${pathname}?selected=${getSelectedEncodedURL()}`;
const { href } = window.location;
const copyString = `${href}`;
if (window.isSecureContext) {
navigator.clipboard.writeText(copyString).then(() => {
toast("Copied to clipboard");
Expand All @@ -73,7 +82,13 @@ export function ConfigDetailsPage() {
};

return (
<SearchLayout title={`Config Details for ${mockName}`}>
<SearchLayout
title={
configDetails?.name
? `Config Details for ${configDetails.name}`
: "Config Details"
}
>
<div className="flex flex-col items-start">
<div className="mb-4 flex flex-row iems-center">
<button
Expand All @@ -93,6 +108,7 @@ export function ConfigDetailsPage() {
type="button"
onClick={() => {
setChecked({});
updateParams({ selected: null });
}}
>
Clear
Expand All @@ -109,43 +125,70 @@ export function ConfigDetailsPage() {
<button
className="border rounded-md px-3 py-1 text-sm"
type="button"
onClick={() => {}}
onClick={() => setShowIncidentModal(true)}
>
Create Incident
</button>
</>
)}
</div>
<div className="flex flex-col w-full border">
{Object.entries(linesArrayWithIndex).map(([lineIndex, line]) => (
<div
key={lineIndex}
style={{
background: checked[lineIndex] ? "#cfe3ff" : "",
borderColor: checked[lineIndex] ? "#cfe3ff" : ""
}}
className="border-b flex flex-row"
>
<div className="px-1 flex items-center justify-center">
<input
checked={checked[lineIndex]}
type="checkbox"
onChange={(e) => handleCheck(lineIndex, e.target.checked)}
/>
</div>

<div className="flex flex-col w-full border rounded-md">
{!isLoading ? (
Object.entries(jsonLines).map(([lineIndex, line]) => (
<div
className="border-l border-r w-10 mr-2 text-sm flex items-center justify-end px-1"
key={lineIndex}
style={{
background: checked[lineIndex] ? "#cfe3ff" : "",
borderColor: checked[lineIndex] ? "#cfe3ff" : ""
}}
className="flex flex-row"
>
{lineIndex}
<button
type="button"
onClick={() => handleClick(lineIndex)}
className="flex text-xs mr-2 select-none border-r w-12 justify-between pb-px"
>
<span
className="w-4 flex items-center justify-center"
style={{
color: checked[lineIndex] ? "#dd0707" : "#086008"
}}
>
{checked[lineIndex] ? "-" : "+"}
</span>
<div
className="text-xs flex items-center justify-end px-1 text-gray-600"
style={{
borderColor: checked[lineIndex] ? "#cfe3ff" : ""
}}
>
{lineIndex}
</div>
</button>

<code className="whitespace-pre-wrap text-xs text-gray-800">
{line}
</code>
</div>
<code className="whitespace-pre-wrap text-sm">{line}</code>
))
) : (
<div className="h-32 flex items-center justify-center">
<Loading />
</div>
))}
)}
</div>
<Modal
open={showIncidentModal}
onClose={() => setShowIncidentModal(false)}
size="small"
title="Create New Incident from Selected Evidence"
>
<IncidentCreate
callback={(response) => {
navigate(`/incidents/${response.id}`, { replace: true });
}}
/>
</Modal>
</div>
</SearchLayout>
);
Expand Down
1 change: 0 additions & 1 deletion src/pages/config/config-list.js
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,6 @@ export function ConfigListPage() {
};

useEffect(() => {
console.log("data update", data);
let filteredData = data;
if (data?.length > 0) {
// do filtering here
Expand Down

0 comments on commit f8f6baf

Please sign in to comment.