mirror of
https://github.com/status-im/fathom.git
synced 2025-03-01 03:20:27 +00:00
only re-fetch data when needed
This commit is contained in:
parent
876309ae59
commit
53c1702d0d
16
assets/src/js/components/Chart.js
vendored
16
assets/src/js/components/Chart.js
vendored
@ -100,14 +100,18 @@ class Chart extends Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillReceiveProps(newProps) {
|
componentWillReceiveProps(newProps, newState) {
|
||||||
if(this.props == newProps) {
|
if(!this.paramsChanged(this.props, newProps)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.fetchData(newProps);
|
this.fetchData(newProps)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
paramsChanged(o, n) {
|
||||||
|
return o.siteId != n.siteId || o.before != n.before && o.after != n.after;
|
||||||
|
}
|
||||||
|
|
||||||
@bind
|
@bind
|
||||||
prepareChart() {
|
prepareChart() {
|
||||||
let padding = { top: 12, right: 12, bottom: 24, left: 40 };
|
let padding = { top: 12, right: 12, bottom: 24, left: 40 };
|
||||||
@ -215,10 +219,10 @@ class Chart extends Component {
|
|||||||
fetchData(props) {
|
fetchData(props) {
|
||||||
this.setState({ loading: true })
|
this.setState({ loading: true })
|
||||||
|
|
||||||
Client.request(`/sites/${props.site.id}/stats/site/groupby/day?before=${props.before}&after=${props.after}`)
|
Client.request(`/sites/${props.siteId}/stats/site/groupby/day?before=${props.before}&after=${props.after}`)
|
||||||
.then((d) => {
|
.then((d) => {
|
||||||
// request finished; check if args changed in the meantime
|
// request finished; check if params changed in the meantime
|
||||||
if( props != this.props) {
|
if( this.paramsChanged(props, this.props)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -24,6 +24,18 @@ class Realtime extends Component {
|
|||||||
clearInterval(this.interval);
|
clearInterval(this.interval);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
componentWillReceiveProps(newProps, newState) {
|
||||||
|
if(!this.paramsChanged(this.props, newProps)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.fetchData()
|
||||||
|
}
|
||||||
|
|
||||||
|
paramsChanged(o, n) {
|
||||||
|
return o.siteId != n.siteId || o.before != n.before && o.after != n.after;
|
||||||
|
}
|
||||||
|
|
||||||
@bind
|
@bind
|
||||||
setDocumentTitle() {
|
setDocumentTitle() {
|
||||||
// update document title
|
// update document title
|
||||||
@ -33,16 +45,12 @@ class Realtime extends Component {
|
|||||||
|
|
||||||
@bind
|
@bind
|
||||||
fetchData() {
|
fetchData() {
|
||||||
Client.request(`/sites/${this.props.site.id}/stats/site/realtime`)
|
let url = `/sites/${this.props.siteId}/stats/site/realtime`
|
||||||
|
Client.request(url)
|
||||||
.then((d) => {
|
.then((d) => {
|
||||||
this.setState({ count: d })
|
this.setState({ count: d })
|
||||||
this.setDocumentTitle();
|
this.setDocumentTitle();
|
||||||
})
|
})
|
||||||
.catch((e) => {
|
|
||||||
if(e.message == 401) {
|
|
||||||
this.props.onError();
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
render(props, state) {
|
render(props, state) {
|
||||||
|
@ -17,24 +17,25 @@ class Sidebar extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
componentWillReceiveProps(newProps, newState) {
|
componentWillReceiveProps(newProps, newState) {
|
||||||
if(newProps == this.props) {
|
if(!this.paramsChanged(this.props, newProps)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.fetchData(newProps);
|
this.fetchData(newProps);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
paramsChanged(o, n) {
|
||||||
|
return o.siteId != n.siteId || o.before != n.before && o.after != n.after;
|
||||||
|
}
|
||||||
|
|
||||||
@bind
|
@bind
|
||||||
fetchData(props) {
|
fetchData(props) {
|
||||||
this.setState({ loading: true })
|
this.setState({ loading: true })
|
||||||
|
|
||||||
Client.request(`/sites/${props.site.id}/stats/site?before=${props.before}&after=${props.after}`)
|
Client.request(`/sites/${props.siteId}/stats/site?before=${props.before}&after=${props.after}`)
|
||||||
.then((data) => {
|
.then((data) => {
|
||||||
// request finished; check if timestamp range is still the one user wants to see
|
// request finished; check if timestamp range is still the one user wants to see
|
||||||
if( props != this.props ) {
|
if(this.paramsChanged(props, this.props)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -43,8 +43,8 @@ class SiteSettings extends Component {
|
|||||||
onSubmit(evt) {
|
onSubmit(evt) {
|
||||||
evt.preventDefault();
|
evt.preventDefault();
|
||||||
let site = this.props.site;
|
let site = this.props.site;
|
||||||
|
let url = site.id > 0 ? `/sites/${site.id}` : `/sites`
|
||||||
Client.request('sites', {
|
Client.request(url, {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
data: {
|
data: {
|
||||||
id: site.id,
|
id: site.id,
|
||||||
@ -77,18 +77,20 @@ class SiteSettings extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render(props, state) {
|
render(props, state) {
|
||||||
|
let newSite = props.site.id == 0;
|
||||||
|
|
||||||
// TODO: Render different form for new sites vs. existing sites
|
// TODO: Render different form for new sites vs. existing sites
|
||||||
return (
|
return (
|
||||||
<div class="modal-wrap" style={"display: " + ( props.visible ? '' : 'none')} onClick={this.maybeCloseModal}>
|
<div class="modal-wrap" style={"display: " + ( props.visible ? '' : 'none')} onClick={this.maybeCloseModal}>
|
||||||
<div class="modal">
|
<div class="modal">
|
||||||
<p>Update your site name or get your tracking code</p>
|
<p>{newSite ? 'Add a new site to track with Fathom' : 'Update your site name or get your tracking code'}</p>
|
||||||
<form onSubmit={this.onSubmit}>
|
<form onSubmit={this.onSubmit}>
|
||||||
<fieldset>
|
<fieldset>
|
||||||
<label for="site-name">Site Name</label>
|
<label for="site-name">Site name</label>
|
||||||
<input type="text" name="site-name" id="site-name" placeholder="" onChange={this.updateSiteName} value={props.site.name} />
|
<input type="text" name="site-name" id="site-name" placeholder="" onChange={this.updateSiteName} value={props.site.name} />
|
||||||
</fieldset>
|
</fieldset>
|
||||||
|
|
||||||
<fieldset>
|
<fieldset style={newSite ? 'display: none;' : ''}>
|
||||||
<label>Add this code to your website <small class="right">(site ID = {props.site.trackingId})</small></label>
|
<label>Add this code to your website <small class="right">(site ID = {props.site.trackingId})</small></label>
|
||||||
<textarea ref={(el) => { this.textarea = el }} onClick={this.onTextareaClick} readonly="readonly">{`<!-- Fathom - simple website analytics - https://github.com/usefathom/fathom -->
|
<textarea ref={(el) => { this.textarea = el }} onClick={this.onTextareaClick} readonly="readonly">{`<!-- Fathom - simple website analytics - https://github.com/usefathom/fathom -->
|
||||||
<script>
|
<script>
|
||||||
@ -111,8 +113,8 @@ fathom('trackPageview');
|
|||||||
|
|
||||||
<fieldset>
|
<fieldset>
|
||||||
<div class="half">
|
<div class="half">
|
||||||
<div class="submit"><button type="submit">Update site name</button></div>
|
<div class="submit"><button type="submit">{newSite ? 'Create site' : 'Update site name'}</button></div>
|
||||||
<div class="delete"><a href="javascript:void(0);" onClick={this.deleteSite}>Delete site</a></div>
|
{newSite ? '' : (<div class="delete"><a href="javascript:void(0);" onClick={this.deleteSite}>Delete site</a></div>)}
|
||||||
</div>
|
</div>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
</form>
|
</form>
|
||||||
|
@ -21,22 +21,26 @@ class Table extends Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillReceiveProps(newProps) {
|
componentWillReceiveProps(newProps, newState) {
|
||||||
if(this.props == newProps) {
|
if(!this.paramsChanged(this.props, newProps)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.fetchData(newProps);
|
this.fetchData(newProps)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
paramsChanged(o, n) {
|
||||||
|
return o.siteId != n.siteId || o.before != n.before && o.after != n.after;
|
||||||
|
}
|
||||||
|
|
||||||
@bind
|
@bind
|
||||||
fetchData(props) {
|
fetchData(props) {
|
||||||
this.setState({ loading: true });
|
this.setState({ loading: true });
|
||||||
|
|
||||||
Client.request(`/sites/${props.site.id}/${props.endpoint}?before=${props.before}&after=${props.after}&limit=${this.state.limit}`)
|
Client.request(`/sites/${props.siteId}/${props.endpoint}?before=${props.before}&after=${props.after}&limit=${this.state.limit}`)
|
||||||
.then((d) => {
|
.then((d) => {
|
||||||
// request finished; check if timestamp range is still the one user wants to see
|
// request finished; check if timestamp range is still the one user wants to see
|
||||||
if( this.props != props ) {
|
if( this.paramsChanged(props, this.props) ) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -47,7 +51,7 @@ class Table extends Component {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// fetch totals too
|
// fetch totals too
|
||||||
Client.request(`/sites/${props.site.id}/${props.endpoint}/pageviews?before=${props.before}&after=${props.after}`)
|
Client.request(`/sites/${props.siteId}/${props.endpoint}/pageviews?before=${props.before}&after=${props.after}`)
|
||||||
.then((d) => {
|
.then((d) => {
|
||||||
this.setState({
|
this.setState({
|
||||||
total: d
|
total: d
|
||||||
|
@ -111,7 +111,7 @@ class Dashboard extends Component {
|
|||||||
<li class="logo"><a href="/">Fathom</a></li>
|
<li class="logo"><a href="/">Fathom</a></li>
|
||||||
<SiteSwitcher sites={state.sites} selectedSite={state.site} onChange={this.changeSelectedSite} onAdd={this.openSiteSettings} showAdd={!state.isPublic}/>
|
<SiteSwitcher sites={state.sites} selectedSite={state.site} onChange={this.changeSelectedSite} onAdd={this.openSiteSettings} showAdd={!state.isPublic}/>
|
||||||
<Gearwheel onClick={this.openSiteSettings} visible={!state.isPublic} />
|
<Gearwheel onClick={this.openSiteSettings} visible={!state.isPublic} />
|
||||||
<li class="visitors"><Realtime site={state.site} /></li>
|
<li class="visitors"><Realtime siteId={state.site.id} /></li>
|
||||||
</ul>
|
</ul>
|
||||||
</nav>
|
</nav>
|
||||||
</header>
|
</header>
|
||||||
@ -122,17 +122,17 @@ class Dashboard extends Component {
|
|||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
<div class="boxes">
|
<div class="boxes">
|
||||||
<Sidebar site={state.site} before={state.before} after={state.after} />
|
<Sidebar siteId={state.site.id} before={state.before} after={state.after} />
|
||||||
|
|
||||||
<div class="boxes-col">
|
<div class="boxes-col">
|
||||||
<div class="box box-graph">
|
<div class="box box-graph">
|
||||||
<Chart site={state.site} before={state.before} after={state.after} />
|
<Chart siteId={state.site.id} before={state.before} after={state.after} />
|
||||||
</div>
|
</div>
|
||||||
<div class="box box-pages">
|
<div class="box box-pages">
|
||||||
<Table endpoint="stats/pages" headers={["Top pages", "Views", "Uniques"]} site={state.site} before={state.before} after={state.after} />
|
<Table endpoint="stats/pages" headers={["Top pages", "Views", "Uniques"]} siteId={state.site.id} before={state.before} after={state.after} />
|
||||||
</div>
|
</div>
|
||||||
<div class="box box-referrers">
|
<div class="box box-referrers">
|
||||||
<Table endpoint="stats/referrers" headers={["Top referrers", "Views", "Uniques"]} site={state.site} before={state.before} after={state.after} showHostname="true" />
|
<Table endpoint="stats/referrers" headers={["Top referrers", "Views", "Uniques"]} siteId={state.site.id} before={state.before} after={state.after} showHostname="true" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -56,6 +56,7 @@ func (agg *aggregator) Run() int {
|
|||||||
trackingIDMap[s.TrackingID] = s.ID
|
trackingIDMap[s.TrackingID] = s.ID
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// add each pageview to the various statistics we gather
|
||||||
for _, p := range pageviews {
|
for _, p := range pageviews {
|
||||||
|
|
||||||
// discard pageview if site tracking ID is unknown
|
// discard pageview if site tracking ID is unknown
|
||||||
@ -81,7 +82,6 @@ func (agg *aggregator) Run() int {
|
|||||||
|
|
||||||
// referrer stats
|
// referrer stats
|
||||||
if p.Referrer != "" {
|
if p.Referrer != "" {
|
||||||
|
|
||||||
hostname, pathname, err := parseUrlParts(p.Referrer)
|
hostname, pathname, err := parseUrlParts(p.Referrer)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error(err)
|
log.Error(err)
|
||||||
|
@ -2,14 +2,21 @@ package api
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"log"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/gorilla/mux"
|
"github.com/gorilla/mux"
|
||||||
"github.com/usefathom/fathom/pkg/models"
|
"github.com/usefathom/fathom/pkg/models"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// seed rand pkg on program init
|
||||||
|
func init() {
|
||||||
|
rand.Seed(time.Now().UTC().UnixNano())
|
||||||
|
}
|
||||||
|
|
||||||
// GET /api/sites
|
// GET /api/sites
|
||||||
func (api *API) GetSitesHandler(w http.ResponseWriter, r *http.Request) error {
|
func (api *API) GetSitesHandler(w http.ResponseWriter, r *http.Request) error {
|
||||||
result, err := api.database.GetSites()
|
result, err := api.database.GetSites()
|
||||||
@ -22,17 +29,31 @@ func (api *API) GetSitesHandler(w http.ResponseWriter, r *http.Request) error {
|
|||||||
// POST /api/sites
|
// POST /api/sites
|
||||||
// POST /api/sites/{id}
|
// POST /api/sites/{id}
|
||||||
func (api *API) SaveSiteHandler(w http.ResponseWriter, r *http.Request) error {
|
func (api *API) SaveSiteHandler(w http.ResponseWriter, r *http.Request) error {
|
||||||
s := &models.Site{}
|
var s *models.Site
|
||||||
|
vars := mux.Vars(r)
|
||||||
|
sid, ok := vars["id"]
|
||||||
|
if ok {
|
||||||
|
id, err := strconv.ParseInt(sid, 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
s, err = api.database.GetSite(id)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
s = &models.Site{
|
||||||
|
TrackingID: generateTrackingID(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
err := json.NewDecoder(r.Body).Decode(s)
|
err := json.NewDecoder(r.Body).Decode(s)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// generate tracking ID if this is a new site
|
log.Printf("Site tracking ID: %s\n", s.TrackingID)
|
||||||
if s.ID == 0 && s.TrackingID == "" {
|
|
||||||
s.TrackingID = generateTrackingID()
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := api.database.SaveSite(s); err != nil {
|
if err := api.database.SaveSite(s); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -21,6 +21,7 @@ type Datastore interface {
|
|||||||
|
|
||||||
// sites
|
// sites
|
||||||
GetSites() ([]*models.Site, error)
|
GetSites() ([]*models.Site, error)
|
||||||
|
GetSite(id int64) (*models.Site, error)
|
||||||
SaveSite(s *models.Site) error
|
SaveSite(s *models.Site) error
|
||||||
DeleteSite(s *models.Site) error
|
DeleteSite(s *models.Site) error
|
||||||
|
|
||||||
|
@ -20,6 +20,13 @@ func (db *sqlstore) GetSites() ([]*models.Site, error) {
|
|||||||
return results, err
|
return results, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (db *sqlstore) GetSite(id int64) (*models.Site, error) {
|
||||||
|
s := &models.Site{}
|
||||||
|
query := db.Rebind("SELECT * FROM sites WHERE id = ?")
|
||||||
|
err := db.Get(s, query, id)
|
||||||
|
return s, mapError(err)
|
||||||
|
}
|
||||||
|
|
||||||
// SaveSite saves the website in the database (inserts or updates)
|
// SaveSite saves the website in the database (inserts or updates)
|
||||||
func (db *sqlstore) SaveSite(s *models.Site) error {
|
func (db *sqlstore) SaveSite(s *models.Site) error {
|
||||||
if s.ID > 0 {
|
if s.ID > 0 {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user