Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Metrics Dashboard #1769

Open
wants to merge 14 commits into
base: development
Choose a base branch
from
3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@
"@ckeditor/ckeditor5-react": "^2.1.0",
"@emotion/react": "^11.11.0",
"@svgr/core": "^8.0.0",
"@tanstack/react-table": "^8.20.5",
"airbnb-prop-types": "^2.16.0",
"highcharts": "^9.1.0",
"highcharts-react-official": "^3.0.0",
Expand All @@ -87,7 +88,9 @@
"prop-types": "^15.7.2",
"quick-lint-js": "^3.2.0",
"react": "^16.8.0",
"react-bootstrap": "^2.10.6",
"react-data-table-component": "^7.6.2",
"react-datepicker": "^7.5.0",
"react-dom": "^16.8.0",
"react-grid-layout": "^1.3.0",
"react-markdown": "^8.0.7",
Expand Down
106 changes: 106 additions & 0 deletions src/clj/collect_earth_online/db/metrics.clj
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
(ns collect-earth-online.db.metrics
(:require
[triangulum.database :refer [call-sql sql-primitive]]
[triangulum.response :refer [data-response]]
[triangulum.logging :refer [log]]
[clojure.string :as str]))

(defn validate-dates [params]
(let [start-date (:startDate params)
end-date (:endDate params)]
(cond
(and start-date end-date) {:valid true}
(and (not start-date) (not end-date))
{:valid false
:message "Missing parameters: startDate and endDate are required."}
(not start-date)
{:valid false
:message "Missing parameter: startDate is required."}
(not end-date)
{:valid false
:message "Missing parameter: endDate is required."})))

(defn validation-error-response [message]
(data-response message {:status 400}))

(defn show-metrics-user [user-id]
(try
(sql-primitive (call-sql "show_metrics_user" user-id))
(catch Exception e
(log (ex-message e))
(data-response "Internal server error." {:status 500}))))


(defn get-imagery-counts [{:keys [params session]}]
(let [validation (validate-dates params)]
(if (:valid validation)
(try
(let [start-date (:startDate params)
end-date (:endDate params)]
(->> (call-sql "get_imagery_counts" start-date end-date)
(mapv (fn [{:keys [imagery_id imagery_name user_plot_count start_date end_date]}]
{:imageryId imagery_id
:imageryName imagery_name
:plots user_plot_count
:startDate start_date
:endDate end_date}))
(data-response)))
(catch Exception e
(log (ex-message e))
(data-response "Internal server error." {:status 500})))
(validation-error-response (:message validation)))))

(defn get-projects-with-gee [{:keys [params session]}]
(let [validation (validate-dates params)]
(if (:valid validation)
(try
(let [start-date (:startDate params)
end-date (:endDate params)]
(->> (call-sql "get_projects_with_gee" start-date end-date)
(mapv (fn [{:keys [show_gee_script project_count start_date end_date]}]
{:showGeeScript show_gee_script
:projects project_count
:startDate start_date
:endDate end_date}))
(data-response)))
(catch Exception e
(log (ex-message e))
(data-response "Internal server error." {:status 500})))
(validation-error-response (:message validation)))))

(defn get-sample-plot-counts [{:keys [params session]}]
(let [validation (validate-dates params)]
(if (:valid validation)
(try
(let [start-date (:startDate params)
end-date (:endDate params)]
(->> (call-sql "get_sample_plot_counts" start-date end-date)
(mapv (fn [{:keys [user_plot_count total_sample_count distinct_project_count start_date end_date]}]
{:userPlots user_plot_count
:totalSamples total_sample_count
:distinctProjects distinct_project_count
:startDate start_date
:endDate end_date}))
(data-response)))
(catch Exception e
(log (ex-message e))
(data-response "Internal server error." {:status 500})))
(validation-error-response (:message validation)))))

(defn get-project-count [{:keys [params session]}]
(let [validation (validate-dates params)]
(if (:valid validation)
(try
(let [start-date (:startDate params)
end-date (:endDate params)]
(->> (call-sql "get_project_count" start-date end-date)
(mapv (fn [{:keys [project_count start_date end_date]}]
{:projects project_count
:startDate (str start_date)
:endDate (str end_date)}))
(data-response)))
(catch Exception e
(log (ex-message e))
(data-response "Internal server error." {:status 500})))
(validation-error-response (:message validation)))))

5 changes: 4 additions & 1 deletion src/clj/collect_earth_online/db/plots.clj
Original file line number Diff line number Diff line change
Expand Up @@ -275,6 +275,7 @@
user-images (:userImages params)
new-plot-samples (:newPlotSamples params)
user-id (if review-mode? current-user-id session-user-id)
imagery-ids (tc/clj->jsonb (:imageryIds params))
;; Samples created in the UI have IDs starting with 1. When the new sample is created
;; in Postgres, it gets different ID. The user sample ID needs to be updated to match.
id-translation (when new-plot-samples
Expand All @@ -297,7 +298,9 @@
(when confidence-comment confidence-comment)
(when-not review-mode? (Timestamp. collection-start))
(tc/clj->jsonb (set/rename-keys user-samples id-translation))
(tc/clj->jsonb (set/rename-keys user-images id-translation)))
(tc/clj->jsonb (set/rename-keys user-images id-translation))
imagery-ids)

