src/cljs/main/broadfcui/common/entity_table.cljs (158 lines of code) (raw):

(ns broadfcui.common.entity-table (:require [dmohs.react :as react] [inflections.core :as inflections] [clojure.string :as string] [broadfcui.common :as common] [broadfcui.common.components :as comps] [broadfcui.common.style :as style] [broadfcui.common.table :refer [Table]] [broadfcui.common.table.style :as table-style] [broadfcui.common.table.utils :as table-utils] [broadfcui.config :as config] [broadfcui.endpoints :as endpoints] [broadfcui.utils :as utils] )) ;; for attributes referring to a single other entity ;; e.g. samples referring to participants (defn is-single-ref? [attr-value] (and (map? attr-value) (= (set (keys attr-value)) #{:entityType :entityName}))) (defn- render-list-item [item] (if (is-single-ref? item) (:entityName item) item)) (react/defc EntityTable {:update-data (fn [{:keys [refs after-update]} reinitialize?] ((@refs "table") :refresh-rows) (when reinitialize? (after-update #((@refs "table") :reinitialize)))) :refresh (fn [{:keys [props state this after-update]} & [entity-type reinitialize?]] (swap! state dissoc :entity-metadata) (endpoints/call-ajax-orch {:endpoint (endpoints/get-entity-types (:workspace-id props)) :on-done (fn [{:keys [success? get-parsed-response]}] (if success? (let [metadata (get-parsed-response) entity-types-from-response (map name (keys metadata)) entity-types-with-given (if entity-type (distinct (conj entity-types-from-response entity-type)) entity-types-from-response) entity-types-sorted (utils/sort-match common/root-entity-types entity-types-with-given) selected-entity-type (or entity-type (first entity-types-sorted))] (swap! state assoc :entity-metadata metadata :entity-types entity-types-sorted :selected-entity-type selected-entity-type) (when-let [f (:on-entity-type-selected props)] (f selected-entity-type)) (after-update #(this :update-data reinitialize?))) (swap! state assoc :server-error (get-parsed-response false))))})) :get-default-props (fn [] {:empty-message "There are no entities to display." :attribute-renderer table-utils/default-render}) :render (fn [{:keys [props state this]}] (let [{:keys [server-error entity-metadata entity-types selected-entity-type]} @state] [:div {} (cond server-error (style/create-server-error-message server-error) (nil? entity-metadata) [:div {:style {:textAlign "center"}} [comps/Spinner {:text "Retrieving entity types..."}]] :else (let [attributes (map keyword (get-in entity-metadata [(keyword selected-entity-type) :attributeNames])) attr-col-width (->> attributes count (/ 1000) int (min 400) (max 100))] [Table {:data-test-id "entity-table" :ref "table" :key selected-entity-type :persistence-key (when selected-entity-type (str (common/workspace-id->string (:workspace-id props)) ":data:" selected-entity-type)) :v 2 :fetch-data (this :-pagination) :blocker-delay-time-ms 0 :tabs {:items (->> entity-types (map (fn [entity-type] {:label entity-type :entity-type entity-type :size (get-in entity-metadata [(keyword entity-type) :count])})) vec) :initial-selection #(= (:entity-type %) selected-entity-type) :on-tab-selected (fn [{:keys [entity-type]}] (swap! state assoc :selected-entity-type entity-type) (when-let [f (:on-entity-type-selected props)] (f entity-type))) :render (fn [label count] (str label " (" count " total)"))} :body {:style (merge table-style/table-heavy (:style props)) ;; this :behavior and the 'or' guard on the first column are to make things ;; behave when there is no data (and thus no entity-types) :behavior {:reorderable-columns? (seq entity-types) :filterable? (seq entity-types)} :columns (into [{:header (or (get-in entity-metadata [(keyword selected-entity-type) :idName]) "No data") :initial-width 200 :as-text :name :sort-by :text :render (or (:entity-name-renderer props) :name)}] (map (fn [k] {:header (name k) :initial-width attr-col-width :column-data (comp k :attributes) :as-text (fn [attr-value] (cond (is-single-ref? attr-value) (:entityName attr-value) (common/attribute-list? attr-value) (map render-list-item (common/attribute-values attr-value)) :else (str attr-value))) :sort-by :text :render (fn [attr-value] (cond (is-single-ref? attr-value) (if-let [renderer (:linked-entity-renderer props)] (renderer attr-value) (:entityName attr-value)) (common/attribute-list? attr-value) (let [items (map render-list-item (common/attribute-values attr-value)) c (count items) entity-type (or (some-> selected-entity-type common/set-type->membership-attribute inflections/singular) "item")] (str (inflections/pluralize c entity-type) (when (and (pos? c) (= entity-type "item")) (str ": " (string/join ", " items))))) :else ((:attribute-renderer props) attr-value)))}) attributes)) :column-defaults (get (:column-defaults props) selected-entity-type) :on-row-click (:on-row-click props) :on-column-change (:on-column-change props)} :toolbar {:get-items (fn [table-props] (when-let [get-toolbar-items (:get-toolbar-items props)] (get-toolbar-items table-props))) :style {:flexWrap "wrap"}}}]))])) :component-did-mount (fn [{:keys [props this]}] (this :refresh (:initial-entity-type props))) :-pagination (fn [{:keys [props state]}] (let [{:keys [entity-types]} @state] (fn [{:keys [query-params tab on-done]}] (if (empty? entity-types) (on-done {:total-count 0 :tab-count 0 :results []}) (let [{:keys [page-number rows-per-page filter-text sort-column sort-order]} query-params entity-type (:entity-type tab)] (endpoints/call-ajax-orch {:endpoint (endpoints/get-entities-paginated (:workspace-id props) entity-type {"page" page-number "pageSize" rows-per-page "filterTerms" (js/encodeURIComponent filter-text) "sortField" sort-column "sortDirection" (name sort-order)}) :on-done (fn [{:keys [success? get-parsed-response status-text status-code]}] (if success? (let [{:keys [results] {:keys [unfilteredCount filteredCount]} :resultMetadata} (get-parsed-response)] (on-done {:total-count unfilteredCount :tab-count filteredCount :results results})) (on-done {:error (str status-text " (" status-code ")")})))}))))))})