diff --git a/appbase/app/app_analytics.go b/appbase/app/app_analytics.go new file mode 100644 index 00000000..f5490fc7 --- /dev/null +++ b/appbase/app/app_analytics.go @@ -0,0 +1,259 @@ +package app + +import ( + "encoding/json" + "fmt" + "io" + "os" + + "github.com/appbaseio/abc/appbase/common" + "github.com/olekukonko/tablewriter" +) + +type analyticsResults struct { + Count json.Number `json:"count"` + Key string `json:"key"` +} + +type analyticsVolumeResults struct { + Count json.Number `json:"count"` + Key json.Number `json:"key"` + DateAsStr string `json:"key_as_string"` +} + +type overviewAnalyticsBody struct { + NoResultSearches []analyticsResults `json:"noResultSearches"` + PopularSearches []analyticsResults `json:"popularSearches"` + SearchVolume []analyticsVolumeResults `json:"searchVolume"` +} + +//ShowOverview ....... +func ShowOverview(body io.ReadCloser) error { + + var res overviewAnalyticsBody + dec := json.NewDecoder(body) + err := dec.Decode(&res) + if err != nil { + fmt.Println(err) + return err + } + + // Display the overview results + fmt.Println("Analytics(Overview) Results:") + + // Display NoResultSearches results + noResultTable := tablewriter.NewWriter(os.Stdout) + noResultTable.SetHeader([]string{"Count", "Key"}) + + for _, elements := range res.NoResultSearches { + noResultTable.Append([]string{common.JSONNumberToString(elements.Count), elements.Key}) + } + noResultTable.SetAlignment(tablewriter.ALIGN_CENTER) + fmt.Println("No Result Searches") + noResultTable.Render() + + // Display PopularSearches results + popularSearchesTable := tablewriter.NewWriter(os.Stdout) + popularSearchesTable.SetHeader([]string{"Count", "Key"}) + + for _, elements := range res.PopularSearches { + popularSearchesTable.Append([]string{common.JSONNumberToString(elements.Count), elements.Key}) + } + popularSearchesTable.SetAlignment(tablewriter.ALIGN_CENTER) + fmt.Println("No Result Searches") + popularSearchesTable.Render() + + // Display SearcheVolume results + searchVolumeTable := tablewriter.NewWriter(os.Stdout) + searchVolumeTable.SetHeader([]string{"Count", "Key", "Date-As-Str"}) + for _, elements := range res.SearchVolume { + searchVolumeTable.Append([]string{common.JSONNumberToString(elements.Count), common.JSONNumberToString(elements.Key), elements.DateAsStr}) + } + searchVolumeTable.SetAlignment(tablewriter.ALIGN_CENTER) + fmt.Println("Search Volume Results") + searchVolumeTable.Render() + + return nil +} + +type latencyResults struct { + Count json.Number `json:"count"` + Key json.Number `json:"key"` +} + +type latency struct { + Latency []latencyResults `json:"latency"` +} + +//ShowLatency ....... +func ShowLatency(body io.ReadCloser) error { + var res latency + dec := json.NewDecoder(body) + err := dec.Decode(&res) + + if err != nil { + fmt.Println(err) + return err + } + + table := tablewriter.NewWriter(os.Stdout) + table.SetHeader([]string{"Count", "Key"}) + + for _, elements := range res.Latency { + table.Append([]string{common.JSONNumberToString(elements.Count), common.JSONNumberToString(elements.Key)}) + } + table.SetAlignment(tablewriter.ALIGN_CENTER) + fmt.Println("Analytics(Latency) Results:") + table.Render() + + return nil +} + +type geoIP struct { + GeoIP []analyticsResults `json:"aggrByCountry"` +} + +//ShowGeoIP ....... +func ShowGeoIP(body io.ReadCloser) error { + var res geoIP + dec := json.NewDecoder(body) + err := dec.Decode(&res) + if err != nil { + fmt.Println(err) + return err + } + + table := tablewriter.NewWriter(os.Stdout) + table.SetHeader([]string{"Count", "Key"}) + + for _, elements := range res.GeoIP { + table.Append([]string{common.JSONNumberToString(elements.Count), elements.Key}) + } + table.SetAlignment(tablewriter.ALIGN_CENTER) + fmt.Println("Analytics(GeoIP) Results:") + table.Render() + + return nil +} + +type analyticsPopularResults struct { + Count json.Number `json:"count"` + Key string `json:"key"` + Source string `json:"source"` +} + +type popularResults struct { + PopularResults []analyticsPopularResults `json:"popularResults"` +} + +//ShowPopularResults ....... +func ShowPopularResults(body io.ReadCloser) error { + var res popularResults + dec := json.NewDecoder(body) + err := dec.Decode(&res) + if err != nil { + fmt.Println(err) + return err + } + + // TODO refine output + + table := tablewriter.NewWriter(os.Stdout) + table.SetHeader([]string{"Count", "Key", "Source"}) + + for _, elements := range res.PopularResults { + table.Append([]string{common.JSONNumberToString(elements.Count), elements.Key, elements.Source}) + } + table.SetAlignment(tablewriter.ALIGN_CENTER) + fmt.Println("Analytics(Popular) Results:") + table.Render() + + return nil +} + +type popularSearches struct { + Results []analyticsResults `json:"popularSearches"` +} + +//ShowPopularSearches ....... +func ShowPopularSearches(body io.ReadCloser) error { + var res popularSearches + dec := json.NewDecoder(body) + err := dec.Decode(&res) + if err != nil { + fmt.Println(err) + return err + } + + table := tablewriter.NewWriter(os.Stdout) + table.SetHeader([]string{"Count", "Key"}) + + for _, elements := range res.Results { + table.Append([]string{common.JSONNumberToString(elements.Count), elements.Key}) + } + table.SetAlignment(tablewriter.ALIGN_CENTER) + fmt.Println("Analytics Popular Searches:") + table.Render() + + return nil +} + +type noResultSearches struct { + Results []analyticsResults `json:"noResultSearches"` +} + +//ShowNoResultSearches ....... +func ShowNoResultSearches(body io.ReadCloser) error { + var res noResultSearches + dec := json.NewDecoder(body) + err := dec.Decode(&res) + if err != nil { + fmt.Println(err) + return err + } + + table := tablewriter.NewWriter(os.Stdout) + table.SetHeader([]string{"Count", "Key"}) + + for _, elements := range res.Results { + table.Append([]string{common.JSONNumberToString(elements.Count), elements.Key}) + } + table.SetAlignment(tablewriter.ALIGN_CENTER) + fmt.Println("Analytics No Result Searches:") + table.Render() + + return nil +} + +type analyticsPopularFilters struct { + Count json.Number `json:"count"` + Key string `json:"key"` + Value string `json:"value"` +} + +type popularFilters struct { + Results []analyticsPopularFilters `json:"popularFilters"` +} + +//ShowPopularFilters ....... +func ShowPopularFilters(body io.ReadCloser) error { + var res popularFilters + dec := json.NewDecoder(body) + err := dec.Decode(&res) + if err != nil { + fmt.Println(err) + return err + } + + table := tablewriter.NewWriter(os.Stdout) + table.SetHeader([]string{"Count", "Key", "Value"}) + + for _, elements := range res.Results { + table.Append([]string{common.JSONNumberToString(elements.Count), elements.Key, elements.Value}) + } + table.SetAlignment(tablewriter.ALIGN_CENTER) + fmt.Println("Analytics Popular Filters:") + table.Render() + + return nil +} diff --git a/appbase/app/app_details.go b/appbase/app/app_details.go index 3bcb8df9..7a7f27f5 100644 --- a/appbase/app/app_details.go +++ b/appbase/app/app_details.go @@ -3,14 +3,15 @@ package app import ( "encoding/json" "fmt" - "github.com/appbaseio/abc/appbase/common" - "github.com/appbaseio/abc/appbase/session" - "github.com/appbaseio/abc/appbase/spinner" - "github.com/olekukonko/tablewriter" "net/http" "os" "strconv" "time" + + "github.com/appbaseio/abc/appbase/common" + "github.com/appbaseio/abc/appbase/session" + "github.com/appbaseio/abc/appbase/spinner" + "github.com/olekukonko/tablewriter" ) // Permission represents an app permission object @@ -95,6 +96,42 @@ func ShowAppMetrics(app string) error { return nil } +// ShowAppAnalytics fetches analytics for an app +func ShowAppAnalytics(app string, endpoint string) error { + spinner.StartText("Fetching app analytics") + defer spinner.Stop() + // show analytics + fmt.Println() + req, err := http.NewRequest("GET", common.AccAPIURL+"/analytics/"+app+"/"+endpoint, nil) + if err != nil { + return err + } + resp, err := session.SendRequest(req) + if err != nil { + return err + } + spinner.Stop() + + switch endpoint { + case "latency": + ShowLatency(resp.Body) + case "geoip": + ShowGeoIP(resp.Body) + case "overview": + ShowOverview(resp.Body) + case "popularresults": + ShowPopularResults(resp.Body) + case "popularsearches": + ShowPopularSearches(resp.Body) + case "popularfilters": + ShowPopularFilters(resp.Body) + case "noresultsearches": + ShowNoResultSearches(resp.Body) + } + + return nil +} + // ShowAppPerms ... func ShowAppPerms(app string) error { spinner.StartText("Fetching app credentials") diff --git a/cmd/abc/appbase_app.go b/cmd/abc/appbase_app.go index dc13599f..6d269451 100644 --- a/cmd/abc/appbase_app.go +++ b/cmd/abc/appbase_app.go @@ -2,6 +2,7 @@ package main import ( "fmt" + "github.com/appbaseio/abc/appbase/app" "github.com/appbaseio/abc/appbase/common" ) @@ -32,8 +33,10 @@ func runApps(args []string) error { // runApp runs `app` command func runApp(args []string) error { flagset := baseFlagSet("app") - basicUsage := "abc app [-c|--creds] [-m|--metrics] [--data-view] [ID|Appname]" + basicUsage := "abc app [-c|--creds] [-m|--metrics] [--data-view] [-a| --analytics] [ID|Appname]" flagset.Usage = usageFor(flagset, basicUsage) + analytics := flagset.BoolP("analytics", "a", false, "show app analytics") + analyticsEndpoint := flagset.String("endpoint", "overview", "the analytics endpoint to be queried") creds := flagset.BoolP("creds", "c", false, "show app credentials") metrics := flagset.BoolP("metrics", "m", false, "show app metrics") dataView := flagset.Bool("data-view", false, "open app data view using Dejavu") @@ -48,6 +51,8 @@ func runApp(args []string) error { return app.OpenAppDataView(args[0]) } else if *queryView { return app.OpenAppQueryView(args[0]) + } else if *analytics { + return app.ShowAppAnalytics(args[0], *analyticsEndpoint) } return app.ShowAppDetails(args[0], *creds, *metrics) } diff --git a/docs/appbase/app.md b/docs/appbase/app.md index fb73a608..2275124d 100644 --- a/docs/appbase/app.md +++ b/docs/appbase/app.md @@ -19,7 +19,7 @@ The above commands will only give basic details of the app such as its name, ID, If you want more details such as credentials and metrics, you can pass in defined switches. The full version of `app` command looks like- ```sh -abc app [-c|--creds] [-m|--metrics] [ID|Appname] +abc app [-c|--creds] [-m|--metrics] [-a| --analytics] [ID|Appname] ``` #### Example @@ -48,3 +48,62 @@ Records: 42 ``` ⭐️ It shows API calls and number of records created on a per day basis (just like the graph you see on dashboard.appbase.io). + + +### Accessing app analytics (only for paid users) + +Paid users can access analytics data for a particular app. Currently the following analytics endpoints are supported: +- overview +- noresultsearches +- popularresults +- popularsearches +- popularfilters +- geoip +- latency + +#### Example + +By default the command will ping the overview endpoint + +```sh +> abc app -a MyCoolApp + +Analytics(Overview) Results: +No Result Searches ++-------+------+ +| COUNT | KEY | ++-------+------+ +| 1 | blah | +| 1 | gurr | ++-------+------+ +No Result Searches ++-------+--------------+ +| COUNT | KEY | ++-------+--------------+ +| 1 | gru | +| ... | ... | +| 1 | wonder woman | +| 1 | wonderland | ++-------+--------------+ +Search Volume Results ++-------+---------------+---------------------+ +| COUNT | KEY | DATE-AS-STR | ++-------+---------------+---------------------+ +| 7 | 1540512000000 | 2018/10/26 00:00:00 | ++-------+---------------+---------------------+ +``` + +To ping other analytics endpoints the `endpoint` flag can be used. For example + +```sh +> abc app -a --endpoint=latency MyCoolApp + +Analytics(Latency) Results: ++-------+-----+ +| COUNT | KEY | ++-------+-----+ +| 2 | 0 | +| .. | .. | +| 0 | 10 | ++-------+-----+ +``` \ No newline at end of file