Fix merge conflicts

This commit is contained in:
Kailash Nadh 2018-11-06 15:59:13 +05:30
commit b333d05609
8 changed files with 198 additions and 34 deletions

View File

@ -3,8 +3,10 @@ package main
import ( import (
"bytes" "bytes"
"encoding/json" "encoding/json"
"fmt"
"net/http" "net/http"
"github.com/jmoiron/sqlx/types"
"github.com/labstack/echo" "github.com/labstack/echo"
) )
@ -15,10 +17,8 @@ type configScript struct {
Messengers []string `json:"messengers"` Messengers []string `json:"messengers"`
} }
// handleGetStats returns a collection of general statistics. type dashboardStats struct {
func handleGetStats(c echo.Context) error { Stats types.JSONText `db:"stats"`
app := c.Get("app").(*App)
return c.JSON(http.StatusOK, okResp{app.Runner.GetMessengerNames()})
} }
// handleGetConfigScript returns general configuration as a Javascript // handleGetConfigScript returns general configuration as a Javascript
@ -41,3 +41,18 @@ func handleGetConfigScript(c echo.Context) error {
j.Encode(out) j.Encode(out)
return c.Blob(http.StatusOK, "application/javascript", b.Bytes()) 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})
}

View File

@ -5,6 +5,7 @@
"dependencies": { "dependencies": {
"antd": "^3.6.5", "antd": "^3.6.5",
"axios": "^0.18.0", "axios": "^0.18.0",
"bizcharts": "^3.2.5-beta.4",
"dayjs": "^1.7.5", "dayjs": "^1.7.5",
"react": "^16.4.1", "react": "^16.4.1",
"react-app-rewire-less": "^2.1.3", "react-app-rewire-less": "^2.1.3",

View File

@ -1,14 +1,123 @@
import React from 'react'; import { Col, Row, notification, Card, Tooltip, Icon } from "antd"
import React from "react";
import { Chart, Axis, Geom, Tooltip as BizTooltip } from 'bizcharts';
import * as cs from "./constants"
class Dashboard extends React.PureComponent { class Dashboard extends React.PureComponent {
componentDidMount = () => { state = {
this.props.pageTitle("Dashboard") stats: null
} }
render() {
return ( campaignTypes = ["running", "finished", "paused", "draft", "scheduled", "cancelled"]
<h1>Welcome</h1>
); 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 })
})
}
orZero(v) {
return v ? v : 0
}
render() {
return (
<section className = "dashboard">
<h1>Welcome</h1>
<hr />
{ this.state.stats &&
<div className="stats">
<Row>
<Col span={ 16 }>
<Row gutter={ 24 }>
<Col span={ 8 }>
<Card title="Active subscribers" bordered={ false }>
<h1 className="count">{ this.orZero(this.state.stats.subscribers.enabled) }</h1>
</Card>
</Col>
<Col span={ 8 }>
<Card title="Blacklisted subscribers" bordered={ false }>
<h1 className="count">{ this.orZero(this.state.stats.subscribers.blacklisted) }</h1>
</Card>
</Col>
<Col span={ 8 }>
<Card title="Orphaned subscribers" bordered={ false }>
<h1 className="count">{ this.orZero(this.state.stats.orphan_subscribers) }</h1>
</Card>
</Col>
</Row>
</Col>
<Col span={ 6 } offset={ 2 }>
<Row gutter={ 24 }>
<Col span={ 12 }>
<Card title="Public lists" bordered={ false }>
<h1 className="count">{ this.orZero(this.state.stats.lists.public) }</h1>
</Card>
</Col>
<Col span={ 12 }>
<Card title="Private lists" bordered={ false }>
<h1 className="count">{ this.orZero(this.state.stats.lists.private) }</h1>
</Card>
</Col>
</Row>
</Col>
</Row>
<hr />
<Row>
<Col span={ 16 }>
<Row gutter={ 24 }>
<Col span={ 12 }>
<Card title="Campaign views (last 3 months)" bordered={ false }>
<h1 className="count">
{ this.state.stats.campaign_views.reduce((total, v) => total + v.count, 0) }
{' '}
views
</h1>
<Chart height={ 220 } padding={ [0, 0, 0, 0] } data={ this.state.stats.campaign_views } forceFit>
<BizTooltip crosshairs={{ type : "y" }} />
<Geom type="area" position="date*count" size={ 0 } color="#7f2aff" />
<Geom type='point' position="date*count" size={ 0 } />
</Chart>
</Card>
</Col>
<Col span={ 12 }>
<Card title="Link clicks (last 3 months)" bordered={ false }>
<h1 className="count">
{ this.state.stats.link_clicks.reduce((total, v) => total + v.count, 0) }
{' '}
clicks
</h1>
<Chart height={ 220 } padding={ [0, 0, 0, 0] } data={ this.state.stats.link_clicks } forceFit>
<BizTooltip crosshairs={{ type : "y" }} />
<Geom type="area" position="date*count" size={ 0 } color="#7f2aff" />
<Geom type='point' position="date*count" size={ 0 } />
</Chart>
</Card>
</Col>
</Row>
</Col>
<Col span={ 6 } offset={ 2 }>
<Card title="Campaigns" bordered={ false } className="campaign-counts">
{ this.campaignTypes.map((key, count) =>
<Row key={ `stats-campaigns-${ key }` }>
<Col span={ 18 }><h1 className="name">{ key }</h1></Col>
<Col span={ 6 }><h1 className="count">{ count }</h1></Col>
</Row>
)}
</Card>
</Col>
</Row>
</div>
}
</section>
);
}
} }
export default Dashboard; export default Dashboard;

