diff --git a/App.js b/App.js index c94c6a9..047a54b 100644 --- a/App.js +++ b/App.js @@ -6,7 +6,7 @@ import { PersistGate } from "redux-persist/integration/react"; import * as Sentry from "sentry-expo"; import ScreenMainComponent from "./components/screen-main/screen-main.component"; import env from "./env"; -import { logImEXEvent } from "./firebase/firebase.utils"; +import { logImEXEvent } from "./firebase/firebase.analytics"; import { client } from "./graphql/client"; import { persistor, store } from "./redux/store"; import "./translations/i18n"; diff --git a/babel-translations.babel b/babel-translations.babel index b75d7f6..ab4065b 100644 --- a/babel-translations.babel +++ b/babel-translations.babel @@ -1320,6 +1320,48 @@ + + storageexceeded + false + + + + + + en-US + false + + + es-MX + false + + + fr-CA + false + + + + + storageexceeded_title + false + + + + + + en-US + false + + + es-MX + false + + + fr-CA + false + + + temporarystorage false diff --git a/components/job-list-item/job-list-item.component.jsx b/components/job-list-item/job-list-item.component.jsx index da29cb4..cb3b4e5 100644 --- a/components/job-list-item/job-list-item.component.jsx +++ b/components/job-list-item/job-list-item.component.jsx @@ -5,7 +5,7 @@ import { useTranslation } from "react-i18next"; import { Button, List, Title } from "react-native-paper"; import { connect } from "react-redux"; import { createStructuredSelector } from "reselect"; -import { logImEXEvent } from "../../firebase/firebase.utils"; +import { logImEXEvent } from "../../firebase/firebase.analytics"; import { setCameraJob, setCameraJobId } from "../../redux/app/app.actions"; const mapStateToProps = createStructuredSelector({}); diff --git a/components/screen-main/screen-main.component.jsx b/components/screen-main/screen-main.component.jsx index fb0d29c..179de25 100644 --- a/components/screen-main/screen-main.component.jsx +++ b/components/screen-main/screen-main.component.jsx @@ -8,7 +8,7 @@ import { Button } from "react-native-paper"; import { SafeAreaView } from "react-native-safe-area-context"; import { connect } from "react-redux"; import { createStructuredSelector } from "reselect"; -import { logImEXEvent } from "../../firebase/firebase.utils"; +import { logImEXEvent } from "../../firebase/firebase.analytics"; import { setCameraJob, setCameraJobId } from "../../redux/app/app.actions"; import { checkUserSession, diff --git a/components/screen-media-browser/screen-media-browser.component.jsx b/components/screen-media-browser/screen-media-browser.component.jsx index 97d4771..f45f2d5 100644 --- a/components/screen-media-browser/screen-media-browser.component.jsx +++ b/components/screen-media-browser/screen-media-browser.component.jsx @@ -1,13 +1,14 @@ +import { useApolloClient } from "@apollo/client"; import { Ionicons } from "@expo/vector-icons"; import { AssetsSelector } from "expo-images-picker"; import * as MediaLibrary from "expo-media-library"; import _ from "lodash"; import React, { useCallback, useState } from "react"; import { useTranslation } from "react-i18next"; -import { StyleSheet, Text, View } from "react-native"; +import { StyleSheet, Text, View, Alert } from "react-native"; import { connect } from "react-redux"; import { createStructuredSelector } from "reselect"; -import { logImEXEvent } from "../../firebase/firebase.utils"; +import { logImEXEvent } from "../../firebase/firebase.analytics"; import { selectCurrentCameraJobId, selectDeleteAfterUpload, @@ -20,8 +21,9 @@ import { handleUpload } from "../../util/document-upload.utility"; import CameraSelectJob from "../camera-select-job/camera-select-job.component"; import UploadDeleteSwitch from "../upload-delete-switch/upload-delete-switch.component"; import UploadProgress from "../upload-progress/upload-progress.component"; - +import { GET_DOC_SIZE_TOTALS } from "../../graphql/documents.queries"; //const limit = plimit(2); +import * as FileSystem from "expo-file-system"; const mapStateToProps = createStructuredSelector({ currentUser: selectCurrentUser, @@ -47,7 +49,7 @@ export function ImageBrowserScreen({ const forceRerender = useCallback(() => { setTick((tick) => tick + 1); }, []); - + const client = useApolloClient(); async function handleOnSuccess(uri, id) { console.log("Succesful upload!", uri); logImEXEvent("imexmobile_successful_upload"); @@ -56,6 +58,49 @@ export function ImageBrowserScreen({ const onDone = async (data) => { logImEXEvent("imexmobile_upload_documents", { count: data.length }); + + //Validate to make sure the totals for the file sizes do not exceed the total on the job. + + const queryData = await client.query({ + query: GET_DOC_SIZE_TOTALS, + + fetchPolicy: "network-only", + variables: { + jobId: selectedCameraJobId !== "temp" ? selectedCameraJobId : null, + }, + }); + + const totalOfUploads = await data.reduce(async (acc, val) => { + //Get the size of the file based on URI. + const info = await FileSystem.getInfoAsync(val.uri, { size: true }); + return (await acc) + info.size; + }, 0); + console.log("data :>> ", data); + console.log( + "Size of uploaded documents.", + queryData.data.documents_aggregate.aggregate.sum.size, + "Shop Limit", + bodyshop.jobsizelimit, + "Space remaining", + bodyshop.jobsizelimit - + queryData.data.documents_aggregate.aggregate.sum.size, + "Total of uploaded files", + totalOfUploads + ); + + if ( + bodyshop.jobsizelimit - + queryData.data.documents_aggregate.aggregate.sum.size <= + totalOfUploads + ) { + //No more room... abandon ship. + Alert.alert( + t("mediabrowser.labels.storageexceeded_title"), + t("mediabrowser.labels.storageexceeded") + ); + return; + } + const ret = await Promise.all( data.map(async (p) => { let filename; diff --git a/components/upload-delete-switch/upload-delete-switch.component.jsx b/components/upload-delete-switch/upload-delete-switch.component.jsx index 2bc9358..7195c0b 100644 --- a/components/upload-delete-switch/upload-delete-switch.component.jsx +++ b/components/upload-delete-switch/upload-delete-switch.component.jsx @@ -3,7 +3,7 @@ import { useTranslation } from "react-i18next"; import { StyleSheet, Switch, Text, View } from "react-native"; import { connect } from "react-redux"; import { createStructuredSelector } from "reselect"; -import { logImEXEvent } from "../../firebase/firebase.utils"; +import { logImEXEvent } from "../../firebase/firebase.analytics"; import { toggleDeleteAfterUpload } from "../../redux/app/app.actions"; import { selectDeleteAfterUpload } from "../../redux/app/app.selectors"; diff --git a/firebase/firebase.analytics.js b/firebase/firebase.analytics.js new file mode 100644 index 0000000..4e0056b --- /dev/null +++ b/firebase/firebase.analytics.js @@ -0,0 +1,24 @@ +import * as Analytics from "expo-firebase-analytics"; +import { store } from "../redux/store"; + +export const logImEXEvent = (eventName, additionalParams, stateProp = null) => { + const state = stateProp || store.getState(); + const eventParams = { + shop: + (state.user && state.user.bodyshop && state.user.bodyshop.shopname) || + null, + user: + (state.user && state.user.currentUser && state.user.currentUser.email) || + null, + ...additionalParams, + }; + console.log( + "%c[Analytics]", + "background-color: green ;font-weight:bold;", + eventName, + eventParams + ); + Analytics.logEvent(eventName, eventParams); +}; + +//export const ExpoAnalytics = Analytics; diff --git a/firebase/firebase.utils.js b/firebase/firebase.utils.js index b06b826..0afa7e0 100644 --- a/firebase/firebase.utils.js +++ b/firebase/firebase.utils.js @@ -55,24 +55,24 @@ export const updateCurrentUser = (userDetails) => { }); }; -export const logImEXEvent = (eventName, additionalParams, stateProp = null) => { - const state = stateProp || store.getState(); - const eventParams = { - shop: - (state.user && state.user.bodyshop && state.user.bodyshop.shopname) || - null, - user: - (state.user && state.user.currentUser && state.user.currentUser.email) || - null, - ...additionalParams, - }; - console.log( - "%c[Analytics]", - "background-color: green ;font-weight:bold;", - eventName, - eventParams - ); - Analytics.logEvent(eventName, eventParams); -}; +// export const logImEXEvent = (eventName, additionalParams, stateProp = null) => { +// const state = stateProp || store.getState(); +// const eventParams = { +// shop: +// (state.user && state.user.bodyshop && state.user.bodyshop.shopname) || +// null, +// user: +// (state.user && state.user.currentUser && state.user.currentUser.email) || +// null, +// ...additionalParams, +// }; +// console.log( +// "%c[Analytics]", +// "background-color: green ;font-weight:bold;", +// eventName, +// eventParams +// ); +// Analytics.logEvent(eventName, eventParams); +// }; //export const ExpoAnalytics = Analytics; diff --git a/graphql/bodyshop.queries.js b/graphql/bodyshop.queries.js index eb3b146..484586a 100644 --- a/graphql/bodyshop.queries.js +++ b/graphql/bodyshop.queries.js @@ -4,7 +4,7 @@ export const QUERY_BODYSHOP = gql` query QUERY_BODYSHOP { bodyshops(where: { associations: { active: { _eq: true } } }) { id - + jobsizelimit md_ro_statuses md_order_statuses shopname diff --git a/graphql/documents.queries.js b/graphql/documents.queries.js index 361eb11..2c20c7f 100644 --- a/graphql/documents.queries.js +++ b/graphql/documents.queries.js @@ -1,11 +1,27 @@ import gql from "graphql-tag"; +export const GET_DOC_SIZE_TOTALS = gql` + query GET_DOC_SIZE_TOTALS($jobId: uuid!) { + documents_aggregate(where: { jobid: { _eq: $jobId } }) { + aggregate { + sum { + size + } + } + } + } +`; + export const GET_DOCUMENTS_BY_JOB = gql` query GET_DOCUMENTS_BY_JOB($jobId: uuid!) { - documents( - where: { jobid: { _eq: $jobId } } - order_by: { updated_at: desc } - ) { + documents_aggregate(where: { jobid: { _eq: $jobId } }) { + aggregate { + sum { + size + } + } + } + documents(order_by: { updated_at: desc }) { id name key diff --git a/redux/user/user.sagas.js b/redux/user/user.sagas.js index 9ea8ce6..cf31186 100644 --- a/redux/user/user.sagas.js +++ b/redux/user/user.sagas.js @@ -3,9 +3,9 @@ import { all, call, put, takeLatest } from "redux-saga/effects"; import { auth, getCurrentUser, - logImEXEvent, updateCurrentUser, } from "../../firebase/firebase.utils"; +import { logImEXEvent } from "../../firebase/firebase.analytics"; import { QUERY_BODYSHOP } from "../../graphql/bodyshop.queries"; import { client } from "../../graphql/client"; import { diff --git a/translations/en-US/common.json b/translations/en-US/common.json index 747bb02..f7f8544 100644 --- a/translations/en-US/common.json +++ b/translations/en-US/common.json @@ -90,6 +90,8 @@ "nomedia": "Look's like there's no media on your device. Take some photos or videos and they will appear here.", "selectjob": "--- Select a job ---", "selectjobassetselector": "Please select a job to upload media. ", + "storageexceeded": "Unable to uploaded selected files because there is not sufficient space available on this job.", + "storageexceeded_title": "Unable to upload file(s)", "temporarystorage": "* Temporary Storage *" }, "titles": { diff --git a/translations/es-MX/common.json b/translations/es-MX/common.json index e3cd237..5177c60 100644 --- a/translations/es-MX/common.json +++ b/translations/es-MX/common.json @@ -90,6 +90,8 @@ "nomedia": "", "selectjob": "", "selectjobassetselector": "", + "storageexceeded": "", + "storageexceeded_title": "", "temporarystorage": "" }, "titles": { diff --git a/translations/fr-CA/common.json b/translations/fr-CA/common.json index 3dd73ae..b47d44a 100644 --- a/translations/fr-CA/common.json +++ b/translations/fr-CA/common.json @@ -90,6 +90,8 @@ "nomedia": "", "selectjob": "", "selectjobassetselector": "", + "storageexceeded": "", + "storageexceeded_title": "", "temporarystorage": "" }, "titles": { diff --git a/util/document-upload.utility.js b/util/document-upload.utility.js index ccbb33c..c459b46 100644 --- a/util/document-upload.utility.js +++ b/util/document-upload.utility.js @@ -153,6 +153,7 @@ export const uploadToCloudinary = async ( type: fileType, extension: extension, bodyshopid: bodyshop.id, + size: file.size, }, ], },