IO-3092 add messaging support for imgproxy

This commit is contained in:
Patrick Fic
2025-02-26 20:56:13 -08:00
parent 66671385d0
commit 25b289b65d
5 changed files with 136 additions and 102 deletions

View File

@@ -1,7 +1,8 @@
import { PictureFilled } from "@ant-design/icons"; import { PictureFilled } from "@ant-design/icons";
import { useQuery } from "@apollo/client"; import { useQuery } from "@apollo/client";
import { useSplitTreatments } from "@splitsoftware/splitio-react";
import { Badge, Popover } from "antd"; import { Badge, Popover } from "antd";
import React, { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { connect } from "react-redux"; import { connect } from "react-redux";
import { createStructuredSelector } from "reselect"; import { createStructuredSelector } from "reselect";
@@ -9,6 +10,7 @@ import { GET_DOCUMENTS_BY_JOB } from "../../graphql/documents.queries";
import { selectBodyshop } from "../../redux/user/user.selectors"; import { selectBodyshop } from "../../redux/user/user.selectors";
import AlertComponent from "../alert/alert.component"; import AlertComponent from "../alert/alert.component";
import JobDocumentsGalleryExternal from "../jobs-documents-gallery/jobs-documents-gallery.external.component"; import JobDocumentsGalleryExternal from "../jobs-documents-gallery/jobs-documents-gallery.external.component";
import JobsDocumentImgproxyGalleryExternal from "../jobs-documents-imgproxy-gallery/jobs-documents-imgproxy-gallery.external.component";
import JobDocumentsLocalGalleryExternal from "../jobs-documents-local-gallery/jobs-documents-local-gallery.external.component"; import JobDocumentsLocalGalleryExternal from "../jobs-documents-local-gallery/jobs-documents-local-gallery.external.component";
import LoadingSpinner from "../loading-spinner/loading-spinner.component"; import LoadingSpinner from "../loading-spinner/loading-spinner.component";
@@ -23,6 +25,13 @@ export default connect(mapStateToProps, mapDispatchToProps)(ChatMediaSelector);
export function ChatMediaSelector({ bodyshop, selectedMedia, setSelectedMedia, conversation }) { export function ChatMediaSelector({ bodyshop, selectedMedia, setSelectedMedia, conversation }) {
const { t } = useTranslation(); const { t } = useTranslation();
const [open, setOpen] = useState(false); const [open, setOpen] = useState(false);
const {
treatments: { Imgproxy }
} = useSplitTreatments({
attributes: {},
names: ["Imgproxy"],
splitKey: bodyshop && bodyshop.imexshopid
});
const { loading, error, data } = useQuery(GET_DOCUMENTS_BY_JOB, { const { loading, error, data } = useQuery(GET_DOCUMENTS_BY_JOB, {
fetchPolicy: "network-only", fetchPolicy: "network-only",
@@ -42,6 +51,9 @@ export function ChatMediaSelector({ bodyshop, selectedMedia, setSelectedMedia, c
setSelectedMedia([]); setSelectedMedia([]);
}, [setSelectedMedia, conversation]); }, [setSelectedMedia, conversation]);
//Knowingly taking on the technical debt of poor implementation below.
//Cloudinary will be removed once the migration is completed.
const content = ( const content = (
<div> <div>
{loading && <LoadingSpinner />} {loading && <LoadingSpinner />}
@@ -49,13 +61,19 @@ export function ChatMediaSelector({ bodyshop, selectedMedia, setSelectedMedia, c
{selectedMedia.filter((s) => s.isSelected).length >= 10 ? ( {selectedMedia.filter((s) => s.isSelected).length >= 10 ? (
<div style={{ color: "red" }}>{t("messaging.labels.maxtenimages")}</div> <div style={{ color: "red" }}>{t("messaging.labels.maxtenimages")}</div>
) : null} ) : null}
{!bodyshop.uselocalmediaserver && data && ( {Imgproxy.treatment === "on" && !bodyshop.uselocalmediaserver && data && (
<JobsDocumentImgproxyGalleryExternal
jobId={conversation.job_conversations[0].jobid}
externalMediaState={[selectedMedia, setSelectedMedia]}
/>
)}
{Imgproxy.treatment !== "on" && !bodyshop.uselocalmediaserver && data && (
<JobDocumentsGalleryExternal <JobDocumentsGalleryExternal
data={data ? data.documents : []} data={data ? data.documents : []}
externalMediaState={[selectedMedia, setSelectedMedia]} externalMediaState={[selectedMedia, setSelectedMedia]}
/> />
)} )}
{bodyshop.uselocalmediaserver && open && ( {Imgproxy.treatment !== "on" && bodyshop.uselocalmediaserver && open && (
<JobDocumentsLocalGalleryExternal <JobDocumentsLocalGalleryExternal
externalMediaState={[selectedMedia, setSelectedMedia]} externalMediaState={[selectedMedia, setSelectedMedia]}
jobId={conversation.job_conversations[0] && conversation.job_conversations[0].jobid} jobId={conversation.job_conversations[0] && conversation.job_conversations[0].jobid}

View File

@@ -2,31 +2,47 @@ import { useQuery } from "@apollo/client";
import React from "react"; import React from "react";
import { GET_DOCUMENTS_BY_JOB } from "../../graphql/documents.queries"; import { GET_DOCUMENTS_BY_JOB } from "../../graphql/documents.queries";
import AlertComponent from "../alert/alert.component"; import AlertComponent from "../alert/alert.component";
import JobDocumentsImgProxy from "../jobs-documents-imgproxy-gallery/jobs-documents-imgproxy-gallery.component";
import LoadingSpinner from "../loading-spinner/loading-spinner.component"; import LoadingSpinner from "../loading-spinner/loading-spinner.component";
import JobDocuments from "./jobs-documents-gallery.component"; import JobDocuments from "./jobs-documents-gallery.component";
import JobDocumentsImgProxy from "../jobs-documents-imgproxy-gallery/jobs-documents-imgproxy-gallery.component"; import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { selectBodyshop } from "../../redux/user/user.selectors";
import { useSplitTreatments } from "@splitsoftware/splitio-react";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop
});
const mapDispatchToProps = (dispatch) => ({});
export default connect(mapStateToProps, mapDispatchToProps)(JobsDocumentsContainer);
export default function JobsDocumentsContainer({ export function JobsDocumentsContainer({
jobId, jobId,
billId, billId,
documentsList, documentsList,
billsCallback, billsCallback,
refetchOverride, refetchOverride,
ignoreSizeLimit ignoreSizeLimit,
bodyshop
}) { }) {
//TODO Add a checker to see whether we should use the img proxy side, or this side. const {
const useImgProxy = true; treatments: { Imgproxy }
} = useSplitTreatments({
attributes: {},
names: ["Imgproxy"],
splitKey: bodyshop && bodyshop.imexshopid
});
const { loading, error, data, refetch } = useQuery(GET_DOCUMENTS_BY_JOB, { const { loading, error, data, refetch } = useQuery(GET_DOCUMENTS_BY_JOB, {
variables: { jobId: jobId }, variables: { jobId: jobId },
fetchPolicy: "network-only", fetchPolicy: "network-only",
nextFetchPolicy: "network-only", nextFetchPolicy: "network-only",
skip: useImgProxy || !!billId skip: Imgproxy.treatment === "on" || !!billId
}); });
if (loading) return <LoadingSpinner />; if (loading) return <LoadingSpinner />;
if (error) return <AlertComponent type="error" message={error.message} />; if (error) return <AlertComponent type="error" message={error.message} />;
if (useImgProxy) { if (Imgproxy.treatment === "on") {
return ( return (
<JobDocumentsImgProxy <JobDocumentsImgProxy
data={(data && data.documents) || documentsList || []} data={(data && data.documents) || documentsList || []}

View File

@@ -1,5 +1,5 @@
import { EditFilled, FileExcelFilled, SyncOutlined } from "@ant-design/icons"; import { EditFilled, FileExcelFilled, SyncOutlined } from "@ant-design/icons";
import { Button, Card, Col, Row, Space, Typography } from "antd"; import { Button, Card, Col, Row, Space } from "antd";
import axios from "axios"; import axios from "axios";
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import { Gallery } from "react-grid-gallery"; import { Gallery } from "react-grid-gallery";
@@ -16,6 +16,7 @@ import JobsDocumentsDownloadButton from "./jobs-document-imgproxy-gallery.downlo
import JobsDocumentsGalleryReassign from "./jobs-document-imgproxy-gallery.reassign.component"; import JobsDocumentsGalleryReassign from "./jobs-document-imgproxy-gallery.reassign.component";
import JobsDocumentsDeleteButton from "./jobs-documents-imgproxy-gallery.delete.component"; import JobsDocumentsDeleteButton from "./jobs-documents-imgproxy-gallery.delete.component";
import JobsDocumentsGallerySelectAllComponent from "./jobs-documents-imgproxy-gallery.selectall.component"; import JobsDocumentsGallerySelectAllComponent from "./jobs-documents-imgproxy-gallery.selectall.component";
import i18n from "i18next";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop bodyshop: selectBodyshop
@@ -37,72 +38,15 @@ function JobsDocumentsImgproxyComponent({
const { t } = useTranslation(); const { t } = useTranslation();
const [modalState, setModalState] = useState({ open: false, index: 0 }); const [modalState, setModalState] = useState({ open: false, index: 0 });
const fetchThumbnails = async () => { const fetchThumbnails = () => {
const result = await axios.post("/media/imgproxy/thumbnails", { jobid: jobId }); fetchImgproxyThumbnails({ setStateCallback: setgalleryImages, jobId });
let documents = result.data.reduce(
(acc, value) => {
if (value.type.startsWith("image")) {
acc.images.push({
src: value.thumbnailUrl,
fullsize: value.originalUrl,
height: 225,
width: 225,
isSelected: false,
key: value.key,
extension: value.extension,
id: value.id,
type: value.type,
size: value.size,
tags: [{ value: value.type, title: value.type }]
});
} else {
const fileName = value.key.split("/").pop();
acc.other.push({
source: value.originalUrlViaProxyPath,
src: value.thumbnailUrl,
fullsize: value.presignedGetUrl,
tags: [
{
value: fileName,
title: fileName
},
{ value: value.type, title: value.type },
...(value.bill
? [
{
value: value.bill.vendor.name,
title: t("vendors.fields.name")
},
{ value: value.bill.date, title: t("bills.fields.date") },
{
value: value.bill.invoice_number,
title: t("bills.fields.invoice_number")
}
]
: [])
],
height: 225,
width: 225,
isSelected: false,
extension: value.extension,
key: value.key,
id: value.id,
type: value.type,
size: value.size
});
}
return acc;
},
{ images: [], other: [] }
);
setgalleryImages(documents);
}; };
useEffect(() => { useEffect(() => {
if (data) { if (data) {
fetchThumbnails(); fetchThumbnails();
} }
}, [data, setgalleryImages, t]); }, [data, setgalleryImages]);
const hasMediaAccess = HasFeatureAccess({ bodyshop, featureName: "media" }); const hasMediaAccess = HasFeatureAccess({ bodyshop, featureName: "media" });
const hasMobileAccess = HasFeatureAccess({ bodyshop, featureName: "mobile" }); const hasMobileAccess = HasFeatureAccess({ bodyshop, featureName: "mobile" });
@@ -249,3 +193,69 @@ function JobsDocumentsImgproxyComponent({
} }
export default connect(mapStateToProps, mapDispatchToProps)(JobsDocumentsImgproxyComponent); export default connect(mapStateToProps, mapDispatchToProps)(JobsDocumentsImgproxyComponent);
export const fetchImgproxyThumbnails = async ({ setStateCallback, jobId, imagesOnly }) => {
const result = await axios.post("/media/imgproxy/thumbnails", { jobid: jobId });
let documents = result.data.reduce(
(acc, value) => {
if (value.type.startsWith("image")) {
acc.images.push({
src: value.thumbnailUrl,
fullsize: value.originalUrl,
height: 225,
width: 225,
isSelected: false,
key: value.key,
extension: value.extension,
id: value.id,
type: value.type,
size: value.size,
tags: [{ value: value.type, title: value.type }]
});
} else {
const fileName = value.key.split("/").pop();
acc.other.push({
source: value.originalUrlViaProxyPath,
src: value.thumbnailUrl,
fullsize: value.presignedGetUrl,
tags: [
{
value: fileName,
title: fileName
},
{ value: value.type, title: value.type },
...(value.bill
? [
{
value: value.bill.vendor.name,
title: i18n.t("vendors.fields.name")
},
{ value: value.bill.date, title: i18n.t("bills.fields.date") },
{
value: value.bill.invoice_number,
title: i18n.t("bills.fields.invoice_number")
}
]
: [])
],
height: 225,
width: 225,
isSelected: false,
extension: value.extension,
key: value.key,
id: value.id,
type: value.type,
size: value.size
});
}
return acc;
},
{ images: [], other: [] }
);
if (imagesOnly) {
setStateCallback(documents.images);
} else {
setStateCallback(documents);
}
};

View File

@@ -1,38 +1,20 @@
import { useEffect } from "react"; import { useEffect } from "react";
import { Gallery } from "react-grid-gallery"; import { Gallery } from "react-grid-gallery";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { GenerateSrcUrl, GenerateThumbUrl } from "./job-documents-imgproxy.utility"; import { fetchImgproxyThumbnails } from "./jobs-documents-imgproxy-gallery.component";
//import { GenerateSrcUrl, GenerateThumbUrl } from "./job-documents-imgproxy.utility";
function JobsDocumentImgproxyGalleryExternal({ function JobsDocumentImgproxyGalleryExternal({ jobId, externalMediaState }) {
data,
externalMediaState
}) {
const [galleryImages, setgalleryImages] = externalMediaState; const [galleryImages, setgalleryImages] = externalMediaState;
const { t } = useTranslation(); const { t } = useTranslation();
useEffect(() => { const fetchThumbnails = () => {
let documents = data.reduce((acc, value) => { fetchImgproxyThumbnails({ setStateCallback: setgalleryImages, jobId, imagesOnly: true });
if (value.type.startsWith("image")) { };
acc.push({
fullsize: GenerateSrcUrl(value),
src: GenerateThumbUrl(value),
thumbnailHeight: 225,
thumbnailWidth: 225,
isSelected: false,
key: value.key,
extension: value.extension,
id: value.id,
type: value.type,
tags: [{ value: value.type, title: value.type }],
size: value.size
});
}
return acc; useEffect(() => {
}, []); fetchThumbnails();
setgalleryImages(documents); }, [jobId]);
}, [data, setgalleryImages, t]);
return ( return (
<div className="clearfix"> <div className="clearfix">

View File

@@ -1,15 +1,15 @@
import { useQuery } from "@apollo/client"; import { useQuery } from "@apollo/client";
import React from "react"; import React from "react";
import AlertComponent from "../../components/alert/alert.component"; import AlertComponent from "../../components/alert/alert.component";
import JobsDocumentsComponent from "../../components/jobs-documents-gallery/jobs-documents-gallery.component";
import JobsDocumentsContainer from "../../components/jobs-documents-gallery/jobs-documents-gallery.container"; import JobsDocumentsContainer from "../../components/jobs-documents-gallery/jobs-documents-gallery.container";
import LoadingSpinner from "../../components/loading-spinner/loading-spinner.component"; import LoadingSpinner from "../../components/loading-spinner/loading-spinner.component";
import { QUERY_TEMPORARY_DOCS } from "../../graphql/documents.queries"; import { QUERY_TEMPORARY_DOCS } from "../../graphql/documents.queries";
import { useSplitTreatments } from "@splitsoftware/splitio-react";
import { connect } from "react-redux"; import { connect } from "react-redux";
import { createStructuredSelector } from "reselect"; import { createStructuredSelector } from "reselect";
import { selectBodyshop } from "../../redux/user/user.selectors";
import JobsDocumentsLocalGallery from "../../components/jobs-documents-local-gallery/jobs-documents-local-gallery.container"; import JobsDocumentsLocalGallery from "../../components/jobs-documents-local-gallery/jobs-documents-local-gallery.container";
import { selectBodyshop } from "../../redux/user/user.selectors";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop bodyshop: selectBodyshop
@@ -20,10 +20,18 @@ const mapDispatchToProps = (dispatch) => ({
export default connect(mapStateToProps, mapDispatchToProps)(TemporaryDocsComponent); export default connect(mapStateToProps, mapDispatchToProps)(TemporaryDocsComponent);
export function TemporaryDocsComponent({ bodyshop }) { export function TemporaryDocsComponent({ bodyshop }) {
const {
treatments: { Imgproxy }
} = useSplitTreatments({
attributes: {},
names: ["Imgproxy"],
splitKey: bodyshop && bodyshop.imexshopid
});
const { loading, error, data, refetch } = useQuery(QUERY_TEMPORARY_DOCS, { const { loading, error, data, refetch } = useQuery(QUERY_TEMPORARY_DOCS, {
fetchPolicy: "network-only", fetchPolicy: "network-only",
nextFetchPolicy: "network-only", nextFetchPolicy: "network-only",
skip: bodyshop.uselocalmediaserver //TODO: Add skip if imgproxy is enabled. skip: Imgproxy.treatment === "on"
}); });
if (loading) return <LoadingSpinner />; if (loading) return <LoadingSpinner />;