(call-sql "delete_user_plot_by_plot" plot-id user-id))
(unlock-plots user-id)
(data-response "")))
Expand Down
2 changes: 2 additions & 0 deletions src/clj/collect_earth_online/handlers.clj
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
(ns collect-earth-online.handlers
(:require [collect-earth-online.db.institutions :refer [is-inst-admin?]]
[collect-earth-online.db.projects :refer [can-collect? is-proj-admin?]]
[collect-earth-online.db.metrics :refer [show-metrics-user]]
[ring.util.codec :refer [url-encode]]
[ring.util.response :refer [redirect]]
[triangulum.config :refer [get-config]]
Expand All @@ -22,6 +23,7 @@
(pos? project-id) (is-proj-admin? user-id project-id token-key)
(pos? institution-id) (is-inst-admin? user-id institution-id))
:no-cross (no-cross-traffic? headers)
:metrics (show-metrics-user user-id)
true)))

(defn redirect-handler [{:keys [session query-string uri] :as _request}]
Expand Down
19 changes: 18 additions & 1 deletion src/clj/collect_earth_online/routing.clj
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
[collect-earth-online.db.geodash :as geodash]
[collect-earth-online.db.imagery :as imagery]
[collect-earth-online.db.institutions :as institutions]
[collect-earth-online.db.metrics :as metrics]
[collect-earth-online.db.plots :as plots]
[collect-earth-online.db.projects :as projects]
[collect-earth-online.db.qaqc :as qaqc]
Expand Down Expand Up @@ -55,6 +56,8 @@
[:get "/widget-layout-editor"] {:handler (render-page "/widget-layout-editor")
:auth-type :admin
:auth-action :redirect}
[:get "/metrics"] {:handler (render-page "/metrics")}

;; Users API
[:get "/get-institution-users"] {:handler users/get-institution-users
:auth-type :user
Expand Down Expand Up @@ -219,4 +222,18 @@
:auth-action :block}
[:get "/get-nicfi-tiles"] {:handler proxy/get-nicfi-tiles
:auth-type :no-cross
:auth-action :block}})
:auth-action :block}

;; Metrics
[:get "/metrics/get-imagery-access"] {:handler metrics/get-imagery-counts
:auth-type :metrics
:auth-action :block}
[:get "/metrics/get-projects-with-gee"] {:handler metrics/get-projects-with-gee
:auth-type :metrics
:auth-action :block}
[:get "/metrics/get-sample-plot-counts"] {:handler metrics/get-sample-plot-counts
:auth-type :metrics
:auth-action :block}
[:get "/metrics/get-project-count"] {:handler metrics/get-project-count}
:auth-type :metrics
:auth-action :block})
82 changes: 82 additions & 0 deletions src/css/metrics.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
.metrics-dashboard {
padding: 20px;
}

.dashboard-header {
background-color: #004d40;
padding: 20px;
color: white;
text-align: center;
margin-bottom: 20px;
}

.filters {
display: flex;
flex-wrap: wrap;
gap: 15px;
margin-bottom: 20px;
}

.filter-group {
display: flex;
gap: 15px;
flex: 1;
}

.filter {
display: flex;
flex-direction: column;
gap: 5px;
flex: 1;
}

.filter label {
font-size: 18px;
font-weight: bold;
}

.date-picker {
padding: 5px;
border: 1px solid #ccc;
border-radius: 4px;
}

.download-csv-btn {
background-color: #004d40;
color: white;
padding: 10px;
border: none;
border-radius: 4px;
cursor: pointer;
}

.download-csv-btn:hover {
background-color: #00332e;
}

.table-container {
margin-top: 20px;
}

.metrics-table {
width: 100%;
border-collapse: collapseg

.metrics-table th,
.metrics-table td {
border: 1px solid #ccc;
padding: 8px;
text-align: left;
}

.metrics-table th {
background-color: #004d40;
color: white;
}

.custom-dropdown {
background-color: #F0EBEB !important;
border-color: #F0EBEB !important;
color: black;
text-align: left;
}
7 changes: 7 additions & 0 deletions src/js/collection.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ class Collection extends React.Component {
imageryAttribution: "",
// attributes to record when sample is saved
imageryAttributes: {},
imageryIds: [],
imageryList: [],
inReviewMode: false,
mapConfig: null,
Expand Down Expand Up @@ -178,6 +179,11 @@ class Collection extends React.Component {
(this.state.currentImagery.id !== prevState.currentImagery.id ||
this.state.mapConfig !== prevState.mapConfig)
) {
if (!prevState.imageryIdsArray.includes(this.state.currentImagery.id)) {
this.setState((prevState) => ({
imageryIds: [...prevState.imageryIds, this.state.currentImagery.id],
}));
}
this.updateMapImagery();
}
}
Expand Down Expand Up @@ -763,6 +769,7 @@ class Collection extends React.Component {
this.state.currentProject.allowDrawnSamples && this.state.currentPlot.samples,
inReviewMode: this.state.inReviewMode,
currentUserId: this.state.currentUserId,
imageryIds: this.state.imageryIds
}),
}).then((response) => {
if (response.ok) {
Expand Down
Loading