WIP: Add dashboard stats queries and endpoint
This commit is contained in:
parent
9aa413013a
commit
31e180089e
23
admin.go
23
admin.go
|
@ -3,8 +3,10 @@ package main
|
|||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/jmoiron/sqlx/types"
|
||||
"github.com/labstack/echo"
|
||||
)
|
||||
|
||||
|
@ -15,10 +17,8 @@ type configScript struct {
|
|||
Messengers []string `json:"messengers"`
|
||||
}
|
||||
|
||||
// handleGetStats returns a collection of general statistics.
|
||||
func handleGetStats(c echo.Context) error {
|
||||
app := c.Get("app").(*App)
|
||||
return c.JSON(http.StatusOK, okResp{app.Runner.GetMessengerNames()})
|
||||
type dashboardStats struct {
|
||||
Stats types.JSONText `db:"stats"`
|
||||
}
|
||||
|
||||
// handleGetConfigScript returns general configuration as a Javascript
|
||||
|
@ -41,3 +41,18 @@ func handleGetConfigScript(c echo.Context) error {
|
|||
j.Encode(out)
|
||||
return c.Blob(http.StatusOK, "application/javascript", b.Bytes())
|
||||
}
|
||||
|
||||
// handleGetDashboardStats returns general states for the dashboard.
|
||||
func handleGetDashboardStats(c echo.Context) error {
|
||||
var (
|
||||
app = c.Get("app").(*App)
|
||||
out dashboardStats
|
||||
)
|
||||
|
||||
if err := app.Queries.GetDashboardStats.Get(&out); err != nil {
|
||||
return echo.NewHTTPError(http.StatusInternalServerError,
|
||||
fmt.Sprintf("Error fetching dashboard stats: %s", pqErrMsg(err)))
|
||||
}
|
||||
|
||||
return c.JSON(http.StatusOK, okResp{out.Stats})
|
||||
}
|
||||
|
|
|
@ -1,12 +1,38 @@
|
|||
import React from 'react';
|
||||
import { Button, Col, Form, Icon, Input, Modal, notification, Popconfirm, Row, Select, Spin, Table, Tag, Tooltip } from "antd"
|
||||
import React from "react";
|
||||
|
||||
import * as cs from "./constants"
|
||||
|
||||
class Dashboard extends React.PureComponent {
|
||||
state = {
|
||||
stats: null
|
||||
}
|
||||
|
||||
componentDidMount = () => {
|
||||
this.props.pageTitle("Dashboard")
|
||||
|
||||
this.props.request(cs.Routes.GetDashboarcStats, cs.MethodGet).then((resp) => {
|
||||
this.setState({ stats: resp.data.data })
|
||||
}).catch(e => {
|
||||
notification["error"]({ message: "Error", description: e.message })
|
||||
})
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<section className = "dashboard">
|
||||
<h1>Welcome</h1>
|
||||
|
||||
{ this.state.stats &&
|
||||
<div className="stats">
|
||||
<Row>
|
||||
<Col span={ 12 }>
|
||||
<h1></h1>
|
||||
</Col>
|
||||
</Row>
|
||||
</div>
|
||||
}
|
||||
</section>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -52,6 +52,7 @@ export const SubscriptionStatusUnsubscribed = "unsubscribed"
|
|||
|
||||
// API routes.
|
||||
export const Routes = {
|
||||
GetDashboarcStats: "/api/dashboard/stats",
|
||||
GetUsers: "/api/users",
|
||||
|
||||
GetLists: "/api/lists",
|
||||
|
|
1
main.go
1
main.go
|
@ -80,6 +80,7 @@ func init() {
|
|||
func registerHandlers(e *echo.Echo) {
|
||||
e.GET("/", handleIndexPage)
|
||||
e.GET("/api/config.js", handleGetConfigScript)
|
||||
e.GET("/api/dashboard/stats", handleGetDashboardStats)
|
||||
e.GET("/api/users", handleGetUsers)
|
||||
e.POST("/api/users", handleCreateUser)
|
||||
e.DELETE("/api/users/:id", handleDeleteUser)
|
||||
|
|
|
@ -8,6 +8,8 @@ import (
|
|||
|
||||
// Queries contains all prepared SQL queries.
|
||||
type Queries struct {
|
||||
GetDashboardStats *sqlx.Stmt `query:"get-dashboard-stats"`
|
||||
|
||||
UpsertSubscriber *sqlx.Stmt `query:"upsert-subscriber"`
|
||||
GetSubscriber *sqlx.Stmt `query:"get-subscriber"`
|
||||
GetSubscribersByEmails *sqlx.Stmt `query:"get-subscribers-by-emails"`
|
||||
|
|
53
queries.sql
53
queries.sql
|
@ -382,22 +382,37 @@ INSERT INTO link_clicks (campaign_id, subscriber_id, link_id)
|
|||
RETURNING (SELECT url FROM link);
|
||||
|
||||
|
||||
-- -- name: get-stats
|
||||
-- WITH lists AS (
|
||||
-- SELECT type, COUNT(id) AS num FROM lists GROUP BY type
|
||||
-- ),
|
||||
-- subs AS (
|
||||
-- SELECT status, COUNT(id) AS num FROM subscribers GROUP by status
|
||||
-- ),
|
||||
-- orphans AS (
|
||||
-- SELECT COUNT(id) FROM subscribers LEFT JOIN subscriber_lists ON (subscribers.id = subscriber_lists.subscriber_id)
|
||||
-- WHERE subscriber_lists.subscriber_id IS NULL
|
||||
-- ),
|
||||
-- camps AS (
|
||||
-- SELECT status, COUNT(id) AS num FROM campaigns GROUP by status
|
||||
-- )
|
||||
-- SELECT JSON_BUILD_OBJECT('lists', lists);
|
||||
-- row_to_json(t)
|
||||
-- from (
|
||||
-- select type, num from lists
|
||||
-- ) t,
|
||||
-- name: get-dashboard-stats
|
||||
WITH lists AS (
|
||||
SELECT JSON_AGG(ROW_TO_JSON(row)) FROM (SELECT type, COUNT(id) AS num FROM lists GROUP BY type) row
|
||||
),
|
||||
subs AS (
|
||||
SELECT JSON_AGG(ROW_TO_JSON(row)) FROM (SELECT status, COUNT(id) AS num FROM subscribers GROUP by status) row
|
||||
),
|
||||
orphans AS (
|
||||
SELECT COUNT(id) FROM subscribers LEFT JOIN subscriber_lists ON (subscribers.id = subscriber_lists.subscriber_id)
|
||||
WHERE subscriber_lists.subscriber_id IS NULL
|
||||
),
|
||||
camps AS (
|
||||
SELECT JSON_AGG(ROW_TO_JSON(row)) FROM (SELECT status, COUNT(id) AS num FROM campaigns GROUP by status) row
|
||||
),
|
||||
clicks AS (
|
||||
-- Clicks by day for the last 3 months
|
||||
SELECT JSON_AGG(ROW_TO_JSON(row))
|
||||
FROM (SELECT COUNT(*) AS num, created_at::DATE as date
|
||||
FROM link_clicks GROUP by date ORDER BY date DESC LIMIT 100
|
||||
) row
|
||||
),
|
||||
views AS (
|
||||
-- Views by day for the last 3 months
|
||||
SELECT JSON_AGG(ROW_TO_JSON(row))
|
||||
FROM (SELECT COUNT(*) AS views, created_at::DATE as date
|
||||
FROM campaign_views GROUP by date ORDER BY date DESC LIMIT 100
|
||||
) row
|
||||
)
|
||||
SELECT JSON_BUILD_OBJECT('lists', COALESCE((SELECT * FROM lists), '[]'),
|
||||
'subscribers', COALESCE((SELECT * FROM subs), '[]'),
|
||||
'orphan_subscribers', (SELECT * FROM orphans),
|
||||
'campaigns', COALESCE((SELECT * FROM camps), '[]'),
|
||||
'link_clicks', COALESCE((SELECT * FROM clicks), '[]'),
|
||||
'campaign_views', COALESCE((SELECT * FROM views), '[]')) AS stats;
|
||||
|
|
Loading…
Reference in New Issue