From a54a85b96cf61572db0bee2d1b07017670bfd065 Mon Sep 17 00:00:00 2001 From: Patrick Fic Date: Fri, 17 Jul 2020 08:27:28 -0700 Subject: [PATCH] Added first round of analytics and event tracking BOD-190 --- .../accounting-payables-table.component.jsx | 12 ++--- .../accounting-payments-table.component.jsx | 6 ++- ...accounting-receivables-table.component.jsx | 6 ++- .../src/components/alert/alert.component.jsx | 3 +- .../audit-trail-list.container.jsx | 7 ++- ...chat-conversation-title-tags.component.jsx | 6 ++- .../chat-conversation.container.jsx | 6 +-- .../chat-send-message.component.jsx | 9 ++-- .../chat-tag-ro/chat-tag-ro.container.jsx | 3 ++ ...ntract-license-decode-button.component.jsx | 9 +++- .../dashboard-grid.component.jsx | 8 ++- .../documents-upload.utility.js | 4 ++ .../email-overlay/email-overlay.container.jsx | 11 ++-- .../error-boundary.component.jsx | 16 +++--- .../fcm-notification.component.jsx | 4 +- .../global-search/global-search.component.jsx | 14 +++-- .../components/header/header.container.jsx | 8 ++- .../invoice-export-all-button.component.jsx | 7 ++- .../invoice-export-button.component.jsx | 7 ++- .../invoice-form.totals.utility.js | 3 ++ .../job-employee-assignments.container.jsx | 5 +- .../job-intake-form.component.jsx | 2 + .../job-intake-template-list.component.jsx | 6 +++ .../job-invoices-total.component.jsx | 3 ++ .../job-scoreboard-add-button.component.jsx | 3 ++ .../job-totals-table/job-totals.utility.js | 3 ++ .../jobs-available-new.container.jsx | 8 +-- .../jobs-available-supplement.container.jsx | 8 +-- ...jobs-close-allocation-button.component.jsx | 26 ++++++--- .../jobs-close-auto-allocate.component.jsx | 4 ++ .../jobs-close-export-button.component.jsx | 7 ++- .../jobs-close-save-button.component.jsx | 13 +++-- ...il-header-actions.addtoproduction.util.jsx | 3 ++ .../jobs-detail-header-actions.component.jsx | 51 +++++++++--------- ...bs-detail-header-actions.csi.component.jsx | 3 +- ...bs-detail-header-actions.duplicate.util.js | 3 ++ ...bs-document-gallery.download.component.jsx | 7 ++- ...obs-documents-gallery.delete.component.jsx | 3 ++ .../jobs-notes/jobs.notes.component.jsx | 54 +++++++++---------- .../note-upsert-modal.container.jsx | 36 +++++++------ .../owner-detail-update-jobs.component.jsx | 19 ++++--- ...-order-line-backorder-button.component.jsx | 2 + .../parts-order-modal.container.jsx | 7 ++- .../payment-export-button.component.jsx | 7 ++- .../payment-modal/payment-modal.container.jsx | 12 +++-- .../print-center-item.component.jsx | 2 +- .../production-board-kanban.component.jsx | 3 ++ ...roduction-list-columns.alert.component.jsx | 3 ++ ...on-list-columns.bodypriority.component.jsx | 3 ++ ...n-list-columns.paintpriority.component.jsx | 3 ++ ...-list-columns.productionnote.component.jsx | 3 ++ ...oduction-list-columns.status.component.jsx | 3 ++ ...tion-list-save-config-button.component.jsx | 3 ++ .../production-remove-button.component.jsx | 3 ++ .../profile-my/profile-my.component.jsx | 27 +++++----- .../profile-shops/profile-shops.container.jsx | 15 +++--- .../schedule-block-day.component.jsx | 8 +-- .../schedule-event.container.jsx | 4 +- .../schedule-job-modal.container.jsx | 3 ++ .../scorebard-remove-button.component.jsx | 3 ++ .../shop-employees.container.jsx | 7 +++ .../shop-info/shop-info.container.jsx | 3 ++ .../shop-template-add.component.jsx | 3 ++ .../shop-template-delete.component.jsx | 3 ++ ...-template-editor-save-button.component.jsx | 3 ++ .../tech-job-clock-out-button.component.jsx | 4 ++ .../tech-job-clock-out-delete.component.jsx | 3 ++ .../vehicle-detail-update-jobs.component.jsx | 19 ++++--- client/src/firebase/firebase.utils.js | 2 +- client/src/redux/messaging/messaging.sagas.js | 22 +++++++- client/src/redux/modals/modals.reducer.js | 4 ++ client/src/redux/tech/tech.sagas.js | 13 ++--- client/src/redux/user/user.sagas.js | 25 ++++++--- 73 files changed, 433 insertions(+), 208 deletions(-) diff --git a/client/src/components/accounting-payables-table/accounting-payables-table.component.jsx b/client/src/components/accounting-payables-table/accounting-payables-table.component.jsx index ffe3badfa..3691379a4 100644 --- a/client/src/components/accounting-payables-table/accounting-payables-table.component.jsx +++ b/client/src/components/accounting-payables-table/accounting-payables-table.component.jsx @@ -8,6 +8,7 @@ import InvoiceExportButton from "../invoice-export-button/invoice-export-button. import InvoiceExportAllButton from "../invoice-export-all-button/invoice-export-all-button.component"; import { DateFormatter } from "../../utils/DateFormatter"; import queryString from "query-string"; +import { logImEXEvent } from "../../firebase/firebase.utils"; export default function AccountingPayablesTableComponent({ loading, @@ -38,8 +39,7 @@ export default function AccountingPayablesTableComponent({ to={{ pathname: `/manage/shop/vendors`, search: queryString.stringify({ selectedvendor: record.vendor.id }), - }} - > + }}> {record.vendor.name} ), @@ -60,8 +60,7 @@ export default function AccountingPayablesTableComponent({ invoiceid: record.id, vendorid: record.vendor.id, }), - }} - > + }}> {record.invoice_number} ), @@ -131,6 +130,7 @@ export default function AccountingPayablesTableComponent({ const handleSearch = (e) => { setState({ ...state, search: e.target.value }); + logImEXEvent("accounting_payables_table_search"); }; const dataSource = state.search @@ -168,10 +168,10 @@ export default function AccountingPayablesTableComponent({ ); }} dataSource={dataSource} - size="small" + size='small' pagination={{ position: "top", pageSize: 50 }} columns={columns} - rowKey="id" + rowKey='id' onChange={handleTableChange} rowSelection={{ onSelectAll: (selected, selectedRows) => diff --git a/client/src/components/accounting-payments-table/accounting-payments-table.component.jsx b/client/src/components/accounting-payments-table/accounting-payments-table.component.jsx index 8ede3eb92..006175863 100644 --- a/client/src/components/accounting-payments-table/accounting-payments-table.component.jsx +++ b/client/src/components/accounting-payments-table/accounting-payments-table.component.jsx @@ -7,6 +7,7 @@ import { DateTimeFormatter } from "../../utils/DateFormatter"; import { alphaSort } from "../../utils/sorters"; import PaymentExportButton from "../payment-export-button/payment-export-button.component"; import { PaymentsExportAllButton } from "../payments-export-all-button/payments-export-all-button.component"; +import { logImEXEvent } from "../../firebase/firebase.utils"; export default function AccountingPayablesTableComponent({ loading, @@ -129,6 +130,7 @@ export default function AccountingPayablesTableComponent({ const handleSearch = (e) => { setState({ ...state, search: e.target.value }); + logImEXEvent("account_payments_table_search"); }; const dataSource = state.search @@ -166,10 +168,10 @@ export default function AccountingPayablesTableComponent({ ); }} dataSource={dataSource} - size="small" + size='small' pagination={{ position: "top", pageSize: 50 }} columns={columns} - rowKey="id" + rowKey='id' onChange={handleTableChange} rowSelection={{ onSelectAll: (selected, selectedRows) => diff --git a/client/src/components/accounting-receivables-table/accounting-receivables-table.component.jsx b/client/src/components/accounting-receivables-table/accounting-receivables-table.component.jsx index 67f1e1bd2..5e62f7b57 100644 --- a/client/src/components/accounting-receivables-table/accounting-receivables-table.component.jsx +++ b/client/src/components/accounting-receivables-table/accounting-receivables-table.component.jsx @@ -5,6 +5,7 @@ import { Link } from "react-router-dom"; import CurrencyFormatter from "../../utils/CurrencyFormatter"; import { alphaSort } from "../../utils/sorters"; import JobExportButton from "../jobs-close-export-button/jobs-close-export-button.component"; +import { logImEXEvent } from "../../firebase/firebase.utils"; export default function AccountingReceivablesTableComponent({ loading, jobs }) { const { t } = useTranslation(); @@ -138,6 +139,7 @@ export default function AccountingReceivablesTableComponent({ loading, jobs }) { const handleSearch = (e) => { setState({ ...state, search: e.target.value }); + logImEXEvent("accounting_receivables_search"); }; const dataSource = state.search @@ -187,10 +189,10 @@ export default function AccountingReceivablesTableComponent({ loading, jobs }) { ); }} dataSource={dataSource} - size="small" + size='small' pagination={{ position: "top" }} columns={columns} - rowKey="id" + rowKey='id' onChange={handleTableChange} /> diff --git a/client/src/components/alert/alert.component.jsx b/client/src/components/alert/alert.component.jsx index 5ba4768a9..c84b887fb 100644 --- a/client/src/components/alert/alert.component.jsx +++ b/client/src/components/alert/alert.component.jsx @@ -1,7 +1,8 @@ import { Alert } from "antd"; - +import { logImEXEvent } from "../../firebase/firebase.utils"; import React from "react"; export default function AlertComponent(props) { + if (props.type === "error") logImEXEvent("alert_render", { ...props }); return ; } diff --git a/client/src/components/audit-trail-list/audit-trail-list.container.jsx b/client/src/components/audit-trail-list/audit-trail-list.container.jsx index e80559f2a..4aae3d101 100644 --- a/client/src/components/audit-trail-list/audit-trail-list.container.jsx +++ b/client/src/components/audit-trail-list/audit-trail-list.container.jsx @@ -3,16 +3,19 @@ import AuditTrailListComponent from "./audit-trail-list.component"; import { useQuery } from "@apollo/react-hooks"; import { QUERY_AUDIT_TRAIL } from "../../graphql/audit_trail.queries"; import AlertComponent from "../alert/alert.component"; +import { logImEXEvent } from "../../firebase/firebase.utils"; export default function AuditTrailListContainer({ recordId }) { const { loading, error, data } = useQuery(QUERY_AUDIT_TRAIL, { variables: { id: recordId }, - fetchPolicy: "network-only" + fetchPolicy: "network-only", }); + + logImEXEvent("audittrail_view", { recordId }); return (
{error ? ( - + ) : ( { - if ( - unreadCount > 0 && - !!selectedConversation && - !markingAsReadInProgress - ) { + if (unreadCount > 0 && !!selectedConversation && !markingAsReadInProgress) { setMarkingAsReadInProgress(true); await markConversationRead(); setMarkingAsReadInProgress(false); diff --git a/client/src/components/chat-send-message/chat-send-message.component.jsx b/client/src/components/chat-send-message/chat-send-message.component.jsx index 67f7129cb..b74593051 100644 --- a/client/src/components/chat-send-message/chat-send-message.component.jsx +++ b/client/src/components/chat-send-message/chat-send-message.component.jsx @@ -1,17 +1,19 @@ -import { Input, Spin } from "antd"; import { LoadingOutlined } from "@ant-design/icons"; -import React, { useState, useEffect, useRef } from "react"; +import { Input, Spin } from "antd"; +import React, { useEffect, useRef, useState } from "react"; import { useTranslation } from "react-i18next"; import { connect } from "react-redux"; import { createStructuredSelector } from "reselect"; +import { logImEXEvent } from "../../firebase/firebase.utils"; import { sendMessage } from "../../redux/messaging/messaging.actions"; -import { selectBodyshop } from "../../redux/user/user.selectors"; import { selectIsSending } from "../../redux/messaging/messaging.selectors"; +import { selectBodyshop } from "../../redux/user/user.selectors"; const mapStateToProps = createStructuredSelector({ bodyshop: selectBodyshop, isSending: selectIsSending, }); + const mapDispatchToProps = (dispatch) => ({ sendMessage: (message) => dispatch(sendMessage(message)), }); @@ -34,6 +36,7 @@ function ChatSendMessageComponent({ const { t } = useTranslation(); const handleEnter = () => { + logImEXEvent("messaging_send_message"); sendMessage({ to: conversation.phone_num, body: message, diff --git a/client/src/components/chat-tag-ro/chat-tag-ro.container.jsx b/client/src/components/chat-tag-ro/chat-tag-ro.container.jsx index 25f8f83a6..2cf1aa6d1 100644 --- a/client/src/components/chat-tag-ro/chat-tag-ro.container.jsx +++ b/client/src/components/chat-tag-ro/chat-tag-ro.container.jsx @@ -6,6 +6,7 @@ import { INSERT_CONVERSATION_TAG } from "../../graphql/job-conversations.queries import { Tag } from "antd"; import { useTranslation } from "react-i18next"; import { PlusOutlined } from "@ant-design/icons"; +import { logImEXEvent } from "../../firebase/firebase.utils"; export default function ChatTagRoContainer({ conversation }) { const { t } = useTranslation(); @@ -21,6 +22,7 @@ export default function ChatTagRoContainer({ conversation }) { ); const executeSearch = () => { + logImEXEvent("messaging_search_job_tag", { searchTerm: searchText }); if (called) refetch(); else { loadRo(); @@ -32,6 +34,7 @@ export default function ChatTagRoContainer({ conversation }) { }); const handleInsertTag = (value, option) => { + logImEXEvent("messaging_add_job_tag"); insertTag({ variables: { jobId: option.key } }); setVisible(false); }; diff --git a/client/src/components/contract-license-decode-button/contract-license-decode-button.component.jsx b/client/src/components/contract-license-decode-button/contract-license-decode-button.component.jsx index 3a0fddea5..96b8b05f9 100644 --- a/client/src/components/contract-license-decode-button/contract-license-decode-button.component.jsx +++ b/client/src/components/contract-license-decode-button/contract-license-decode-button.component.jsx @@ -5,6 +5,7 @@ import { useTranslation } from "react-i18next"; import aamva from "../../utils/aamva"; import DataLabel from "../data-label/data-label.component"; import LoadingSkeleton from "../loading-skeleton/loading-skeleton.component"; +import { logImEXEvent } from "../../firebase/firebase.utils"; export default function ContractLicenseDecodeButton({ form }) { const { t } = useTranslation(); @@ -12,14 +13,19 @@ export default function ContractLicenseDecodeButton({ form }) { const [loading, setLoading] = useState(false); const [decodedBarcode, setDecodedBarcode] = useState(null); console.log("form", form); + const handleDecode = (e) => { + logImEXEvent("contract_license_decode"); setLoading(true); const aamvaParse = aamva.parse(e.currentTarget.value); console.log("AAMVA", aamvaParse); setDecodedBarcode(aamvaParse); setLoading(false); }; + const handleInsertForm = () => { + logImEXEvent("contract_license_decode_fill_form"); + const values = { driver_dlnumber: decodedBarcode.dl, driver_dlexpiry: moment( @@ -55,8 +61,7 @@ export default function ContractLicenseDecodeButton({ form }) { okText={t("contracts.actions.senddltoform")} onOk={handleInsertForm} okButtonProps={{ disabled: !!!decodedBarcode }} - onCancel={handleCancel} - > + onCancel={handleCancel}>
({ export function DashboardGridComponent({ currentUser, bodyshop }) { const { loading, error, data } = useQuery(QUERY_DASHBOARD_DETAILS); + console.log("DashboardGridComponent -> data", data) const { t } = useTranslation(); const [state, setState] = useState({ layout: bodyshop.associations[0].user.dashboardlayout || [ @@ -47,6 +49,7 @@ export function DashboardGridComponent({ currentUser, bodyshop }) { const [updateLayout] = useMutation(UPDATE_DASHBOARD_LAYOUT); const handleLayoutChange = async (newLayout) => { + logImEXEvent("dashboard_change_layout"); setState({ ...state, layout: newLayout }); const result = await updateLayout({ variables: { email: currentUser.email, layout: newLayout }, @@ -62,6 +65,8 @@ export function DashboardGridComponent({ currentUser, bodyshop }) { }; const handleRemoveComponent = (key) => { + logImEXEvent("dashboard_remove_component", { name: key }); + const idxToRemove = state.layout.findIndex((i) => i.i === key); const newLayout = state.layout; newLayout.splice(idxToRemove, 1); @@ -70,6 +75,8 @@ export function DashboardGridComponent({ currentUser, bodyshop }) { }; const handleAddComponent = (e) => { + logImEXEvent("dashboard_add_component", { name: e }); + handleLayoutChange([ ...state.layout, { @@ -99,7 +106,6 @@ export function DashboardGridComponent({ currentUser, bodyshop }) { ))} ); - console.log("Dashboard Data:", data); if (error) return ; diff --git a/client/src/components/documents-upload/documents-upload.utility.js b/client/src/components/documents-upload/documents-upload.utility.js index 4f5fda5e2..7700a0232 100644 --- a/client/src/components/documents-upload/documents-upload.utility.js +++ b/client/src/components/documents-upload/documents-upload.utility.js @@ -4,10 +4,14 @@ import Resizer from "react-image-file-resizer"; import { client } from "../../App/App.container"; import { INSERT_NEW_DOCUMENT } from "../../graphql/documents.queries"; import i18n from "i18next"; +import { logImEXEvent } from "../../firebase/firebase.utils"; //Context: currentUserEmail, bodyshop, jobid, invoiceid export const handleUpload = (ev, context) => { console.log("ev", ev); + + logImEXEvent("document_upload", { filetype: ev.file.type }); + const { onError, onSuccess, onProgress } = ev; const { bodyshop, jobId } = context; //If PDF, upload directly. diff --git a/client/src/components/email-overlay/email-overlay.container.jsx b/client/src/components/email-overlay/email-overlay.container.jsx index acd6adc0d..edbbddda2 100644 --- a/client/src/components/email-overlay/email-overlay.container.jsx +++ b/client/src/components/email-overlay/email-overlay.container.jsx @@ -14,6 +14,7 @@ import { EmailSettings } from "../../utils/TemplateConstants"; import RenderTemplate from "../../utils/RenderTemplate"; import LoadingSpinner from "../loading-spinner/loading-spinner.component"; import EmailOverlayComponent from "./email-overlay.component"; +import { logImEXEvent } from "../../firebase/firebase.utils"; const mapStateToProps = createStructuredSelector({ modalVisible: selectEmailVisible, @@ -47,6 +48,8 @@ export function EmailOverlayContainer({ }); const handleOk = async () => { + logImEXEvent("email_send_from_modal"); + setSending(true); try { await axios.post("/sendemail", messageOptions); @@ -70,6 +73,8 @@ export function EmailOverlayContainer({ }; const render = async () => { + logImEXEvent("email_render_template", { template: emailConfig.template }); + setLoading(true); console.log("emailConfig", emailConfig); let html = await RenderTemplate(emailConfig.template, bodyshop); @@ -94,8 +99,7 @@ export function EmailOverlayContainer({ onCancel={() => { toggleEmailOverlayVisible(); }} - okButtonProps={{ loading: sending }} - > + okButtonProps={{ loading: sending }}> { navigator.clipboard.writeText(messageOptions.html); - }} - > + }}> Copy HTML diff --git a/client/src/components/error-boundary/error-boundary.component.jsx b/client/src/components/error-boundary/error-boundary.component.jsx index 51d3f3d6c..96211cc00 100644 --- a/client/src/components/error-boundary/error-boundary.component.jsx +++ b/client/src/components/error-boundary/error-boundary.component.jsx @@ -1,6 +1,7 @@ +import { Button, Col, Collapse, Result, Row, Space } from "antd"; import React from "react"; import { withTranslation } from "react-i18next"; -import { Result, Button, Collapse, Row, Col, Space } from "antd"; +import { logImEXEvent } from "../../firebase/firebase.utils"; class ErrorBoundary extends React.Component { constructor() { @@ -24,28 +25,29 @@ class ErrorBoundary extends React.Component { render() { const { t } = this.props; + const { error, info } = this.state; if (this.state.hasErrored === true) { + logImEXEvent("error_boundary_rendered", { error, info }); + return (
diff --git a/client/src/components/fcm-notification/fcm-notification.component.jsx b/client/src/components/fcm-notification/fcm-notification.component.jsx index 24ab1c0c2..998c068de 100644 --- a/client/src/components/fcm-notification/fcm-notification.component.jsx +++ b/client/src/components/fcm-notification/fcm-notification.component.jsx @@ -2,9 +2,10 @@ import React, { Component } from "react"; import { withApollo } from "react-apollo"; import { connect } from "react-redux"; import { createStructuredSelector } from "reselect"; -import { messaging } from "../../firebase/firebase.utils"; +import { logImEXEvent, messaging } from "../../firebase/firebase.utils"; import { UPDATE_FCM_TOKEN } from "../../graphql/user.queries"; import { selectCurrentUser } from "../../redux/user/user.selectors"; + const mapStateToProps = createStructuredSelector({ currentUser: selectCurrentUser, }); @@ -28,6 +29,7 @@ class FcmNotificationComponent extends Component { }) .catch(function (err) { console.log("Unable to get permission to notify.", err); + logImEXEvent("fcm_permission_denied", { message: err }); }); navigator.serviceWorker.addEventListener("message", (message) => { const { payload } = message.data.firebaseMessaging; diff --git a/client/src/components/global-search/global-search.component.jsx b/client/src/components/global-search/global-search.component.jsx index 95701af79..d0a800728 100644 --- a/client/src/components/global-search/global-search.component.jsx +++ b/client/src/components/global-search/global-search.component.jsx @@ -1,10 +1,12 @@ -import React, { useState } from "react"; import { useLazyQuery } from "@apollo/react-hooks"; -import { GLOBAL_SEARCH_QUERY } from "../../graphql/search.queries"; -import { Input, AutoComplete } from "antd"; +import { AutoComplete, Input } from "antd"; +import React from "react"; import { useTranslation } from "react-i18next"; -import CurrencyFormatter from "../../utils/CurrencyFormatter"; import { Link } from "react-router-dom"; +import { logImEXEvent } from "../../firebase/firebase.utils"; +import { GLOBAL_SEARCH_QUERY } from "../../graphql/search.queries"; +import CurrencyFormatter from "../../utils/CurrencyFormatter"; +import AlertComponent from "../alert/alert.component"; export default function GlobalSearch() { const { t } = useTranslation(); @@ -13,6 +15,8 @@ export default function GlobalSearch() { ); const handleSearch = (searchTerm) => { + logImEXEvent("global_search", { term: searchTerm }); + if (searchTerm.length > 0) callSearch({ variables: { search: searchTerm } }); }; @@ -129,6 +133,8 @@ export default function GlobalSearch() { ] : []; + if (error) return ; + return (
({ setUserLanguage: (language) => dispatch(setUserLanguage(language)), @@ -12,8 +13,13 @@ export function HeaderContainer({ setUserLanguage }) { const handleMenuClick = (e) => { if (e.item.props.actiontype === "lang-select") { i18next.changeLanguage(e.key, (err, t) => { - if (err) + if (err) { + logImEXEvent("language_change_error", { error: err }); + return console.log("Error encountered when changing languages.", err); + } + logImEXEvent("language_change", { language: e.key }); + setUserLanguage(e.key); }); } diff --git a/client/src/components/invoice-export-all-button/invoice-export-all-button.component.jsx b/client/src/components/invoice-export-all-button/invoice-export-all-button.component.jsx index 70c6d367a..afcfe25c2 100644 --- a/client/src/components/invoice-export-all-button/invoice-export-all-button.component.jsx +++ b/client/src/components/invoice-export-all-button/invoice-export-all-button.component.jsx @@ -8,6 +8,8 @@ import { createStructuredSelector } from "reselect"; import { auth } from "../../firebase/firebase.utils"; import { UPDATE_INVOICES } from "../../graphql/invoices.queries"; import { selectBodyshop } from "../../redux/user/user.selectors"; +import { logImEXEvent } from "../../firebase/firebase.utils"; + const mapStateToProps = createStructuredSelector({ bodyshop: selectBodyshop, }); @@ -24,6 +26,8 @@ export function InvoiceExportAllButton({ const [loading, setLoading] = useState(false); const handleQbxml = async () => { + logImEXEvent("accounting_payables_export_all"); + setLoading(true); if (!!loadingCallback) loadingCallback(true); @@ -117,8 +121,7 @@ export function InvoiceExportAllButton({ onClick={handleQbxml} loading={loading} disabled={disabled} - type="dashed" - > + type='dashed'> {t("jobs.actions.exportselected")} ); diff --git a/client/src/components/invoice-export-button/invoice-export-button.component.jsx b/client/src/components/invoice-export-button/invoice-export-button.component.jsx index 20aaa3ce6..974d147f0 100644 --- a/client/src/components/invoice-export-button/invoice-export-button.component.jsx +++ b/client/src/components/invoice-export-button/invoice-export-button.component.jsx @@ -8,6 +8,8 @@ import { createStructuredSelector } from "reselect"; import { auth } from "../../firebase/firebase.utils"; import { UPDATE_INVOICES } from "../../graphql/invoices.queries"; import { selectBodyshop } from "../../redux/user/user.selectors"; +import { logImEXEvent } from "../../firebase/firebase.utils"; + const mapStateToProps = createStructuredSelector({ bodyshop: selectBodyshop, }); @@ -23,6 +25,8 @@ export function InvoiceExportButton({ const [loading, setLoading] = useState(false); const handleQbxml = async () => { + logImEXEvent("accounting_export_payable"); + setLoading(true); if (!!loadingCallback) loadingCallback(true); @@ -114,8 +118,7 @@ export function InvoiceExportButton({ onClick={handleQbxml} loading={loading} disabled={disabled} - type="dashed" - > + type='dashed'> {t("jobs.actions.export")} ); diff --git a/client/src/components/invoice-form/invoice-form.totals.utility.js b/client/src/components/invoice-form/invoice-form.totals.utility.js index 07ef47908..02f64f2aa 100644 --- a/client/src/components/invoice-form/invoice-form.totals.utility.js +++ b/client/src/components/invoice-form/invoice-form.totals.utility.js @@ -1,6 +1,9 @@ import Dinero from "dinero.js"; +import { logImEXEvent } from "../../firebase/firebase.utils"; export const CalculateInvoiceTotal = (invoice) => { + logImEXEvent("invoice_calculate_total"); + const { total, diff --git a/client/src/components/job-employee-assignments/job-employee-assignments.container.jsx b/client/src/components/job-employee-assignments/job-employee-assignments.container.jsx index f99ed58a3..0edf3bec0 100644 --- a/client/src/components/job-employee-assignments/job-employee-assignments.container.jsx +++ b/client/src/components/job-employee-assignments/job-employee-assignments.container.jsx @@ -4,6 +4,7 @@ import React from "react"; import { useTranslation } from "react-i18next"; import { UPDATE_JOB } from "../../graphql/jobs.queries"; import JobEmployeeAssignmentsComponent from "./job-employee-assignments.component"; +import { logImEXEvent } from "../../firebase/firebase.utils"; export default function JobEmployeeAssignmentsContainer({ job }) { const { t } = useTranslation(); @@ -11,6 +12,7 @@ export default function JobEmployeeAssignmentsContainer({ job }) { const handleAdd = async (assignment) => { const { operation, employeeid } = assignment; + logImEXEvent("job_assign_employee", { operation }); let empAssignment = determineFieldName(operation); @@ -29,7 +31,8 @@ export default function JobEmployeeAssignmentsContainer({ job }) { } }; const handleRemove = async (operation) => { - console.log("handleRemove -> operation", operation); + logImEXEvent("job_unassign_employee", { operation }); + let empAssignment = determineFieldName(operation); const result = await updateJob({ variables: { jobId: job.id, job: { [empAssignment]: null } }, diff --git a/client/src/components/job-intake/components/job-intake-form/job-intake-form.component.jsx b/client/src/components/job-intake/components/job-intake-form/job-intake-form.component.jsx index 8c6c06518..c9ad59410 100644 --- a/client/src/components/job-intake/components/job-intake-form/job-intake-form.component.jsx +++ b/client/src/components/job-intake/components/job-intake-form/job-intake-form.component.jsx @@ -11,6 +11,7 @@ import { UPDATE_JOB } from "../../../../graphql/jobs.queries"; import { selectBodyshop } from "../../../../redux/user/user.selectors"; import DateTimePicker from "../../../form-date-time-picker/form-date-time-picker.component"; import ConfigFormComponents from "../../../config-form-components/config-form-components.component"; +import { logImEXEvent } from "../../../../firebase/firebase.utils"; const mapStateToProps = createStructuredSelector({ bodyshop: selectBodyshop, @@ -29,6 +30,7 @@ export function JobIntakeForm({ formItems, bodyshop }) { const handleFinish = async (values) => { console.log("values", values); + logImEXEvent("job_complete_intake"); const result = await intakeJob({ variables: { diff --git a/client/src/components/job-intake/components/job-intake-template-list/job-intake-template-list.component.jsx b/client/src/components/job-intake/components/job-intake-template-list/job-intake-template-list.component.jsx index 69799a332..722367e89 100644 --- a/client/src/components/job-intake/components/job-intake-template-list/job-intake-template-list.component.jsx +++ b/client/src/components/job-intake/components/job-intake-template-list/job-intake-template-list.component.jsx @@ -9,6 +9,7 @@ import { selectBodyshop } from "../../../../redux/user/user.selectors"; import { connect } from "react-redux"; import { createStructuredSelector } from "reselect"; import { useTranslation } from "react-i18next"; +import { logImEXEvent } from "../../../../firebase/firebase.utils"; const mapStateToProps = createStructuredSelector({ //currentUser: selectCurrentUser @@ -21,7 +22,10 @@ const mapDispatchToProps = (dispatch) => ({ export function JobIntakeTemplateList({ bodyshop, templates }) { const { jobId } = useParams(); const { t } = useTranslation(); + //TODO SHould this be using the generic one? const renderTemplate = async (templateKey) => { + logImEXEvent("job_intake_template_render"); + const html = await RenderTemplate( { name: templateKey, @@ -33,6 +37,8 @@ export function JobIntakeTemplateList({ bodyshop, templates }) { }; const renderAllTemplates = () => { + logImEXEvent("job_intake_render_all_templates"); + templates.forEach((template) => renderTemplate(template)); }; diff --git a/client/src/components/job-invoices-total/job-invoices-total.component.jsx b/client/src/components/job-invoices-total/job-invoices-total.component.jsx index d0bb014a3..d50a4411c 100644 --- a/client/src/components/job-invoices-total/job-invoices-total.component.jsx +++ b/client/src/components/job-invoices-total/job-invoices-total.component.jsx @@ -5,13 +5,16 @@ import { useTranslation } from "react-i18next"; import AlertComponent from "../alert/alert.component"; import LoadingSkeleton from "../loading-skeleton/loading-skeleton.component"; import "./job-invoices-total.styles.scss"; + export default function JobInvoiceTotals({ loading, invoices, jobTotals }) { const { t } = useTranslation(); + if (loading) return ; if (!!!jobTotals) return ( ); + const totals = JSON.parse(jobTotals); let invoiceTotals = Dinero({ amount: 0 }); diff --git a/client/src/components/job-scoreboard-add-button/job-scoreboard-add-button.component.jsx b/client/src/components/job-scoreboard-add-button/job-scoreboard-add-button.component.jsx index 4fef7f88a..40b79a485 100644 --- a/client/src/components/job-scoreboard-add-button/job-scoreboard-add-button.component.jsx +++ b/client/src/components/job-scoreboard-add-button/job-scoreboard-add-button.component.jsx @@ -12,6 +12,7 @@ import moment from "moment"; import React, { useState } from "react"; import { useTranslation } from "react-i18next"; import { INSERT_SCOREBOARD_ENTRY } from "../../graphql/scoreboard.queries"; +import { logImEXEvent } from "../../firebase/firebase.utils"; export default function ScoreboardAddButton({ job, ...otherBtnProps }) { const { t } = useTranslation(); @@ -21,6 +22,8 @@ export default function ScoreboardAddButton({ job, ...otherBtnProps }) { const [visibility, setVisibility] = useState(false); const handleFinish = async (values) => { + logImEXEvent("job_close_add_to_scoreboard"); + setLoading(true); const result = await insertScoreboardEntry({ variables: { sbInput: [{ jobid: job.id, ...values }] }, diff --git a/client/src/components/job-totals-table/job-totals.utility.js b/client/src/components/job-totals-table/job-totals.utility.js index e5376554c..ceb1e06c9 100644 --- a/client/src/components/job-totals-table/job-totals.utility.js +++ b/client/src/components/job-totals-table/job-totals.utility.js @@ -1,6 +1,9 @@ import Dinero from "dinero.js"; +import { logImEXEvent } from "../../firebase/firebase.utils"; export function CalculateJob(job, shoprates) { + logImEXEvent("job_calculate_total"); + let ret = { parts: CalculatePartsTotals(job.joblines), rates: CalculateRatesTotals(job, shoprates), diff --git a/client/src/components/jobs-available-new/jobs-available-new.container.jsx b/client/src/components/jobs-available-new/jobs-available-new.container.jsx index 8f1e12d15..397b61f48 100644 --- a/client/src/components/jobs-available-new/jobs-available-new.container.jsx +++ b/client/src/components/jobs-available-new/jobs-available-new.container.jsx @@ -15,6 +15,7 @@ import { selectBodyshop } from "../../redux/user/user.selectors"; import AlertComponent from "../alert/alert.component"; import LoadingSpinner from "../loading-spinner/loading-spinner.component"; import JobsAvailableComponent from "./jobs-available-new.component"; +import { logImEXEvent } from "../../firebase/firebase.utils"; const mapStateToProps = createStructuredSelector({ bodyshop: selectBodyshop, @@ -40,6 +41,8 @@ export function JobsAvailableContainer({ const [loadEstData, estData] = estDataLazyLoad; const onModalOk = () => { + logImEXEvent("job_import_new"); + setModalVisible(false); setInsertLoading(true); @@ -118,12 +121,11 @@ export function JobsAvailableContainer({ setSelectedOwner(null); }; - if (error) return ; + if (error) return ; return ( + message={t("jobs.labels.creating_new_job")}> { + logImEXEvent("job_import_supplement"); + setModalVisible(false); setInsertLoading(true); @@ -137,12 +140,11 @@ export function JobsAvailableSupplementContainer({ setSelectedJob(null); }; - if (error) return ; + if (error) return ; return ( + message={t("jobs.labels.creating_new_job")}> { + logImEXEvent("jobs_close_allocate_single"); + const existingIndex = allocation.allocations.findIndex( (e) => e.center === state.center ); @@ -70,8 +73,7 @@ export function JobsCloseLabmatAllocationButton({ - + - diff --git a/client/src/components/profile-shops/profile-shops.container.jsx b/client/src/components/profile-shops/profile-shops.container.jsx index 06fe5cea5..be32193c0 100644 --- a/client/src/components/profile-shops/profile-shops.container.jsx +++ b/client/src/components/profile-shops/profile-shops.container.jsx @@ -2,28 +2,31 @@ import React from "react"; import { useQuery, useMutation } from "@apollo/react-hooks"; import { QUERY_ALL_ASSOCIATIONS, - UPDATE_ASSOCIATION + UPDATE_ASSOCIATION, } from "../../graphql/associations.queries"; import AlertComponent from "../alert/alert.component"; import ProfileShopsComponent from "./profile-shops.component"; +import { logImEXEvent } from "../../firebase/firebase.utils"; export default function ProfileShopsContainer() { const { loading, error, data, refetch } = useQuery(QUERY_ALL_ASSOCIATIONS); const [updateAssocation] = useMutation(UPDATE_ASSOCIATION); - const updateActiveShop = activeShopId => { - data.associations.forEach(record => { + const updateActiveShop = (activeShopId) => { + logImEXEvent("profile_change_active_shop"); + + data.associations.forEach((record) => { updateAssocation({ variables: { assocId: record.id, - assocActive: record.id === activeShopId ? true : false - } + assocActive: record.id === activeShopId ? true : false, + }, }); }); refetch(); }; - if (error) return ; + if (error) return ; return ( { e.domEvent.stopPropagation(); - console.log("date", date); + if (e.key === "block") { - console.log("Block."); + const blockAppt = { title: t("appointments.labels.blocked"), @@ -35,7 +36,8 @@ export function ScheduleBlockDay({ date, children, refetch, bodyshop }) { start: moment(date).startOf("day"), end: moment(date).endOf("day"), }; - console.log("handleMenu -> blockAppt", blockAppt); + logImEXEvent("dashboard_change_layout"); + const result = await insertBlock({ variables: { app: [blockAppt] }, diff --git a/client/src/components/schedule-event/schedule-event.container.jsx b/client/src/components/schedule-event/schedule-event.container.jsx index 8debac831..3ee93e1bb 100644 --- a/client/src/components/schedule-event/schedule-event.container.jsx +++ b/client/src/components/schedule-event/schedule-event.container.jsx @@ -5,13 +5,15 @@ import { UPDATE_JOB } from "../../graphql/jobs.queries"; import ScheduleEventComponent from "./schedule-event.component"; import { notification } from "antd"; import { useTranslation } from "react-i18next"; - +import { logImEXEvent } from "../../firebase/firebase.utils"; export default function ScheduleEventContainer({ event, refetch }) { const { t } = useTranslation(); const [cancelAppointment] = useMutation(CANCEL_APPOINTMENT_BY_ID); const [updateJob] = useMutation(UPDATE_JOB); const handleCancel = async (id) => { + logImEXEvent("schedule_cancel_appt"); + const cancelAppt = await cancelAppointment({ variables: { appid: event.id }, }); diff --git a/client/src/components/schedule-job-modal/schedule-job-modal.container.jsx b/client/src/components/schedule-job-modal/schedule-job-modal.container.jsx index 18b9eb5fc..f91e1103e 100644 --- a/client/src/components/schedule-job-modal/schedule-job-modal.container.jsx +++ b/client/src/components/schedule-job-modal/schedule-job-modal.container.jsx @@ -17,6 +17,7 @@ import { selectBodyshop } from "../../redux/user/user.selectors"; import { selectSchedule } from "../../redux/modals/modals.selectors"; import { toggleModalVisible } from "../../redux/modals/modals.actions"; import { TemplateList } from "../../utils/TemplateConstants"; +import { logImEXEvent } from "../../firebase/firebase.utils"; const mapStateToProps = createStructuredSelector({ bodyshop: selectBodyshop, @@ -64,6 +65,8 @@ export function ScheduleJobModalContainer({ }); const handleOk = async () => { + logImEXEvent("schedule_new_appointment"); + setLoading(true); if (!!previousEvent) { const cancelAppt = await cancelAppointment({ diff --git a/client/src/components/scoreboard-remove-button/scorebard-remove-button.component.jsx b/client/src/components/scoreboard-remove-button/scorebard-remove-button.component.jsx index 2b6bb66fd..5e103bd16 100644 --- a/client/src/components/scoreboard-remove-button/scorebard-remove-button.component.jsx +++ b/client/src/components/scoreboard-remove-button/scorebard-remove-button.component.jsx @@ -4,12 +4,15 @@ import { Button, notification } from "antd"; import { DeleteFilled } from "@ant-design/icons"; import { useMutation } from "@apollo/react-hooks"; import { DELETE_SCOREBOARD_ENTRY } from "../../graphql/scoreboard.queries"; +import { logImEXEvent } from "../../firebase/firebase.utils"; export default function ScoreboardRemoveButton({ scoreboardId }) { const { t } = useTranslation(); const [deleteScoreboardEntry] = useMutation(DELETE_SCOREBOARD_ENTRY); const [loading, setLoading] = useState(false); const handleDelete = async (e) => { + logImEXEvent("scoreboard_remove_job"); + e.stopPropagation(); setLoading(true); const result = await deleteScoreboardEntry({ diff --git a/client/src/components/shop-employees/shop-employees.container.jsx b/client/src/components/shop-employees/shop-employees.container.jsx index 33803ecb2..6ef6032b9 100644 --- a/client/src/components/shop-employees/shop-employees.container.jsx +++ b/client/src/components/shop-employees/shop-employees.container.jsx @@ -13,6 +13,7 @@ import { import { selectBodyshop } from "../../redux/user/user.selectors"; import AlertComponent from "../alert/alert.component"; import ShopEmployeeComponent from "./shop-employees.component"; +import { logImEXEvent } from "../../firebase/firebase.utils"; const mapStateToProps = createStructuredSelector({ bodyshop: selectBodyshop, @@ -30,6 +31,8 @@ function ShopEmployeesContainer({ bodyshop }) { const [insertEmployees] = useMutation(INSERT_EMPLOYEES); const [deleteEmployee] = useMutation(DELETE_EMPLOYEE); const handleDelete = (id) => { + logImEXEvent("shop_employee_delete"); + deleteEmployee({ variables: { id: id } }) .then((r) => { notification["success"]({ @@ -52,6 +55,8 @@ function ShopEmployeesContainer({ bodyshop }) { console.log("values", values); if (employeeState[0].id) { //Update a record. + logImEXEvent("shop_employee_update"); + updateEmployee({ variables: { id: employeeState[0].id, employee: values }, }) @@ -72,6 +77,8 @@ function ShopEmployeesContainer({ bodyshop }) { }); } else { //New record, insert it. + logImEXEvent("shop_employee_insert"); + insertEmployees({ variables: { employees: [{ ...values, shopid: bodyshop.id }] }, }).then((r) => { diff --git a/client/src/components/shop-info/shop-info.container.jsx b/client/src/components/shop-info/shop-info.container.jsx index 0bb14174f..f81886cff 100644 --- a/client/src/components/shop-info/shop-info.container.jsx +++ b/client/src/components/shop-info/shop-info.container.jsx @@ -6,6 +6,8 @@ import { QUERY_BODYSHOP, UPDATE_SHOP } from "../../graphql/bodyshop.queries"; import LoadingSpinner from "../loading-spinner/loading-spinner.component"; import AlertComponent from "../alert/alert.component"; import { useTranslation } from "react-i18next"; +import { logImEXEvent } from "../../firebase/firebase.utils"; + export default function ShopInfoContainer() { const [form] = Form.useForm(); const { t } = useTranslation(); @@ -16,6 +18,7 @@ export default function ShopInfoContainer() { const handleFinish = values => { console.log("values", values); + logImEXEvent("shop_update"); updateBodyshop({ variables: { id: data.bodyshops[0].id, shop: values } diff --git a/client/src/components/shop-template-add/shop-template-add.component.jsx b/client/src/components/shop-template-add/shop-template-add.component.jsx index e19a8f7d3..4f4325667 100644 --- a/client/src/components/shop-template-add/shop-template-add.component.jsx +++ b/client/src/components/shop-template-add/shop-template-add.component.jsx @@ -10,6 +10,7 @@ import { createStructuredSelector } from "reselect"; import { INSERT_TEMPLATE } from "../../graphql/templates.queries"; import { selectBodyshop } from "../../redux/user/user.selectors"; import { TemplateList } from "../../utils/TemplateConstants"; +import { logImEXEvent } from "../../firebase/firebase.utils"; const mapStateToProps = createStructuredSelector({ //currentUser: selectCurrentUser @@ -34,6 +35,8 @@ export function ShopTemplateAddComponent({ ); const handleAdd = async (item) => { + logImEXEvent("shop_template_add"); + const result = await insertTemplate({ variables: { template: { diff --git a/client/src/components/shop-template-delete/shop-template-delete.component.jsx b/client/src/components/shop-template-delete/shop-template-delete.component.jsx index cc1fee3a2..7c5f7606d 100644 --- a/client/src/components/shop-template-delete/shop-template-delete.component.jsx +++ b/client/src/components/shop-template-delete/shop-template-delete.component.jsx @@ -5,6 +5,7 @@ import React from "react"; import { useTranslation } from "react-i18next"; import { useHistory, useLocation } from "react-router-dom"; import { DELETE_TEMPLATE } from "../../graphql/templates.queries"; +import { logImEXEvent } from "../../firebase/firebase.utils"; export default function ShopTemplateDeleteComponent({ templateId, refetch }) { const { t } = useTranslation(); @@ -13,6 +14,8 @@ export default function ShopTemplateDeleteComponent({ templateId, refetch }) { const [deleteTemplate] = useMutation(DELETE_TEMPLATE); const handleDelete = async () => { + logImEXEvent("shop_template_delete"); + const result = await deleteTemplate({ variables: { templateId: templateId, diff --git a/client/src/components/shop-template-editor-save-button/shop-template-editor-save-button.component.jsx b/client/src/components/shop-template-editor-save-button/shop-template-editor-save-button.component.jsx index 23542ef09..15a28a4e1 100644 --- a/client/src/components/shop-template-editor-save-button/shop-template-editor-save-button.component.jsx +++ b/client/src/components/shop-template-editor-save-button/shop-template-editor-save-button.component.jsx @@ -3,12 +3,15 @@ import { Button, notification } from "antd"; import { useMutation } from "@apollo/react-hooks"; import { useTranslation } from "react-i18next"; import { UPDATE_TEMPLATE } from "../../graphql/templates.queries"; +import { logImEXEvent } from "../../firebase/firebase.utils"; export default function ShopTemplateSaveButton({ templateId, html, gql }) { const { t } = useTranslation(); const [updateTemplate] = useMutation(UPDATE_TEMPLATE); const handleSave = async () => { + logImEXEvent("shop_template_update"); + const result = await updateTemplate({ variables: { templateId: templateId, diff --git a/client/src/components/tech-job-clock-out-button/tech-job-clock-out-button.component.jsx b/client/src/components/tech-job-clock-out-button/tech-job-clock-out-button.component.jsx index 26a6a5959..d6ed5e1b8 100644 --- a/client/src/components/tech-job-clock-out-button/tech-job-clock-out-button.component.jsx +++ b/client/src/components/tech-job-clock-out-button/tech-job-clock-out-button.component.jsx @@ -17,6 +17,8 @@ import { createStructuredSelector } from "reselect"; import { selectBodyshop } from "../../redux/user/user.selectors"; import { selectTechnician } from "../../redux/tech/tech.selectors"; import TechJobClockoutDelete from "../tech-job-clock-out-delete/tech-job-clock-out-delete.component"; +import { logImEXEvent } from "../../firebase/firebase.utils"; + const mapStateToProps = createStructuredSelector({ bodyshop: selectBodyshop, technician: selectTechnician, @@ -37,6 +39,8 @@ export function TechClockOffButton({ const { t } = useTranslation(); const handleFinish = async (values) => { + logImEXEvent("tech_clock_out_job"); + setLoading(true); const result = await updateTimeticket({ variables: { diff --git a/client/src/components/tech-job-clock-out-delete/tech-job-clock-out-delete.component.jsx b/client/src/components/tech-job-clock-out-delete/tech-job-clock-out-delete.component.jsx index 2375f51a0..200f8060c 100644 --- a/client/src/components/tech-job-clock-out-delete/tech-job-clock-out-delete.component.jsx +++ b/client/src/components/tech-job-clock-out-delete/tech-job-clock-out-delete.component.jsx @@ -4,6 +4,7 @@ import { DeleteFilled } from "@ant-design/icons"; import { DELETE_TIME_TICKET } from "../../graphql/timetickets.queries"; import { useTranslation } from "react-i18next"; import { useMutation } from "@apollo/react-hooks"; +import { logImEXEvent } from "../../firebase/firebase.utils"; export default function TechJobClockoutDelete({ timeTicketId, @@ -12,6 +13,8 @@ export default function TechJobClockoutDelete({ const [deleteTimeTicket] = useMutation(DELETE_TIME_TICKET); const { t } = useTranslation(); const handleDelete = async () => { + logImEXEvent("tech_clock_delete"); + const result = await deleteTimeTicket({ variables: { id: timeTicketId }, refetchQueries: ["QUERY_ACTIVE_TIME_TICKETS"], diff --git a/client/src/components/vehicle-detail-update-jobs/vehicle-detail-update-jobs.component.jsx b/client/src/components/vehicle-detail-update-jobs/vehicle-detail-update-jobs.component.jsx index 44404ae28..33e76a913 100644 --- a/client/src/components/vehicle-detail-update-jobs/vehicle-detail-update-jobs.component.jsx +++ b/client/src/components/vehicle-detail-update-jobs/vehicle-detail-update-jobs.component.jsx @@ -3,15 +3,18 @@ import { Button, notification } from "antd"; import { useTranslation } from "react-i18next"; import { useMutation } from "@apollo/react-hooks"; import { UPDATE_JOBS } from "../../graphql/jobs.queries"; +import { logImEXEvent } from "../../firebase/firebase.utils"; export default function VehicleDetailUpdateJobsComponent({ vehicle, selectedJobs, - disabled + disabled, }) { const { t } = useTranslation(); const [updateJobs] = useMutation(UPDATE_JOBS); - const handlecClick = e => { + const handlecClick = (e) => { + logImEXEvent("vehicle_update_jobs"); + updateJobs({ variables: { jobIds: selectedJobs, @@ -22,16 +25,16 @@ export default function VehicleDetailUpdateJobsComponent({ v_model_yr: vehicle["v_model_yr"], v_model_desc: vehicle["v_model_desc"], v_make_desc: vehicle["v_make_desc"], - v_color: vehicle["v_color"] - } - } + v_color: vehicle["v_color"], + }, + }, }) - .then(response => { + .then((response) => { notification["success"]({ message: t("jobs.successes.updated") }); }) - .catch(error => { + .catch((error) => { notification["error"]({ - message: t("jobs.errors.updating", { error: JSON.stringify(error) }) + message: t("jobs.errors.updating", { error: JSON.stringify(error) }), }); }); }; diff --git a/client/src/firebase/firebase.utils.js b/client/src/firebase/firebase.utils.js index 46feef523..2c8e25ba7 100644 --- a/client/src/firebase/firebase.utils.js +++ b/client/src/firebase/firebase.utils.js @@ -47,7 +47,7 @@ try { export { messaging }; -export const logImEXEvent = async (eventName, additionalParams) => { +export const logImEXEvent = (eventName, additionalParams) => { const state = store.getState(); const eventParams = { shop: diff --git a/client/src/redux/messaging/messaging.sagas.js b/client/src/redux/messaging/messaging.sagas.js index 018815fc6..a63a9e065 100644 --- a/client/src/redux/messaging/messaging.sagas.js +++ b/client/src/redux/messaging/messaging.sagas.js @@ -2,23 +2,35 @@ import axios from "axios"; import phone from "phone"; import { all, call, put, select, takeLatest } from "redux-saga/effects"; import { client } from "../../App/App.container"; +import { logImEXEvent } from "../../firebase/firebase.utils"; import { CONVERSATION_ID_BY_PHONE, CREATE_CONVERSATION, } from "../../graphql/conversations.queries"; import { INSERT_CONVERSATION_TAG } from "../../graphql/job-conversations.queries"; +import { selectBodyshop } from "../user/user.selectors"; import { sendMessageFailure, sendMessageSuccess, setSelectedConversation, } from "./messaging.actions"; import MessagingActionTypes from "./messaging.types"; -import { selectBodyshop } from "../user/user.selectors"; +export function* onToggleChatVisible() { + yield takeLatest(MessagingActionTypes.TOGGLE_CHAT_VISIBLE, toggleChatLogging); +} +export function* toggleChatLogging() { + try { + yield logImEXEvent("messaging_toggle_popup"); + } catch (error) { + console.log("Error in sendMessage saga.", error); + } +} export function* onOpenChatByPhone() { yield takeLatest(MessagingActionTypes.OPEN_CHAT_BY_PHONE, openChatByPhone); } export function* openChatByPhone({ payload }) { + logImEXEvent("messaging_open_by_phone"); const { phone_num, jobid } = payload; const bodyshop = yield select(selectBodyshop); try { @@ -69,6 +81,8 @@ export function* onSendMessage() { } export function* sendMessage({ payload }) { try { + yield logImEXEvent("messaging_send_message"); + const response = yield call(axios.post, "/sms/send", payload); if (response.status === 200) { yield put(sendMessageSuccess(payload)); @@ -82,5 +96,9 @@ export function* sendMessage({ payload }) { } export function* messagingSagas() { - yield all([call(onSendMessage), call(onOpenChatByPhone)]); + yield all([ + call(onSendMessage), + call(onOpenChatByPhone), + call(onToggleChatVisible), + ]); } diff --git a/client/src/redux/modals/modals.reducer.js b/client/src/redux/modals/modals.reducer.js index e91b9658b..24589d2e2 100644 --- a/client/src/redux/modals/modals.reducer.js +++ b/client/src/redux/modals/modals.reducer.js @@ -1,4 +1,5 @@ import ModalsActionTypes from "./modals.types"; +import { logImEXEvent } from "../../firebase/firebase.utils"; const baseModal = { visible: false, @@ -24,6 +25,7 @@ const INITIAL_STATE = { const modalsReducer = (state = INITIAL_STATE, action) => { switch (action.type) { case ModalsActionTypes.TOGGLE_MODAL_VISIBLE: + logImEXEvent("redux_toggle_modal_visible", { modal: action.payload }); return { ...state, [action.payload]: { @@ -32,6 +34,8 @@ const modalsReducer = (state = INITIAL_STATE, action) => { }, }; case ModalsActionTypes.SET_MODAL_CONTEXT: + logImEXEvent("redux_set_modal_context", { modal: action.payload.modal }); + return { ...state, [action.payload.modal]: { diff --git a/client/src/redux/tech/tech.sagas.js b/client/src/redux/tech/tech.sagas.js index 1a1bc616a..74f797e99 100644 --- a/client/src/redux/tech/tech.sagas.js +++ b/client/src/redux/tech/tech.sagas.js @@ -3,30 +3,25 @@ import { all, call, put, select, takeLatest } from "redux-saga/effects"; import { selectBodyshop } from "../user/user.selectors"; import { techLoginFailure, techLoginSuccess } from "./tech.actions"; import TechActionTypes from "./tech.types"; +import { logImEXEvent } from "../../firebase/firebase.utils"; export function* onSignInStart() { yield takeLatest(TechActionTypes.TECH_LOGIN_START, signInStart); } export function* signInStart({ payload: { employeeid, pin } }) { try { + logImEXEvent("redux_tech_sign_in"); + const bodyshop = yield select(selectBodyshop); const response = yield call(axios.post, "/tech/login", { shopid: bodyshop.id, employeeid: employeeid, pin: pin, }); - console.log("response", response); + const { valid, technician, error } = response.data; - console.log( - "function*signInStart -> valid, technician, erro", - valid, - technician, - error - ); - if (valid) { - console.log("Valid in else"); yield put(techLoginSuccess(technician)); } else { yield put(techLoginFailure(error)); diff --git a/client/src/redux/user/user.sagas.js b/client/src/redux/user/user.sagas.js index 6fbd762a4..0a61b504f 100644 --- a/client/src/redux/user/user.sagas.js +++ b/client/src/redux/user/user.sagas.js @@ -1,24 +1,24 @@ -import { all, call, put, takeLatest, delay, select } from "redux-saga/effects"; -import LogRocket from "logrocket"; -import { firestore } from "../../firebase/firebase.utils"; import Fingerprint2 from "fingerprintjs2"; - +import LogRocket from "logrocket"; +import { all, call, delay, put, select, takeLatest } from "redux-saga/effects"; import { auth, + firestore, getCurrentUser, + logImEXEvent, updateCurrentUser, } from "../../firebase/firebase.utils"; import { + checkInstanceId, + setInstanceConflict, + setInstanceId, + setLocalFingerprint, signInFailure, signInSuccess, signOutFailure, signOutSuccess, unauthorizedUser, updateUserDetailsSuccess, - setInstanceId, - checkInstanceId, - setLocalFingerprint, - setInstanceConflict, } from "./user.actions"; import UserActionTypes from "./user.types"; @@ -27,6 +27,8 @@ export function* onEmailSignInStart() { } export function* signInWithEmail({ payload: { email, password } }) { try { + logImEXEvent("redux_sign_in_attempt", { user: email }); + const { user } = yield auth.signInWithEmailAndPassword(email, password); yield put( @@ -40,6 +42,7 @@ export function* signInWithEmail({ payload: { email, password } }) { ); } catch (error) { yield put(signInFailure(error)); + logImEXEvent("redux_sign_in_failure", { user: email, error }); } } @@ -48,6 +51,8 @@ export function* onCheckUserSession() { } export function* isUserAuthenticated() { try { + logImEXEvent("redux_auth_check"); + const user = yield getCurrentUser(); if (!user) { yield put(unauthorizedUser()); @@ -73,6 +78,8 @@ export function* onSignOutStart() { } export function* signOutStart() { try { + logImEXEvent("redux_sign_out"); + yield auth.signOut(); yield put(signOutSuccess()); localStorage.removeItem("token"); @@ -135,6 +142,7 @@ export function* checkInstanceIdSaga({ payload: uid }) { yield put(checkInstanceId(uid)); } else { console.log("ERROR: Fingerprints do not match. Conflict detected."); + logImEXEvent("instance_confict"); yield put(setInstanceConflict()); } } catch (error) { @@ -150,6 +158,7 @@ export function* onSignInSuccess() { export function* signInSuccessSaga({ payload }) { LogRocket.identify(payload.email); yield put(setInstanceId(payload.uid)); + yield logImEXEvent("redux_sign_in_success"); } export function* userSagas() {