View File

@ -52,6 +52,7 @@ export const SubscriptionStatusUnsubscribed = "unsubscribed"
// API routes. // API routes.
export const Routes = { export const Routes = {
GetDashboarcStats: "/api/dashboard/stats",
GetUsers: "/api/users", GetUsers: "/api/users",
GetLists: "/api/lists", GetLists: "/api/lists",

View File

@ -48,11 +48,14 @@ body {
} }
.content-body { .content-body {
background: #fff;
padding: 24px;
min-height: 90vh; min-height: 90vh;
} }
section.content {
padding: 24px;
background: #fff;
}
.logo { .logo {
padding: 30px; padding: 30px;
} }
@ -69,10 +72,19 @@ body {
width: 20px; width: 20px;
} }
.ant-card-head-title {
font-size: .85em !important;
color: #999 !important;
}
.broken { .broken {
margin: 100px; margin: 100px;
} }
.hidden {
display: none;
}
/* Form */ /* Form */
@ -90,6 +102,14 @@ td .ant-tag {
margin-top: 5px; margin-top: 5px;
} }
/* Dashboard */
.dashboard {
margin: 24px;
}
.dashboard .campaign-counts .name {
text-transform: capitalize;
}
/* Templates */ /* Templates */
.wysiwyg { .wysiwyg {
padding: 30px; padding: 30px;

View File

@ -80,6 +80,7 @@ func init() {
func registerHandlers(e *echo.Echo) { func registerHandlers(e *echo.Echo) {
e.GET("/", handleIndexPage) e.GET("/", handleIndexPage)
e.GET("/api/config.js", handleGetConfigScript) e.GET("/api/config.js", handleGetConfigScript)
e.GET("/api/dashboard/stats", handleGetDashboardStats)
e.GET("/api/users", handleGetUsers) e.GET("/api/users", handleGetUsers)
e.POST("/api/users", handleCreateUser) e.POST("/api/users", handleCreateUser)
e.DELETE("/api/users/:id", handleDeleteUser) e.DELETE("/api/users/:id", handleDeleteUser)

View File

@ -8,6 +8,8 @@ import (
// Queries contains all prepared SQL queries. // Queries contains all prepared SQL queries.
type Queries struct { type Queries struct {
GetDashboardStats *sqlx.Stmt `query:"get-dashboard-stats"`
InsertSubscriber *sqlx.Stmt `query:"insert-subscriber"` InsertSubscriber *sqlx.Stmt `query:"insert-subscriber"`
UpsertSubscriber *sqlx.Stmt `query:"upsert-subscriber"` UpsertSubscriber *sqlx.Stmt `query:"upsert-subscriber"`
BlacklistSubscriber *sqlx.Stmt `query:"blacklist-subscriber"` BlacklistSubscriber *sqlx.Stmt `query:"blacklist-subscriber"`

View File

@ -416,22 +416,37 @@ INSERT INTO link_clicks (campaign_id, subscriber_id, link_id)
RETURNING (SELECT url FROM link); RETURNING (SELECT url FROM link);
-- -- name: get-stats -- name: get-dashboard-stats
-- WITH lists AS ( WITH lists AS (
-- SELECT type, COUNT(id) AS num FROM lists GROUP BY type SELECT JSON_OBJECT_AGG(type, num) FROM (SELECT type, COUNT(id) AS num FROM lists GROUP BY type) row
-- ), ),
-- subs AS ( subs AS (
-- SELECT status, COUNT(id) AS num FROM subscribers GROUP by status SELECT JSON_OBJECT_AGG(status, num) FROM (SELECT status, COUNT(id) AS num FROM subscribers GROUP by status) row
-- ), ),
-- orphans AS ( orphans AS (
-- SELECT COUNT(id) FROM subscribers LEFT JOIN subscriber_lists ON (subscribers.id = subscriber_lists.subscriber_id) SELECT COUNT(id) FROM subscribers LEFT JOIN subscriber_lists ON (subscribers.id = subscriber_lists.subscriber_id)
-- WHERE subscriber_lists.subscriber_id IS NULL WHERE subscriber_lists.subscriber_id IS NULL
-- ), ),
-- camps AS ( camps AS (
-- SELECT status, COUNT(id) AS num FROM campaigns GROUP by status SELECT JSON_OBJECT_AGG(status, num) FROM (SELECT status, COUNT(id) AS num FROM campaigns GROUP by status) row
-- ) ),
-- SELECT JSON_BUILD_OBJECT('lists', lists); clicks AS (
-- row_to_json(t) -- Clicks by day for the last 3 months
-- from ( SELECT JSON_AGG(ROW_TO_JSON(row))
-- select type, num from lists FROM (SELECT COUNT(*) AS count, created_at::DATE as date
-- ) t, 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 count, 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;