(ns auto-ap.graphql (:require [auto-ap.datomic :refer [merge-query uri]] [auto-ap.datomic.users :as d-users] [auto-ap.graphql.accounts :as gq-accounts] [auto-ap.graphql.checks :as gq-checks] [auto-ap.graphql.clients :as gq-clients] [auto-ap.graphql.expected-deposit :as gq-expected-deposit] [auto-ap.graphql.ezcater :as gq-ezcater] [auto-ap.graphql.import-batch :as gq-import-batches] [auto-ap.graphql.intuit-bank-accounts :as gq-intuit-bank-accounts] [auto-ap.graphql.invoices :as gq-invoices] [auto-ap.graphql.jobs :as gq-jobs] [auto-ap.graphql.ledger :as gq-ledger] [auto-ap.graphql.plaid :as gq-plaid] [auto-ap.graphql.reports :as gq-reports] [auto-ap.graphql.sales-orders :as gq-sales-orders] [auto-ap.graphql.transaction-rules :as gq-transaction-rules] [auto-ap.graphql.transactions :as gq-transactions] [auto-ap.graphql.users :as gq-users] [auto-ap.graphql.utils :refer [assert-admin attach-tracing-resolvers]] [auto-ap.graphql.vendors :as gq-vendors] [auto-ap.graphql.yodlee-merchants :as ym] [auto-ap.graphql.yodlee2 :as gq-yodlee2] [auto-ap.logging :as alog :refer [error-event info-event warn-event]] [auto-ap.time :as time] [clj-time.coerce :as coerce] [clj-time.core :as t] [clojure.string :as str] [clojure.tools.logging :as log] [clojure.walk :as walk] [com.brunobonacci.mulog :as mu] [com.unbounce.dogstatsd.core :as statsd] [com.walmartlabs.lacinia :refer [execute]] [com.walmartlabs.lacinia.parser :as p] [com.walmartlabs.lacinia.schema :as schema] [datomic.api :as d] [unilog.context :as lc] [yang.time :refer [time-it]]) (:import (clojure.lang IPersistentMap))) (def integreat-schema { :scalars {:id {:parse #(cond (number? %) % % (Long/parseLong %)) :serialize #(.toString %)} :ident {:parse (fn [x] {:db/ident x}) :serialize #(or (:ident %) (:db/ident %) %)} :iso_date {:parse #(time/parse % time/iso-date) :serialize #(time/unparse % time/iso-date)} :iso_date_time {:parse #(clj-time.coerce/to-date-time %) :serialize #(str (clj-time.coerce/to-date-time %))} :money {:parse #(cond (and (string? %) (not (str/blank? %))) (Double/parseDouble %) (and (string? %) (str/blank? (str/trim %))) 0.0 (nil? %) 0.0 (int? %) (double %) :else %) :serialize #(cond (double? %) (str %) (int? %) (str %) :else %)} :percentage {:parse #(cond (and (string? %) (not (str/blank? %))) (Double/parseDouble %) (int? %) (double %) :else %) :serialize #(if (double? %) (str %) %)}} :objects { :message {:fields {:message {:type 'String}}} :search_result {:fields {:name {:type 'String} :id {:type :id}}} :account_search_result {:fields {:name {:type 'String} :id {:type :id} :location {:type 'String} :warning {:type 'String}}} :yodlee_provider_account {:fields {:id {:type 'Int} :client {:type :client} :status {:type 'String} :detailed_status {:type 'String} :last_updated {:type :iso_date} :accounts {:type '(list :yodlee_account)}}} :integration_status {:fields {:id {:type :id} :last_attempt {:type :iso_date} :last_updated {:type :iso_date} :message {:type 'String} :state {:type :integration_state}}} :yodlee_account {:fields {:id {:type 'Int} :status {:type 'String} :available_balance {:type :money} :name {:type 'String} :number {:type 'String} :last_updated {:type :iso_date}}} :contact {:fields {:id {:type :id} :name {:type 'String} :email {:type 'String} :phone {:type 'String}}} :address {:fields {:street1 {:type 'String} :street2 {:type 'String} :city {:type 'String} :state {:type 'String} :zip {:type 'String}}} :terms_override {:fields {:id {:type :id} :client {:type :client} :terms {:type 'Int}}} :schedule_payment_dom {:fields {:id {:type :id} :client {:type :client} :dom {:type 'Int}}} :vendor_account_override {:fields {:id {:type :id} :client {:type :client} :account {:type :account}}} :usage {:fields {:client_id {:type :id} :count {:type 'Int}}} :vendor {:fields {:id {:type :id} :name {:type 'String} :code {:type 'String} :terms {:type 'Int} :hidden {:type 'Boolean} :automatically_paid_when_due {:type '(list :client)} :terms_overrides {:type '(list :terms_override)} :schedule_payment_dom {:type '(list :schedule_payment_dom)} :account_overrides {:type '(list :vendor_account_override)} :usage {:type '(list :usage)} :print_as {:type 'String} :primary_contact {:type :contact} :secondary_contact {:type :contact} :address {:type :address} :default_account {:type :account} :invoice_reminder_schedule {:type 'String} :legal_entity_first_name {:type 'String} :legal_entity_middle_name {:type 'String} :legal_entity_last_name {:type 'String} :legal_entity_tin {:type 'String} :legal_entity_tin_type {:type :tin_type} :legal_entity_1099_type {:type :type_1099}}} :reminder {:fields {:id {:type 'Int} :email {:type 'String} :subject {:type 'String} :body {:type 'String} :scheduled {:type 'String} :sent {:type 'String} :vendor {:type :vendor}}} :yodlee_merchant {:fields {:id {:type :id} :yodlee_id {:type 'String} :name {:type 'String}}} :intuit_bank_account {:fields {:id {:type :id} :external_id {:type 'String} :name {:type 'String}}} :forecast_match {:fields {:id {:type :id} :identifier {:type 'String}}} :transaction_rule {:fields {:id {:type :id} :note {:type 'String} :client {:type :client} :bank_account {:type :bank_account} :yodlee_merchant {:type :yodlee_merchant} :description {:type 'String} :amount_lte {:type 'String} :amount_gte {:type 'String} :dom_lte {:type 'Int} :dom_gte {:type 'Int} :vendor {:type :vendor} :accounts {:type '(list :percentage_account)} :transaction_approval_status {:type :transaction_approval_status}}} :user {:fields {:id {:type :id} :name {:type 'String} :role {:type :role} :clients {:type '(list :client)}}} :csv {:fields {:csv_content_b64 {:type 'String}}} :account_client_override {:fields {:id {:type :id} :client {:type :client} :name {:type 'String}}} :account {:fields {:id {:type :id} :numeric_code {:type 'Int} :invoice_allowance {:type :allowance} :vendor_allowance {:type :allowance} :type {:type :ident} :applicability {:type :applicability} :account_set {:type 'String} :location {:type 'String} :name {:type 'String} :client_overrides {:type '(list :account_client_override)}}} :percentage_account {:fields {:id {:type :id} :account {:type :account} :location {:type 'String} :percentage {:type :percentage}}} :yodlee_provider_account_page {:fields {:yodlee_provider_accounts {:type '(list :yodlee_provider_account)} :count {:type 'Int} :total {:type 'Int} :start {:type 'Int} :end {:type 'Int}}} :transaction_rule_page {:fields {:transaction_rules {:type '(list :transaction_rule)} :count {:type 'Int} :total {:type 'Int} :start {:type 'Int} :end {:type 'Int}}} :vendor_page {:fields {:vendors {:type '(list :vendor)} :count {:type 'Int} :total {:type 'Int} :start {:type 'Int} :end {:type 'Int}}} :account_page {:fields {:accounts {:type '(list :account)} :count {:type 'Int} :total {:type 'Int} :start {:type 'Int} :end {:type 'Int}}} :reminder_page {:fields {:reminders {:type '(list :reminder)} :count {:type 'Int} :total {:type 'Int} :start {:type 'Int} :end {:type 'Int}}} :check_result {:fields {:invoices {:type '(list :invoice)} :pdf_url {:type 'String}}} :expense_account_stat {:fields {:account {:type :account} :total {:type 'String}}} :invoice_stat {:fields {:name {:type 'String} :paid {:type 'String} :unpaid {:type 'String}}} :upcoming_transaction {:fields {:amount {:type :money} :identifier {:type 'String} :date {:type :iso_date}}} :cash_flow_result {:fields {:beginning_balance {:type :money} :invoices_due_soon {:type '(list :invoice)} :outstanding_payments {:type :money} :upcoming_credits {:type '(list :upcoming_transaction)} :upcoming_debits {:type '(list :upcoming_transaction)}}}} :queries {:expense_account_stats {:type '(list :expense_account_stat) :args {:client_id {:type :id}} :resolve :get-expense-account-stats} :potential_transaction_rule_matches {:type '(list :transaction_rule) :args {:transaction_id {:type :id}} :resolve :get-transaction-rule-matches} :test_transaction_rule {:type '(list :transaction) :args {:transaction_rule {:type :edit_transaction_rule}} :resolve :test-transaction-rule} :run_transaction_rule {:type '(list :transaction) :args {:transaction_rule_id {:type :id}} :resolve :run-transaction-rule} :invoice_stats {:type '(list :invoice_stat) :args {:client_id {:type :id}} :resolve :get-invoice-stats} :cash_flow {:type :cash_flow_result :args {:client_id {:type :id}} :resolve :get-cash-flow} :yodlee_provider_account_page {:type :yodlee_provider_account_page :args {:client_id {:type :id}} :resolve :get-yodlee-provider-account-page} :account_page {:type :account_page :args {:name_like {:type 'String} :numeric_code {:type 'Int} :start {:type 'Int} :per_page {:type 'Int} :sort {:type '(list :sort_item)}} :resolve :get-accounts} :all_accounts {:type '(list :account) :args {} :resolve :get-all-accounts} :search_vendor {:type '(list :search_result) :args {:query {:type 'String}} :resolve :search-vendor} :search_account {:type '(list :account_search_result) :args {:query {:type 'String} :allowance {:type :account_allowance} :client_id {:type :id}} :resolve :search-account} :yodlee_merchants {:type '(list :yodlee_merchant) :args {} :resolve :get-yodlee-merchants} :intuit_bank_accounts {:type '(list :intuit_bank_account) :args {} :resolve :get-intuit-bank-accounts} :transaction_rule_page {:type :transaction_rule_page :args {:client_id {:type :id} :vendor_id {:type :id} :start {:type 'Int} :per_page {:type 'Int} :sort {:type '(list :sort_item)} :asc {:type 'Boolean} :note {:type 'String} :description {:type 'String}} :resolve :get-transaction-rule-page} :vendor {:type :vendor_page :args {:name_like {:type 'String} :start {:type 'Int} :per_page {:type 'Int} :sort {:type '(list :sort_item)}} :resolve :get-vendor} :user {:type '(list :user) :resolve :get-user} :vendor_by_id {:type :vendor :args {:id {:type :id}} :resolve :vendor-by-id} :account_for_vendor {:type :account :args {:client_id {:type :id} :vendor_id {:type :id}} :resolve :account-for-vendor}} :input-objects { :sort_item {:fields {:sort_key {:type 'String} :sort_name {:type 'String} :asc {:type 'Boolean}}} :date_range {:fields {:start {:type :iso_date} :end {:type :iso_date}}} :edit_user {:fields {:id {:type :id} :name {:type 'String} :role {:type :role} :clients {:type '(list String)}}} :add_contact {:fields {:id {:type :id} :name {:type 'String} :email {:type 'String} :phone {:type 'String}}} :add_address {:fields {:street1 {:type 'String} :street2 {:type 'String} :city {:type 'String} :state {:type 'String} :zip {:type 'String}}} :add_terms_override {:fields {:id {:type :id} :client_id {:type :id} :terms {:type 'Int}}} :add_account_override {:fields {:id {:type :id} :client_id {:type :id} :account_id {:type :id}}} :add_schedule_payment_dom {:fields {:id {:type :id} :client_id {:type :id} :dom {:type 'Int}}} :add_vendor {:fields {:id {:type :id} :name {:type 'String} :terms {:type 'Int} :terms_overrides {:type '(list :add_terms_override)} :code {:type 'String} :automatically_paid_when_due {:type '(list :id)} :hidden {:type 'Boolean} :print_as {:type 'String} :primary_contact {:type :add_contact} :secondary_contact {:type :add_contact} :address {:type :add_address} :default_account_id {:type :id} :account_overrides {:type '(list :add_account_override)} :schedule_payment_dom {:type '(list :add_schedule_payment_dom)} :invoice_reminder_schedule {:type 'String} :legal_entity_first_name {:type 'String} :legal_entity_middle_name {:type 'String} :legal_entity_last_name {:type 'String} :legal_entity_tin {:type 'String} :legal_entity_tin_type {:type :tin_type} :legal_entity_1099_type {:type :type_1099}}} :edit_percentage_account {:fields {:id {:type :id} :account_id {:type :id} :location {:type 'String} :percentage {:type :percentage}}} :edit_transaction_rule {:fields {:id {:type :id} :description {:type 'String} :note {:type 'String} :bank_account_id {:type :id} :client_id {:type :id} :yodlee_merchant_id {:type :id} :amount_lte {:type :money} :amount_gte {:type :money} :dom_lte {:type 'Int} :dom_gte {:type 'Int} :vendor_id {:type :id} :accounts {:type '(list :edit_percentage_account)} :transaction_approval_status {:type :transaction_approval_status}}} :edit_account_client_override {:fields {:id {:type :id} :client_id {:type :id} :name {:type 'String}}} :edit_account {:fields {:id {:type :id} :type {:type :account_type} :applicability {:type :applicability} :invoice_allowance {:type :allowance} :vendor_allowance {:type :allowance} :numeric_code {:type 'Int} :location {:type 'String} :account_set {:type 'String} :name {:type 'String} :client_overrides {:type '(list :edit_account_client_override)}}}} :enums { :processor {:values [{:enum-value :na} {:enum-value :doordash} {:enum-value :koala} {:enum-value :ezcater} {:enum-value :square} {:enum-value :uber_eats} {:enum-value :grubhub}]} :account_allowance {:values [{:enum-value :vendor} {:enum-value :invoice}]} :integration_state {:values [{:enum-value :failed} {:enum-value :success} {:enum-value :unauthorized}]} :allowance {:values [{:enum-value :allowed} {:enum-value :denied} {:enum-value :warn} {:enum-value :admin_only}]} :tin_type {:values [{:enum-value :ein} {:enum-value :ssn}]} :type_1099 {:values [{:enum-value :none} {:enum-value :misc} {:enum-value :landlord}]} :applicability {:values [{:enum-value :global} {:enum-value :optional} {:enum-value :customized}]} :role {:values [{:enum-value :none} {:enum-value :user} {:enum-value :manager} {:enum-value :power_user} {:enum-value :admin}]} :account_type {:values [{:enum-value :dividend} {:enum-value :expense} {:enum-value :asset} {:enum-value :liability} {:enum-value :equity} {:enum-value :revenue}]}} :mutations { :delete_transaction_rule {:type :id :args {:transaction_rule_id {:type :id}} :resolve :mutation/delete-transaction-rule} :merge_vendors {:type :id :args {:from {:type :id} :to {:type :id}} :resolve :mutation/merge-vendors} :edit_user {:type :user :args {:edit_user {:type :edit_user}} :resolve :mutation/edit-user} :upsert_vendor {:type :vendor :args {:vendor {:type :add_vendor}} :resolve :mutation/upsert-vendor} :upsert_transaction_rule {:type :transaction_rule :args {:transaction_rule {:type :edit_transaction_rule}} :resolve :mutation/upsert-transaction-rule} :upsert_account {:type :account :args {:account {:type :edit_account}} :resolve :mutation/upsert-account}}}) (defn snake->kebab [s] (str/replace s #"_" "-")) (defn kebab [x] (keyword (snake->kebab (name x)))) (defn kebab->snake [s] (str/replace s #"-" "_")) (defn snake [x] (keyword (kebab->snake (name x)))) (defn ->graphql [m] (walk/postwalk (fn [node] (cond (keyword? node) (snake node) :else node)) m)) (defn get-user [context args _] (assert-admin (:id context)) (let [users (d-users/get-graphql args)] (->graphql users))) #_{:clj-kondo/ignore [:clojure-lsp/unused-public-var]} (defn categorize [x] (cond (<= x 0) :due (<= x 30 ) :due-30 (<= x 60 ) :due-60 :else :due-later)) (defn get-expense-account-stats [_ {:keys [client_id] } _] (let [result (cond-> {:query {:find ['?account '?account-name '(sum ?amount)] :in ['$] :where []} :args [(d/db (d/connect uri)) client_id]} client_id (merge-query {:query {:in ['?c]} :args [client_id]}) (not client_id) (merge-query {:query {:where ['[?c :client/name]]}}) true (merge-query {:query {:where ['[?i :invoice/client ?c] '[?i :invoice/expense-accounts ?expense-account] '[?expense-account :invoice-expense-account/account ?account] '[?account :account/name ?account-name] '[?expense-account :invoice-expense-account/amount ?amount]]}}) true (d/query))] (for [[account-id account-name total] result] {:account {:id account-id :name account-name} :total total}))) (defn get-invoice-stats [_ {:keys [client_id] } _] (let [result (cond-> {:query {:find ['?name '(sum ?outstanding-balance) '(sum ?total)] :in ['$] :where []} :args [(d/db (d/connect uri)) client_id]} client_id (merge-query {:query {:in ['?c]} :args [client_id]}) (not client_id) (merge-query {:query {:where ['[?c :client/name]]}}) true (merge-query {:query {:where ['[?i :invoice/client ?c] '[?i :invoice/outstanding-balance ?outstanding-balance] '[?i :invoice/total ?total] '[?i :invoice/due ?date] '[(.toInstant ^java.util.Date ?date) ?d2] '[(.between java.time.temporal.ChronoUnit/DAYS (java.time.Instant/now) ?d2 ) ?d3] '[(auto-ap.graphql/categorize ?d3) ?name]]}}) true (d/query)) result (group-by first result)] (for [[id name] [[:due "Due"] [:due-30 "0-30 days"] [:due-60 "31-60 days"] [:due-later ">60 days"]] :let [[[_ outstanding-balance total] ] (id result nil) outstanding-balance (or outstanding-balance 0) total (or total 0)]] {:name name :unpaid outstanding-balance :paid (if (= :due id) 0 (- total outstanding-balance))}))) (defn has-fulfilled? [id date recent-fulfillments] (seq (transduce (filter (fn [[potential-id potential-date]] (let [date (coerce/to-date-time date) potential-date (coerce/to-date-time potential-date)] (and (= id potential-id) (<= (t/in-days (apply t/interval (sort [date potential-date]))) 10))))) conj [] recent-fulfillments))) (def first-week-a (coerce/to-date-time #inst "1999-12-27T00:00:00.000-07:00")) (defn get-cash-flow [_ {:keys [client_id]} _] (when client_id (let [{:client/keys [week-a-credits week-a-debits week-b-credits week-b-debits forecasted-transactions ]} (d/pull (d/db (d/connect uri)) '[*] client_id) total-cash (reduce (fn [total [credit debit]] (- (+ total credit) debit)) 0.0 (d/query {:query {:find '[?debit ?credit] :in '[$ ?client] :where ['[?j :journal-entry/client ?client] '[?j :journal-entry/line-items ?je] '[?je :journal-entry-line/account ?ba] '[?ba :bank-account/type :bank-account-type/check] '[(get-else $ ?je :journal-entry-line/debit 0.0) ?debit] '[(get-else $ ?je :journal-entry-line/credit 0.0) ?credit]]} :args [(d/db (d/connect uri)) client_id]})) bills-due-soon (d/query {:query {:find '[?due ?outstanding ?invoice-number ?vendor-id ?vendor-name] :in '[$ ?client ?due-before] :where ['[?i :invoice/client ?client] '[?i :invoice/status :invoice-status/unpaid] '[?i :invoice/due ?due] '[(<= ?due ?due-before)] '[?i :invoice/outstanding-balance ?outstanding] '[?i :invoice/invoice-number ?invoice-number] '[?i :invoice/vendor ?vendor-id] '[?vendor-id :vendor/name ?vendor-name]]} :args [(d/db (d/connect uri)) client_id (coerce/to-date (t/plus (time/local-now) (t/days 180)))]}) outstanding-checks (reduce + 0.0 (map first (d/query {:query {:find '[?amount] :in '[$ ?client ?due-before] :where ['[?p :payment/client ?client] '[?p :payment/status :payment-status/pending] '[?p :payment/amount ?amount] '(or [?p :payment/type :payment-type/debit] [?p :payment/type :payment-type/check])]} :args [(d/db (d/connect uri)) client_id (coerce/to-date (t/plus (time/local-now) (t/days 180)))]}))) recent-fulfillments (d/query {:query {:find '[?f ?d] :in '[$ ?client ?min-date] :where ['[?t :transaction/forecast-match ?f] '[?t :transaction/date ?d] '[?t :transaction/client ?client] '[(>= ?d ?min-date)]]} :args [(d/db (d/connect uri)) client_id (coerce/to-date (t/plus (time/local-now) (t/months -2)))]}) forecasted-transactions (for [{:forecasted-transaction/keys [amount identifier day-of-month] :db/keys [id]} forecasted-transactions month (range -1 7) :let [next (t/plus (t/local-date (t/year (time/local-now)) (t/month (time/local-now)) (Math/min (Math/max day-of-month 1) 30)) (t/months month))] :when (not (has-fulfilled? id next recent-fulfillments))] {:identifier identifier :amount amount :date (coerce/to-date-time next)}) is-week-a? (fn [d] (= 0 (mod (t/in-weeks (t/interval first-week-a d)) 2)))] {:beginning_balance total-cash :outstanding_payments outstanding-checks :invoices_due_soon (mapv (fn [[due outstanding invoice-number vendor-id vendor-name]] {:due (coerce/to-date-time due) :invoice_number invoice-number :vendor {:id vendor-id :name vendor-name} :outstanding_balance outstanding}) bills-due-soon) :upcoming_credits (into (mapv (fn [date] {:amount (if (is-week-a? (coerce/to-date-time date)) (or week-a-credits 0) (or week-b-credits 0)) :date (coerce/to-date-time date)}) (take (* 7 4) (time/day-of-week-seq 1))) (filter #(>= (:amount %) 0) forecasted-transactions)) :upcoming_debits (into (mapv (fn [date] {:amount (- (if (is-week-a? (coerce/to-date-time date)) (or week-a-debits 0) (or week-b-debits 0))) :date (coerce/to-date-time date)}) (take (* 7 4) (time/day-of-week-seq 1))) (filter #(< (:amount %) 0) forecasted-transactions))}))) (def schema (-> integreat-schema (attach-tracing-resolvers { :get-yodlee-provider-account-page gq-yodlee2/get-yodlee-provider-account-page :get-accounts gq-accounts/get-graphql :get-all-accounts gq-accounts/get-all-graphql :get-transaction-rule-page gq-transaction-rules/get-transaction-rule-page :get-transaction-rule-matches gq-transaction-rules/get-transaction-rule-matches :get-expense-account-stats get-expense-account-stats :get-invoice-stats get-invoice-stats :get-cash-flow get-cash-flow :get-yodlee-merchants ym/get-yodlee-merchants :get-intuit-bank-accounts gq-intuit-bank-accounts/get-intuit-bank-accounts :vendor-by-id gq-vendors/get-by-id :account-for-vendor gq-accounts/default-for-vendor :get-user get-user :mutation/delete-transaction-rule gq-transaction-rules/delete-transaction-rule :mutation/edit-user gq-users/edit-user :mutation/upsert-transaction-rule gq-transaction-rules/upsert-transaction-rule :test-transaction-rule gq-transaction-rules/test-transaction-rule :run-transaction-rule gq-transaction-rules/run-transaction-rule :mutation/upsert-vendor gq-vendors/upsert-vendor :mutation/upsert-account gq-accounts/upsert-account :mutation/merge-vendors gq-vendors/merge-vendors :get-vendor gq-vendors/get-graphql :search-vendor gq-vendors/search :search-account gq-accounts/search}) gq-checks/attach gq-ledger/attach gq-reports/attach gq-plaid/attach gq-import-batches/attach gq-transactions/attach gq-expected-deposit/attach gq-ezcater/attach gq-invoices/attach gq-clients/attach gq-sales-orders/attach gq-jobs/attach schema/compile)) (defn simplify "Converts all ordered maps nested within the map into standard hash maps, and sequences into vectors, which makes for easier constants in the tests, and eliminates ordering problems." [m] (walk/postwalk (fn [node] (cond (instance? IPersistentMap node) (into {} node) (seq? node) (vec node) (keyword? node) (kebab node) :else node)) m)) (defn query-name [q] (try (str/join "__" (map name (:operations (p/operations (p/parse-query schema q))))) (catch Exception _ "unknown query"))) (defn query ([id q] (query id q nil)) ([id q v] (statsd/increment "query.graphql.count" {:tags #{(str "query:" (query-name q))}}) (statsd/time! [(str "query.graphql.time" ) {:tags #{(str "query:" (query-name q))}}] (mu/with-context {:query-string q :user id} (lc/with-context {:query q} (log/info "executing query name" (query-name q)) (try (let [[result time] (time-it (simplify (execute schema q v {:id id})))] (info-event "Query completed" {:time (:time time) :errors (seq (:errors result))}) (when (seq (:errors result)) (throw (ex-info "GraphQL error" {:result result}))) result) (catch Exception e (if-let [v (or (:validation-error (ex-data e)) (:validation-error (ex-data (.getCause e))))] (do (alog/warn ::query-validation :exception e) (warn-event "validation error" {:validation-error v :data (ex-data e)}) (throw e) #_{:errors [{:message v}]}) (do (error-event "query error" {:error e}) (alog/error ::query-error :exception e) (throw e))))))))))