diff --git a/src/components/Checkbox/index.js b/src/components/Checkbox/index.js new file mode 100644 index 00000000..cb00358b --- /dev/null +++ b/src/components/Checkbox/index.js @@ -0,0 +1,17 @@ +const Checkbox = ({ name, selected, onClick }) => { + return ( +
+ +
+ ); +}; + +export default Checkbox; diff --git a/src/components/Layout/Navbar.js b/src/components/Layout/Navbar.js index bca4068e..89f7048f 100644 --- a/src/components/Layout/Navbar.js +++ b/src/components/Layout/Navbar.js @@ -15,7 +15,7 @@ const Navbar = ({setSidebarOpen}) => { return ( <> -
+
- - { - if (moment(fromInput, FORMAT).isValid) - setFromInput(moment(fromInput, FORMAT).format(FORMAT)); - }} - onChange={(e) => { - setFromInput(e.target.value); - }} - className="custom-focus custom-input text-xs" - type="text" - /> - {/*
- + + { + if (moment(fromInput, FORMAT).isValid) + setFromInput(moment(fromInput, FORMAT).format(FORMAT)); + }} + onChange={(e) => { + setFromInput(e.target.value); + }} + className="custom-focus custom-input text-xs" + type="text" /> -
*/} +
- - { - if (moment(toInput, FORMAT).isValid) - setToInput(moment(toInput, FORMAT).format(FORMAT)); - }} - onChange={(e) => { - setToInput(e.target.value); - }} - className="custom-input custom-focus text-xs" - type="text" - /> - {/*
- + + { + if (moment(toInput, FORMAT).isValid) + setToInput(moment(toInput, FORMAT).format(FORMAT)); + }} + onChange={(e) => { + setToInput(e.target.value); + }} + className="custom-input custom-focus text-xs" + type="text" /> -
*/} +
- +
{!checkValidDate() && (
diff --git a/src/page/Dashboard/FieldBox.js b/src/page/Dashboard/FieldBox.js new file mode 100644 index 00000000..fa2c21d6 --- /dev/null +++ b/src/page/Dashboard/FieldBox.js @@ -0,0 +1,45 @@ +import Checkbox from "../../components/Checkbox"; + +const Field = ({ + logStreamSchema, + selectedLogSchema, + setSelectedLogSchema, +}) => { + return ( +
+
+

Columns

+
+
+ {logStreamSchema?.data?.data?.fields + ?.filter((field) => field.name !== "p_metadata" && field.name !== "p_tags") + .map((key, index) => ( + + e.target.checked + ? setSelectedLogSchema( + logStreamSchema.data.data.fields + .map((x) => x.name) + .filter( + (schema) => + selectedLogSchema.includes(schema) || + schema === key.name, + ), + ) + : setSelectedLogSchema( + selectedLogSchema.filter( + (clickedName) => clickedName !== key.name, + ), + ) + } + selected={selectedLogSchema?.includes(key.name)} + /> + ))} +
+
+ ); +}; + +export default Field; diff --git a/src/page/Dashboard/index.js b/src/page/Dashboard/index.js index 5cfe31d3..7758fdb1 100644 --- a/src/page/Dashboard/index.js +++ b/src/page/Dashboard/index.js @@ -1,5 +1,5 @@ import moment from "moment"; -import { useState, Fragment } from "react"; +import { useState, Fragment, useRef, useCallback, useEffect } from "react"; import Layout from "../../components/Layout"; import SideDialog from "../../components/SideDialog"; import { Listbox, Transition } from "@headlessui/react"; @@ -9,9 +9,13 @@ import { Combobox } from "@headlessui/react"; import { CheckIcon, XCircleIcon, SelectorIcon } from "@heroicons/react/solid"; import BeatLoader from "react-spinners/BeatLoader"; import { Menu } from "@headlessui/react"; -import { useGetLogStream, useQueryLogs } from "../../utils/api"; +import { + useGetLogStream, + useGetLogStreamSchema, + useQueryLogs, +} from "../../utils/api"; import "./index.css"; -import Picker from "./DateTimeRangePicker"; +import Field from "./FieldBox"; import Calendar from "./DateRangeSeletor"; const override = { @@ -21,6 +25,8 @@ const override = { }; function hasSubArray(master, sub) { + master.sort(); + sub.sort(); return sub.every( ( (i) => (v) => @@ -28,6 +34,7 @@ function hasSubArray(master, sub) { )(0), ); } + const Dashboard = () => { const getCurrentTime = () => { let now = new Date(); @@ -45,6 +52,7 @@ const Dashboard = () => { return moment(start); }; + const getRange = () => { return { // "Live tracking": [moment(start), moment(end)], @@ -80,13 +88,24 @@ const Dashboard = () => { const [searchSelected, setSearchSelected] = useState({}); const [interval, setInterval] = useState(null); const [range, setRange] = useState(0); - const [dateRangeValues, setDateRangeValues] = useState(getRange); + const [selectedLogSchema, setSelectedLogSchema] = useState([]); + const [availableTags, setAvailableTags] = useState([]); + const [selectedTags, setSelectedTags] = useState([]); + // const [dateRangeValues, setDateRangeValues] = useState(getRange); const [startTime, setStartTime] = useState( getCurrentTime().subtract(10, "minutes"), // .utcOffset("+00:00") // .format("YYYY-MM-DDThh:mm:ss), ); + const addAvailableTags = (label) => { + if (availableTags.includes(label)) { + return; + } else { + setAvailableTags([...availableTags, label]); + } + }; + const [endTime, setEndTime] = useState(getCurrentTime()); const refreshInterval = [ @@ -135,38 +154,73 @@ const Dashboard = () => { const logStream = useGetLogStream({ retry: false, - staleTime: 60 * 1000, refetchOnWindowFocus: false, + onSuccess: (data) => { + setSelectedLogStream(data.data[0]); + }, + }); + + const logStreamSchema = useGetLogStreamSchema(selectedLogStream?.name, { + retry: false, + enabled: !!(selectedLogStream?.name != null), + refetchOnWindowFocus: false, + onSuccess: (data) => { + const allFields = data.data.fields.map((field) => { + return field.name; + }); + + setSelectedLogSchema([ + ...allFields.filter( + (field) => field !== "p_metadata" && field !== "p_tags", + ), + ]); + }, }); - if ( - logStream.isSuccess && - logStream?.data?.data.length && - !selectedLogStream - ) { - setSelectedLogStream(logStream.data.data[0]); - } const logQueries = useQueryLogs( selectedLogStream?.name, moment(startTime).utcOffset("+00:00").format("YYYY-MM-DDTHH:mm:ssZ"), moment(endTime).utcOffset("+00:00").format("YYYY-MM-DDTHH:mm:ssZ"), + selectedLogSchema, () => { if (range < 7) { const rangeVal = getRange(); - setDateRangeValues(rangeVal); setStartTime(rangeVal[rangeArr[range]][0]); setEndTime(rangeVal[rangeArr[range]][1]); } }, { + onSuccess: () => { + setSelectedTags([]); + setAvailableTags([]); + }, retry: false, - enabled: !!(selectedLogStream?.name != null), + enabled: !!(selectedLogSchema?.length !== 0), refetchOnWindowFocus: false, refetchInterval: interval === null || range === 7 ? false : interval * 1000, }, ); + const { fetchNextPage } = logQueries; + + useEffect(() => { + let fetching = false; + const handleScroll = async (e) => { + const { scrollHeight, scrollTop, clientHeight } = + e.target.scrollingElement; + if (!fetching && scrollHeight - scrollTop <= clientHeight * 1.2) { + fetching = true; + await fetchNextPage(); + fetching = false; + } + }; + document.addEventListener("scroll", handleScroll); + return () => { + document.removeEventListener("scroll", handleScroll); + }; + }, [fetchNextPage]); + const timeZoneChange = (e) => { setTimeZone(e.target.value); }; @@ -187,21 +241,20 @@ const Dashboard = () => { } }; - const clearLabel = (label) => { - const labelArray = labelSelected; - const filteredArray = [...labelArray.filter((item) => item !== label)]; - setLabelSelected(filteredArray); + const removeTag = (tag) => { + setSelectedTags([...selectedTags.filter((item) => item !== tag)]); }; return ( <> 0 && logQueries?.data?.data[0]?.labels + logQueries?.data?.data?.length > 0 && + logQueries?.data?.data[0]?.labels } > -
-
+
+
@@ -341,7 +394,7 @@ const Dashboard = () => { />
- { )) )} - + */}
@@ -444,7 +497,7 @@ const Dashboard = () => { } > {refreshInterval.map((interval) => ( - + {({ active, selected }) => (
setInterval(interval.value)} @@ -467,18 +520,18 @@ const Dashboard = () => { Tag filters
- {labelSelected.length > 0 - ? labelSelected.map((label) => ( + {selectedTags.length > 0 + ? selectedTags.map((tag) => ( - {label} + {tag} clearLabel(label)} + onClick={() => removeTag(tag)} className="hover:text-gray-600 transform duration-200 text-gray-700 w-4 absolute top-1 right-1" /> @@ -498,43 +551,39 @@ const Dashboard = () => { leaveTo="opacity-0" > - {Object.keys( - logQueries?.data?.data ? logQueries?.data?.data : [], - ).length !== 0 ? ( - logQueries?.data?.data[0].labels - ?.split(",") - .map((person, personIdx) => ( - - `relative cursor-default select-none py-2 px-2 ${ - active - ? "bg-bluePrimary text-white" - : "text-gray-900" - }` - } - value={person} - > - {({ selected }) => ( - <> - - {selected ? ( -
- -
- ) : ( -
- )} - {person} -
- - )} -
- )) + {availableTags.length !== 0 ? ( + availableTags.map((person, personIdx) => ( + + `relative cursor-default select-none py-2 px-2 ${ + active + ? "bg-bluePrimary text-white" + : "text-gray-900" + }` + } + value={person} + > + {({ selected }) => ( + <> + + {selected ? ( +
+ +
+ ) : ( +
+ )} + {person} +
+ + )} +
+ )) ) : ( Nothing Found )} @@ -552,123 +601,118 @@ const Dashboard = () => {
*/} -
-
-
- - - - + {logQueries.isLoading && + (!logQueries.data || + !logQueries.data?.data || + logQueries.data?.data?.length === 0) ? ( + + + + + + ) : ( + + {logQueries?.data?.pages?.map && + logQueries.data.pages.map( + (page) => + page?.data?.map && + page?.data?.map( + (data, index) => + hasSubArray( + data.p_tags?.split(","), + selectedTags, + ) && + (searchQuery === "" || + JSON.stringify(data) + .toLowerCase() + .includes(searchQuery.toLowerCase())) && ( + { + console.log(JSON.stringify(data)); + setOpen(true); + setClickedRow(data); + }} + className="cursor-pointer hover:bg-slate-100 hover:shadow" + key={index} + > + {selectedLogSchema.map((schema) => ( + + ))} + + + ), + ), + )} + + + + + )} +
-
Time
- - + + + {selectedLogSchema?.map((name) => ( + + ))} + - - - - - - {logQueries.fetchStatus !== "idle" && - (!logQueries.data || - !logQueries.data?.data || - logQueries.data?.data?.length === 0) ? ( - - - - - + Tags + - - ) : ( - - {logQueries?.data?.data?.map && - logQueries?.data?.data?.map( - (data, index) => - hasSubArray( - data.labels?.split(","), - labelSelected, - ) && ( - { - setOpen(true); - setClickedRow(data); - }} - className="cursor-pointer hover:bg-slate-100 hover:shadow" - key={index} - > - - - - - - ), - )} - - )} -
+ {name} + - - {/* */} - - - - Log - - Tags - - Edit -
- -
- {timeZone === "UTC" || timeZone === "GMT" - ? moment - .utc(data.time) - .format("DD/MM/YYYY, HH:mm") - : moment(data.time) - .utcOffset("+05:30") - .format("DD/MM/YYYY, HH:mm")} - - {data.log} - - {data.labels - ?.split(",") - .filter((tag, index) => index <= 2) - .map((tag, index) => ( -
- {tag} -
- ))} -
- {data.labels - ?.split(",") - .filter((tag, index) => index <= 1) - .map((tag, index) => ( -
- {tag} -
- ))} -
+
+ +
+ {data[schema] || ""} + + {data.p_tags + ?.split(",") + .map((tag, index) => { + addAvailableTags(tag); + return ( +
+ {tag} +
+ ); + })} +
+ +
+
{logStream.isError || !logStream?.data?.data.length ? ( diff --git a/src/utils/api/constants.js b/src/utils/api/constants.js index 9140c9b4..4a47c059 100644 --- a/src/utils/api/constants.js +++ b/src/utils/api/constants.js @@ -1,5 +1,7 @@ export const LOG_STREAMS = "log_stream"; export const LOG_STREAMS_URL = "api/v1/logstream"; +export const LOG_STREAMS_SCHEMA = "logstream_schema"; + export const QUERY = "query"; export const QUERY_URL = "api/v1/query"; diff --git a/src/utils/api/logstreams.js b/src/utils/api/logstreams.js index 04ce6948..6e2f9afa 100644 --- a/src/utils/api/logstreams.js +++ b/src/utils/api/logstreams.js @@ -1,9 +1,21 @@ import { useQuery } from "@tanstack/react-query"; import { get } from "./index"; -import { LOG_STREAMS, LOG_STREAMS_URL } from "./constants"; +import { + LOG_STREAMS, + LOG_STREAMS_URL, +} from "./constants"; const getLogStream = async () => { return await get(LOG_STREAMS_URL); }; -export const useGetLogStream = (option = {}) => useQuery([LOG_STREAMS], getLogStream, option); \ No newline at end of file +export const useGetLogStream = (option = {}) => + useQuery([LOG_STREAMS], getLogStream, {...option}); + + +const getLogStreamSchema = async (streamName) => { + return await get(`${LOG_STREAMS_URL}/${streamName}/schema`); +}; + +export const useGetLogStreamSchema = (streamName, option = {}) => + useQuery([streamName], () => getLogStreamSchema(streamName), option); \ No newline at end of file diff --git a/src/utils/api/query.js b/src/utils/api/query.js index 658b7f5a..a6fa2d99 100644 --- a/src/utils/api/query.js +++ b/src/utils/api/query.js @@ -1,12 +1,32 @@ -import { useQuery } from "@tanstack/react-query"; +import { useInfiniteQuery } from "@tanstack/react-query"; import { post } from "./index"; import { QUERY_URL, QUERY } from "./constants"; -const queryLogs = async (streamName, startTime, endTime, signal) => { +const queryLogs = async ( + streamName, + startTime, + endTime, + signal, + pageParam, + selectedLogSchema, +) => { + let dateStream = null; + + for (let index in selectedLogSchema) { + if ( + selectedLogSchema[index].includes("date") || + selectedLogSchema[index].includes("time") + ) { + dateStream = selectedLogSchema[index]; + } + } + return await post( QUERY_URL, { - query: `select * from ${streamName}`, + query: `select * from ${streamName} ${ + dateStream !== null ? `order by ${dateStream}` : "" + } limit 10 ${pageParam === 1 ? "" : `offset ${pageParam * 10}`} `, startTime: startTime, endTime: endTime, }, @@ -14,12 +34,32 @@ const queryLogs = async (streamName, startTime, endTime, signal) => { ); }; -export const useQueryLogs = (streamName, startTime, endTime, fn, option = {}) => - useQuery( +export const useQueryLogs = ( + streamName, + startTime, + endTime, + selectedLogSchema, + fn, + option = {}, +) => + useInfiniteQuery( [QUERY, streamName, startTime, endTime], - async ({ signal }) => { + async ({ signal, pageParam = 1 }) => { await fn(); - return await queryLogs(streamName, startTime, endTime, signal); + return await queryLogs( + streamName, + startTime, + endTime, + signal, + pageParam, + selectedLogSchema, + ); + }, + { + ...option, + getNextPageParam: (lastPage, allPages) => { + const nextPage = allPages.length + 1; + return lastPage.data.length !== 0 ? nextPage : undefined; + }, }, - option, );