only re-fetch data when needed

This commit is contained in:
Danny van Kooten 2018-10-05 15:13:48 +02:00
parent 876309ae59
commit 53c1702d0d
10 changed files with 92 additions and 44 deletions

View File

@ -100,14 +100,18 @@ class Chart extends Component {
}
}
componentWillReceiveProps(newProps) {
if(this.props == newProps) {
componentWillReceiveProps(newProps, newState) {
if(!this.paramsChanged(this.props, newProps)) {
return;
}
this.fetchData(newProps);
this.fetchData(newProps)
}
paramsChanged(o, n) {
return o.siteId != n.siteId || o.before != n.before && o.after != n.after;
}
@bind
prepareChart() {
let padding = { top: 12, right: 12, bottom: 24, left: 40 };
@ -215,10 +219,10 @@ class Chart extends Component {
fetchData(props) {
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) => {
// request finished; check if args changed in the meantime
if( props != this.props) {
// request finished; check if params changed in the meantime
if( this.paramsChanged(props, this.props)) {
return;
}

View File

@ -24,6 +24,18 @@ class Realtime extends Component {
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
setDocumentTitle() {
// update document title
@ -33,16 +45,12 @@ class Realtime extends Component {
@bind
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) => {
this.setState({ count: d })
this.setDocumentTitle();
})
.catch((e) => {
if(e.message == 401) {
this.props.onError();
}
})
}
render(props, state) {

View File

@ -17,24 +17,25 @@ class Sidebar extends Component {
}
componentWillReceiveProps(newProps, newState) {
if(newProps == this.props) {
if(!this.paramsChanged(this.props, newProps)) {
return;
}
this.fetchData(newProps);
}
paramsChanged(o, n) {
return o.siteId != n.siteId || o.before != n.before && o.after != n.after;
}
@bind
fetchData(props) {
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) => {
// 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;
}

View File

@ -43,8 +43,8 @@ class SiteSettings extends Component {
onSubmit(evt) {
evt.preventDefault();
let site = this.props.site;
Client.request('sites', {
let url = site.id > 0 ? `/sites/${site.id}` : `/sites`
Client.request(url, {
method: "POST",
data: {
id: site.id,
@ -77,18 +77,20 @@ class SiteSettings extends Component {
}
render(props, state) {
let newSite = props.site.id == 0;
// TODO: Render different form for new sites vs. existing sites
return (
<div class="modal-wrap" style={"display: " + ( props.visible ? '' : 'none')} onClick={this.maybeCloseModal}>
<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}>
<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} />
</fieldset>
<fieldset>
<fieldset style={newSite ? 'display: none;' : ''}>
<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 -->
<script>
@ -111,8 +113,8 @@ fathom('trackPageview');
<fieldset>
<div class="half">
<div class="submit"><button type="submit">Update site name</button></div>
<div class="delete"><a href="javascript:void(0);" onClick={this.deleteSite}>Delete site</a></div>
<div class="submit"><button type="submit">{newSite ? 'Create site' : 'Update site name'}</button></div>
{newSite ? '' : (<div class="delete"><a href="javascript:void(0);" onClick={this.deleteSite}>Delete site</a></div>)}
</div>
</fieldset>
</form>

View File

@ -21,22 +21,26 @@ class Table extends Component {
}
}
componentWillReceiveProps(newProps) {
if(this.props == newProps) {
return;
}
componentWillReceiveProps(newProps, newState) {
if(!this.paramsChanged(this.props, newProps)) {
return;
}
this.fetchData(newProps);
this.fetchData(newProps)
}
paramsChanged(o, n) {
return o.siteId != n.siteId || o.before != n.before && o.after != n.after;
}
@bind
fetchData(props) {
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) => {
// 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;
}
@ -47,7 +51,7 @@ class Table extends Component {
});
// 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) => {
this.setState({
total: d

View File

@ -111,7 +111,7 @@ class Dashboard extends Component {
<li class="logo"><a href="/">Fathom</a></li>
<SiteSwitcher sites={state.sites} selectedSite={state.site} onChange={this.changeSelectedSite} onAdd={this.openSiteSettings} showAdd={!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>
</nav>
</header>
@ -122,17 +122,17 @@ class Dashboard extends Component {
</nav>
<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="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 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 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>

View File

@ -56,6 +56,7 @@ func (agg *aggregator) Run() int {
trackingIDMap[s.TrackingID] = s.ID
}
// add each pageview to the various statistics we gather
for _, p := range pageviews {
// discard pageview if site tracking ID is unknown
@ -81,7 +82,6 @@ func (agg *aggregator) Run() int {
// referrer stats
if p.Referrer != "" {
hostname, pathname, err := parseUrlParts(p.Referrer)
if err != nil {
log.Error(err)

View File

@ -2,14 +2,21 @@ package api
import (
"encoding/json"
"log"
"math/rand"
"net/http"
"strconv"
"time"
"github.com/gorilla/mux"
"github.com/usefathom/fathom/pkg/models"
)
// seed rand pkg on program init
func init() {
rand.Seed(time.Now().UTC().UnixNano())
}
// GET /api/sites
func (api *API) GetSitesHandler(w http.ResponseWriter, r *http.Request) error {
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/{id}
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)
if err != nil {
return err
}
// generate tracking ID if this is a new site
if s.ID == 0 && s.TrackingID == "" {
s.TrackingID = generateTrackingID()
}
log.Printf("Site tracking ID: %s\n", s.TrackingID)
if err := api.database.SaveSite(s); err != nil {
return err
}

View File

@ -21,6 +21,7 @@ type Datastore interface {
// sites
GetSites() ([]*models.Site, error)
GetSite(id int64) (*models.Site, error)
SaveSite(s *models.Site) error
DeleteSite(s *models.Site) error

View File

@ -20,6 +20,13 @@ func (db *sqlstore) GetSites() ([]*models.Site, error) {
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)
func (db *sqlstore) SaveSite(s *models.Site) error {
if s.ID > 0 {