Skip to content

Commit

Permalink
[TM-1531] add job connection (#745)
Browse files Browse the repository at this point in the history
* [TM-1531] add job service url and script

* [TM-1531] apiSlice add job resource

* [TM-1531] updates for job service

* [TM-1531] sort alphabetical

* [TM-1531] add connection for list the delayed jobs

* [TM-1531] remove unnecesary code and consistency in selector and API request

* [TM-1531] add bulk update call on clear

* [TM-1531] add entityname and display data in notification

* [TM-1531] use store and display only not acknowledged

* [TM-1531] remove from apifetcher call and add continous call for delayed jobs

* [TM-1531] rollback apifetcher functionality

* [TM-1531] adding message to notification

* [TM-1531] display error messages in notifications

* [TM-1531] add error messages for failed jobs

* [TM-1531] improve connection and remove awaits and asyncs

* [TM-1531] changes and remove log

* [TM-1531] yarn generate:services

* [TM-1531] divide names loading for connections

* [TM-1531] running with build

* [TM-1531] only send not pending

* [TM-1531] fix loaded delayedjobs and filter

* [TM-1531] remove logs and unused variable

* [TM-1531] join both connections

* [TM-1531] transifex and fix selector
  • Loading branch information
egrojMonroy authored Dec 26, 2024
1 parent 9f57613 commit 2d6f02d
Show file tree
Hide file tree
Showing 9 changed files with 229 additions and 53 deletions.
8 changes: 1 addition & 7 deletions src/admin/components/ResourceTabs/PolygonReviewTab/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,6 @@ import { IconNames } from "@/components/extensive/Icon/Icon";
import ModalAdd from "@/components/extensive/Modal/ModalAdd";
import ModalConfirm from "@/components/extensive/Modal/ModalConfirm";
import { ModalId } from "@/components/extensive/Modal/ModalConst";
import { useLoading } from "@/context/loaderAdmin.provider";
import { useMapAreaContext } from "@/context/mapArea.provider";
import { useModalContext } from "@/context/modal.provider";
import { useMonitoredDataContext } from "@/context/monitoredData.provider";
Expand Down Expand Up @@ -153,7 +152,6 @@ const PolygonReviewTab: FC<IProps> = props => {
const [saveFlags, setSaveFlags] = useState<boolean>(false);
const [polygonFromMap, setPolygonFromMap] = useState<IpolygonFromMap>({ isOpen: false, uuid: "" });
const [errorMessage, setErrorMessage] = useState<string | null>(null);
const { showLoader, hideLoader } = useLoading();
const {
setSelectedPolygonsInCheckbox,
setPolygonCriteriaMap,
Expand Down Expand Up @@ -315,8 +313,7 @@ const PolygonReviewTab: FC<IProps> = props => {
}, [shouldRefetchValidation]);
const uploadFiles = async () => {
const uploadPromises = [];
showLoader();

closeModal(ModalId.ADD_POLYGON);
for (const file of files) {
const fileToUpload = file.rawFile as File;
const site_uuid = record.uuid;
Expand Down Expand Up @@ -351,10 +348,8 @@ const PolygonReviewTab: FC<IProps> = props => {
}
refetch();
refetchSiteBbox();
closeModal(ModalId.ADD_POLYGON);
setPolygonLoaded(false);
setSubmitPolygonLoaded(false);
hideLoader();
} catch (error) {
let errorMessage;

Expand All @@ -380,7 +375,6 @@ const PolygonReviewTab: FC<IProps> = props => {
errorMessage = t("An unknown error occurred");
}
openNotification("error", t("Error uploading file"), errorMessage || t("An unknown error occurred"));
hideLoader();
}
};

Expand Down
19 changes: 2 additions & 17 deletions src/admin/modules/sites/components/SiteShow.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import { FC, useState } from "react";
import { FC } from "react";
import { Show, TabbedShowLayout } from "react-admin";

import ShowActions from "@/admin/components/Actions/ShowActions";
import DelayedJobsProgressAlert from "@/admin/components/Alerts/DelayedJobsProgressAlert";
import AuditLogTab from "@/admin/components/ResourceTabs/AuditLogTab/AuditLogTab";
import { AuditLogButtonStates } from "@/admin/components/ResourceTabs/AuditLogTab/constants/enum";
import ChangeRequestsTab from "@/admin/components/ResourceTabs/ChangeRequestsTab/ChangeRequestsTab";
Expand All @@ -16,9 +15,6 @@ import { RecordFrameworkProvider } from "@/context/framework.provider";
import { MapAreaProvider } from "@/context/mapArea.provider";

const SiteShow: FC = () => {
const [isLoadingDelayedJob, setIsLoadingDelayedJob] = useState(false);
const [alertTitle, setAlertTitle] = useState("");

return (
<Show
title={<ShowTitle moduleName="Site" getTitle={record => record?.name} />}
Expand All @@ -30,13 +26,7 @@ const SiteShow: FC = () => {
<InformationTab type="sites" />
<TabbedShowLayout.Tab label="Polygon Review">
<MapAreaProvider>
<PolygonReviewTab
label=""
type={"sites"}
setIsLoadingDelayedJob={setIsLoadingDelayedJob!}
isLoadingDelayedJob={isLoadingDelayedJob!}
setAlertTitle={setAlertTitle!}
/>
<PolygonReviewTab label="" type={"sites"} />
</MapAreaProvider>
</TabbedShowLayout.Tab>
<GalleryTab label="Site Gallery" entity="sites" />
Expand All @@ -46,11 +36,6 @@ const SiteShow: FC = () => {
<AuditLogTab entity={AuditLogButtonStates.SITE} />
</TabbedShowLayout>
</RecordFrameworkProvider>
<DelayedJobsProgressAlert
show={isLoadingDelayedJob}
title={alertTitle}
setIsLoadingDelayedJob={setIsLoadingDelayedJob!}
/>
</Show>
);
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,7 @@ const CheckPolygonControl = (props: CheckSitePolygonProps) => {

const runFixPolygonOverlaps = () => {
if (siteUuid) {
closeModal(ModalId.FIX_POLYGONS);
setIsLoadingDelayedJob?.(true);
setAlertTitle?.("Fix Polygons");
clipPolygons({ pathParams: { uuid: siteUuid } });
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,7 @@ const ProcessBulkPolygonsControl = ({
className: "px-8 py-3",
variant: "primary",
onClick: () => {
closeModal(ModalId.FIX_POLYGONS);
setIsLoadingDelayedJob?.(true);
setAlertTitle?.("Fix Polygons");
fixPolygons(
Expand All @@ -124,7 +125,7 @@ const ProcessBulkPolygonsControl = ({
{
onSuccess: response => {
const processedNames = response?.processed?.map(item => item.poly_name).join(", ");
closeModal(ModalId.FIX_POLYGONS);

setIsLoadingDelayedJob?.(false);
ApiSlice.addTotalContent(0);
ApiSlice.addProgressContent(0);
Expand Down
146 changes: 119 additions & 27 deletions src/components/elements/Notification/FloatNotification.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
import { LinearProgress } from "@mui/material";
import { useT } from "@transifex/react";
import classNames from "classnames";
import { useState } from "react";
import { useEffect, useState } from "react";
import { When } from "react-if";

import Icon, { IconNames } from "@/components/extensive/Icon/Icon";
import { triggerBulkUpdate, useDelayedJobs } from "@/connections/DelayedJob";
import { DelayedJobData, DelayedJobDto } from "@/generated/v3/jobService/jobServiceSchemas";
import { getErrorMessageFromPayload } from "@/utils/errors";

import LinearProgressBar from "../ProgressBar/LinearProgressBar/LinearProgressBar";
import Text from "../Text/Text";
Expand All @@ -17,8 +22,50 @@ export interface FloatNotificationProps {
data: FloatNotificationDataProps[];
}

const FloatNotification = ({ data }: FloatNotificationProps) => {
const FloatNotification = () => {
const t = useT();
const [openModalNotification, setOpenModalNotification] = useState(false);
const [isLoaded, { delayedJobs }] = useDelayedJobs();
const [notAcknowledgedJobs, setNotAcknowledgedJobs] = useState<DelayedJobDto[]>([]);
const clearJobs = () => {
if (delayedJobs === undefined) return;
const newJobsData: DelayedJobData[] = delayedJobs
.filter((job: DelayedJobDto) => job.status !== "pending")
.map((job: DelayedJobDto) => {
return {
uuid: job.uuid,
type: "delayedJobs",
attributes: {
isAcknowledged: true
}
};
});
triggerBulkUpdate(newJobsData);
};
useEffect(() => {
if (delayedJobs === undefined) return;
const notAcknowledgedJobs = delayedJobs.filter((job: DelayedJobDto) => !job.isAcknowledged);
setNotAcknowledgedJobs(notAcknowledgedJobs);
}, [delayedJobs]);
useEffect(() => {
if (!notAcknowledgedJobs.length) {
setOpenModalNotification(false);
}
}, [notAcknowledgedJobs]);
const listOfPolygonsFixed = (data: Record<string, any> | null) => {
if (data?.updated_polygons) {
const updatedPolygonNames = data.updated_polygons
?.map((p: any) => p.poly_name)
.filter(Boolean)
.join(", ");
if (updatedPolygonNames) {
return "Success! The following polygons have been fixed: " + updatedPolygonNames;
} else {
return "No polygons were fixed";
}
}
return null;
};

return (
<div className="fixed bottom-10 right-10 z-50">
Expand All @@ -31,43 +78,88 @@ const FloatNotification = ({ data }: FloatNotificationProps) => {
)}
>
<Text variant="text-20-bold" className="border-b border-grey-350 p-6 text-blueCustom-900">
Notifications
{t("Notifications")}
</Text>
<div className="flex flex-col overflow-hidden px-6 pb-8 pt-6">
<div className="mb-2 flex items-center justify-between">
<Text variant="text-14-light" className="text-neutral-400">
Actions Taken
{t("Actions Taken")}
</Text>
<Text variant="text-12-semibold" className="text-primary">
Clear completed
<Text variant="text-12-semibold" className="text-primary" onClick={clearJobs}>
{t("Clear completed")}
</Text>
</div>
<div className="-mr-2 flex flex-1 flex-col gap-3 overflow-auto pr-2">
{data.map((item, index) => (
<div key={index} className="rounded-lg border-2 border-grey-350 bg-white p-4 hover:border-primary">
<div className="mb-2 flex items-center gap-1">
<div className="h-2 w-2 rounded-full bg-primary" />
<Text variant="text-14-light" className="leading-[normal] text-darkCustom " as={"span"}>
{item.label}
</Text>
</div>
<Text variant="text-14-light" className="text-darkCustom">
Site: <b>{item.site}</b>
</Text>
<div className="mt-2 flex items-center gap-2">
<LinearProgressBar value={parseInt(item.value)} className="h-2 bg-success-40" color="success-600" />
<Text variant="text-12-semibold" className="text-black">
{item.value}
{isLoaded &&
notAcknowledgedJobs &&
notAcknowledgedJobs.map((item, index) => (
<div key={index} className="rounded-lg border-2 border-grey-350 bg-white p-4 hover:border-primary">
<div className="mb-2 flex items-center gap-1">
<div className="h-2 w-2 rounded-full bg-primary" />
<Text variant="text-14-light" className="leading-[normal] text-darkCustom " as={"span"}>
{item.name}
</Text>
</div>
<Text variant="text-14-light" className="text-darkCustom">
Site: <b>{item.entityName}</b>
</Text>
<div className="mt-2">
{item.status === "failed" ? (
<Text variant="text-12-semibold" className="text-error-600">
{item.payload ? t(getErrorMessageFromPayload(item.payload)) : t("Failed to complete")}
</Text>
) : (
<div className="flex items-center gap-2">
{item.name === "Polygon Upload" &&
(item.processedContent === null || item.totalContent === null) &&
item.status === "pending" ? (
<div style={{ width: "100%" }}>
<LinearProgress
sx={{
height: 9,
borderRadius: 99,
backgroundColor: "#a9e7d6",
"& .MuiLinearProgress-bar": { backgroundColor: "#29c499" }
}}
/>
</div>
) : (
<LinearProgressBar
value={
item.status === "succeeded"
? 100
: ((item.processedContent ?? 0) / (item.totalContent ?? 1)) * 100
}
className="h-2 bg-success-40"
color="success-600"
/>
)}
<Text variant="text-12-semibold" className="text-black">
{item.name === "Polygon Upload"
? item.status === "succeeded"
? t("Done!")
: ""
: item.status === "succeeded"
? t("Done!")
: `${Math.round(((item.processedContent ?? 0) / (item.totalContent ?? 1)) * 100)}%`}
</Text>
</div>
)}

{item.status === "succeeded" && (
<Text variant="text-12-light" className="mt-2 text-neutral-500">
{listOfPolygonsFixed(item.payload)}
</Text>
)}
</div>
</div>
</div>
))}
))}
</div>
</div>
</div>
<When condition={data.length > 0}>
<When condition={isLoaded && (notAcknowledgedJobs ?? []).length > 0}>
<div className="text-14-bold absolute right-[-5px] top-[-5px] z-20 flex min-h-[24px] min-w-[24px] items-center justify-center rounded-full bg-red-300 leading-[normal] text-white">
{data.length}
{notAcknowledgedJobs?.length}
</div>
</When>
<button
Expand All @@ -77,8 +169,8 @@ const FloatNotification = ({ data }: FloatNotificationProps) => {
className={classNames(
"z-10 flex h-15 w-15 items-center justify-center rounded-full border border-grey-950 bg-primary duration-300 hover:scale-105",
{
hidden: data.length < 1,
visible: data.length > 0
hidden: (notAcknowledgedJobs?.length ?? 0) === 0,
visible: (notAcknowledgedJobs?.length ?? 0) > 0
}
)}
>
Expand Down
69 changes: 69 additions & 0 deletions src/connections/DelayedJob.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import { useEffect } from "react";
import { createSelector } from "reselect";

import { bulkUpdateJobs, listDelayedJobs } from "@/generated/v3/jobService/jobServiceComponents";
import { bulkUpdateJobsFetchFailed, bulkUpdateJobsIsFetching } from "@/generated/v3/jobService/jobServicePredicates";
import { DelayedJobData, DelayedJobDto } from "@/generated/v3/jobService/jobServiceSchemas";
import { useConnection } from "@/hooks/useConnection";
import { ApiDataStore } from "@/store/apiSlice";
import { Connection } from "@/types/connection";

type DelayedJobCombinedConnection = {
delayedJobs?: DelayedJobDto[] | undefined;
delayedJobsIsLoading: boolean;
delayedJobsHasFailed: boolean;
bulkUpdateJobsIsLoading: boolean;
bulkUpdateJobsHasFailed: boolean;
updatedJobsResponse?: DelayedJobDto[];
};

const delayedJobsSelector = (store: ApiDataStore) => store.delayedJobs;

const combinedSelector = createSelector(
[delayedJobsSelector, bulkUpdateJobsIsFetching, bulkUpdateJobsFetchFailed],
(delayedJobs, bulkUpdateJobsIsLoading, bulkUpdateJobsFailure) => ({
delayedJobs: Object.values(delayedJobs ?? {}).map(resource => resource.attributes),
delayedJobsIsLoading: delayedJobs == null && !bulkUpdateJobsFailure,
delayedJobsHasFailed: Boolean(bulkUpdateJobsFailure),
bulkUpdateJobsIsLoading,
bulkUpdateJobsHasFailed: bulkUpdateJobsFailure != null,
updatedJobsResponse: Object.values(delayedJobs ?? {}).map(resource => resource.attributes as DelayedJobDto)
})
);

const combinedLoad = (connection: DelayedJobCombinedConnection) => {
if (!combinedIsLoaded(connection)) {
listDelayedJobs();
}
};

const combinedIsLoaded = ({
delayedJobs,
delayedJobsHasFailed,
bulkUpdateJobsIsLoading,
bulkUpdateJobsHasFailed
}: DelayedJobCombinedConnection) =>
(delayedJobs != null || delayedJobsHasFailed) && !bulkUpdateJobsIsLoading && !bulkUpdateJobsHasFailed;

const delayedJobsCombinedConnection: Connection<DelayedJobCombinedConnection> = {
load: combinedLoad,
isLoaded: combinedIsLoaded,
selector: combinedSelector
};

export const useDelayedJobs = () => {
const connection = useConnection(delayedJobsCombinedConnection);

useEffect(() => {
const intervalId = setInterval(() => {
listDelayedJobs();
}, 1500);

return () => {
clearInterval(intervalId);
};
}, []);

return connection;
};
export const triggerBulkUpdate = (jobs: DelayedJobData[]) => bulkUpdateJobs({ body: { data: jobs } });
2 changes: 1 addition & 1 deletion src/context/floatNotification.provider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ const FloatNotificationProvider = ({ children }: FloatNotificationProviderProps)
return (
<FloatNotificationContext.Provider value={value}>
{children}
<FloatNotification data={data} />
<FloatNotification />
</FloatNotificationContext.Provider>
);
};
Expand Down
Loading

0 comments on commit 2d6f02d

Please sign in to comment.