ahoy_captain 0.77 → 0.81

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (80) hide show
  1. checksums.yaml +4 -4
  2. data/app/assets/javascript/ahoy_captain/application.js +4 -4
  3. data/app/assets/javascript/ahoy_captain/controllers/application.js +5 -5
  4. data/app/assets/javascript/ahoy_captain/controllers/application_controller.js +9 -10
  5. data/app/assets/javascript/ahoy_captain/controllers/details_modal_controller.js +5 -5
  6. data/app/assets/javascript/ahoy_captain/controllers/dropdown_label_controller.js +2 -2
  7. data/app/assets/javascript/ahoy_captain/controllers/filter_controller.js +64 -58
  8. data/app/assets/javascript/ahoy_captain/controllers/filter_tag_controller.js +11 -8
  9. data/app/assets/javascript/ahoy_captain/controllers/funnel_chart_controller.js +77 -133
  10. data/app/assets/javascript/ahoy_captain/controllers/index.js +4 -3
  11. data/app/assets/javascript/ahoy_captain/controllers/interval_controller.js +10 -0
  12. data/app/assets/javascript/ahoy_captain/controllers/link_controller.js +22 -22
  13. data/app/assets/javascript/ahoy_captain/controllers/navigation_controller.js +10 -10
  14. data/app/assets/javascript/ahoy_captain/controllers/realtime_controller.js +7 -8
  15. data/app/controllers/ahoy_captain/application_controller.rb +17 -15
  16. data/app/controllers/ahoy_captain/campaigns_controller.rb +2 -10
  17. data/app/controllers/ahoy_captain/cities_controller.rb +2 -6
  18. data/app/controllers/ahoy_captain/countries_controller.rb +2 -6
  19. data/app/controllers/ahoy_captain/devices_controller.rb +3 -6
  20. data/app/controllers/ahoy_captain/entry_pages_controller.rb +2 -4
  21. data/app/controllers/ahoy_captain/exit_pages_controller.rb +3 -4
  22. data/app/controllers/ahoy_captain/exports_controller.rb +15 -0
  23. data/app/controllers/ahoy_captain/realtimes_controller.rb +1 -1
  24. data/app/controllers/ahoy_captain/regions_controller.rb +3 -7
  25. data/app/controllers/ahoy_captain/sources_controller.rb +2 -5
  26. data/app/controllers/ahoy_captain/stats/base_controller.rb +61 -0
  27. data/app/controllers/ahoy_captain/stats/bounce_rates_controller.rb +3 -2
  28. data/app/controllers/ahoy_captain/stats/total_pageviews_controller.rb +1 -1
  29. data/app/controllers/ahoy_captain/stats/total_visits_controller.rb +1 -1
  30. data/app/controllers/ahoy_captain/stats/unique_visitors_controller.rb +1 -1
  31. data/app/controllers/ahoy_captain/stats/views_per_visits_controller.rb +7 -6
  32. data/app/controllers/ahoy_captain/stats/visit_durations_controller.rb +1 -1
  33. data/app/controllers/ahoy_captain/top_pages_controller.rb +2 -8
  34. data/app/decorators/ahoy_captain/application_decorator.rb +27 -3
  35. data/app/decorators/ahoy_captain/campaign_decorator.rb +8 -0
  36. data/app/decorators/ahoy_captain/city_decorator.rb +12 -0
  37. data/app/decorators/ahoy_captain/country_decorator.rb +10 -0
  38. data/app/decorators/ahoy_captain/device_decorator.rb +13 -2
  39. data/app/decorators/ahoy_captain/page_decorator.rb +11 -0
  40. data/app/decorators/ahoy_captain/region_decorator.rb +16 -0
  41. data/app/decorators/ahoy_captain/source_decorator.rb +7 -0
  42. data/app/models/ahoy_captain/export.rb +48 -0
  43. data/app/presenters/ahoy_captain/dashboard_presenter.rb +24 -16
  44. data/app/presenters/ahoy_captain/funnel_presenter.rb +32 -29
  45. data/app/presenters/ahoy_captain/goals_presenter.rb +32 -23
  46. data/app/queries/ahoy_captain/application_query.rb +5 -2
  47. data/app/queries/ahoy_captain/campaign_query.rb +14 -0
  48. data/app/queries/ahoy_captain/city_query.rb +11 -0
  49. data/app/queries/ahoy_captain/country_query.rb +10 -0
  50. data/app/queries/ahoy_captain/device_query.rb +10 -0
  51. data/app/queries/ahoy_captain/entry_pages_query.rb +3 -2
  52. data/app/queries/ahoy_captain/event_query.rb +16 -1
  53. data/app/queries/ahoy_captain/exit_pages_query.rb +6 -4
  54. data/app/queries/ahoy_captain/region_query.rb +11 -0
  55. data/app/queries/ahoy_captain/source_query.rb +10 -0
  56. data/app/queries/ahoy_captain/stats/average_visit_duration_query.rb +3 -7
  57. data/app/queries/ahoy_captain/stats/bounce_rates_query.rb +9 -6
  58. data/app/queries/ahoy_captain/stats/total_visitors_query.rb +1 -1
  59. data/app/queries/ahoy_captain/stats/unique_visitors_query.rb +1 -1
  60. data/app/queries/ahoy_captain/stats/views_per_visit_query.rb +2 -2
  61. data/app/queries/ahoy_captain/stats/visit_duration_query.rb +4 -4
  62. data/app/queries/ahoy_captain/top_page_query.rb +13 -0
  63. data/app/queries/ahoy_captain/visit_query.rb +12 -12
  64. data/app/views/ahoy_captain/goals/index.html.erb +5 -5
  65. data/app/views/ahoy_captain/roots/show.html.erb +1 -1
  66. data/app/views/ahoy_captain/stats/base/index.html.erb +9 -1
  67. data/app/views/ahoy_captain/stats/show.html.erb +1 -1
  68. data/config/routes.rb +1 -0
  69. data/lib/ahoy_captain/ahoy/event_methods.rb +1 -1
  70. data/lib/ahoy_captain/configuration.rb +2 -2
  71. data/lib/ahoy_captain/engine.rb +1 -0
  72. data/lib/ahoy_captain/goals.rb +8 -4
  73. data/lib/ahoy_captain/period_collection.rb +1 -1
  74. data/lib/ahoy_captain/version.rb +1 -1
  75. data/lib/generators/ahoy_captain/migration_generator.rb +21 -0
  76. data/lib/generators/ahoy_captain/templates/config.rb.tt +18 -3
  77. data/lib/generators/ahoy_captain/templates/migration.rb.tt +7 -0
  78. metadata +42 -4
  79. data/app/models/ahoy_captain/current.rb +0 -9
  80. data/app/models/ahoy_captain/url_helpers.rb +0 -6
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: bc2218d617b8530eb24d10b8f8cf8da63364fece7069a6610fb621c80eda9450
4
- data.tar.gz: 6a9fd5388653ad8654062447fa564c81fc716c644d36cf00c64987c6d2627f3c
3
+ metadata.gz: be1cf9a1c2b19db594abc06b05d15085bc180fe720986671f9016f1d660feec5
4
+ data.tar.gz: e447a70fc694ae39ad8500661a5726eb73ecd4777a0c6d9b4f4bc7d1fdee0541
5
5
  SHA512:
6
- metadata.gz: 07c8677c2052fc124aa9e23338fcd75d8b963bf18d27a1f804e1e506e21bcc96e74803c0ae1cc051cae420fe572dd2144c1f2275e89ea20dba818e850a665da4
7
- data.tar.gz: e8657c6e04703d68d105912a6bdf75335aa98eb38311c76214828d1cf5e35f351c368584fd39cd5c61ef2215adf8dfffae42afff763590cd1021d6d58522846d
6
+ metadata.gz: cef676f367fd05a6b080db63106ab748347b32316f93245a1b1f1bed2bca99a1cb6df185b168721629eae537d14167ef494fc42f6ad1cb24d9eabbd1160ae0f1
7
+ data.tar.gz: af29e7be6843d2ceb9922b5e234219c6466e72dda75d992078cf6a95c20e52fe62f6e870f6eaef68d546d00beaf4179222b4bd504eee2666274e571bc158f2d3
@@ -1,4 +1,4 @@
1
- import "@hotwired/turbo-rails"
2
- import "controllers"
3
- import "chartkick"
4
- import "Chart.bundle"
1
+ import '@hotwired/turbo-rails';
2
+ import 'controllers';
3
+ import 'chartkick';
4
+ import 'Chart.bundle';
@@ -1,9 +1,9 @@
1
- import { Application } from "@hotwired/stimulus"
1
+ import { Application } from '@hotwired/stimulus';
2
2
 
3
- const application = Application.start()
3
+ const application = Application.start();
4
4
 
5
5
  // Configure Stimulus development experience
6
- application.debug = false
7
- window.Stimulus = application
6
+ application.debug = false;
7
+ window.Stimulus = application;
8
8
 
9
- export { application }
9
+ export { application };
@@ -1,14 +1,13 @@
1
- import {Controller} from "@hotwired/stimulus"
1
+ import { Controller } from '@hotwired/stimulus';
2
2
 
3
3
  export default class extends Controller {
4
- connect() {
5
- if(new URLSearchParams(window.location.search).get("period") === 'realtime') {
6
- this.element.querySelectorAll('turbo-frame').forEach(frame => {
7
- setInterval(() => {
8
- frame.reload()
9
- }, 1000 * 30);
10
- })
11
- }
4
+ connect() {
5
+ if (new URLSearchParams(window.location.search).get('period') === 'realtime') {
6
+ this.element.querySelectorAll('turbo-frame').forEach((frame) => {
7
+ setInterval(() => {
8
+ frame.reload();
9
+ }, 1000 * 30);
10
+ });
12
11
  }
13
-
12
+ }
14
13
  }
@@ -1,15 +1,15 @@
1
- import {Controller} from "@hotwired/stimulus"
1
+ import { Controller } from '@hotwired/stimulus';
2
2
 
3
3
  export default class extends Controller {
4
4
  static values = {
5
- target: String
6
- }
7
-
5
+ target: String,
6
+ };
7
+
8
8
  connect() {
9
9
  this.modal = document.querySelector('#detailsModal');
10
10
  this.turboFrame = document.querySelector('#detailsModal turbo-frame');
11
11
  }
12
-
12
+
13
13
  openModal(e) {
14
14
  e.preventDefault();
15
15
  this.modal.showModal();
@@ -1,7 +1,7 @@
1
- import {Controller} from "@hotwired/stimulus"
1
+ import { Controller } from '@hotwired/stimulus';
2
2
 
3
3
  export default class extends Controller {
4
- static targets = ["label", "close"]
4
+ static targets = ['label', 'close'];
5
5
 
6
6
  setLabel(event) {
7
7
  this.labelTarget.innerText = event.target.innerText;
@@ -1,12 +1,14 @@
1
- import {Controller} from "@hotwired/stimulus"
2
- import SlimSelect from 'slim-select'
1
+ import { Controller } from '@hotwired/stimulus';
2
+ import SlimSelect from 'slim-select';
3
3
 
4
4
  export default class extends Controller {
5
5
  static values = {
6
6
  url: String,
7
7
  column: String,
8
8
  };
9
- static targets = ["select", 'predicate'];
9
+
10
+ static targets = ['select', 'predicate'];
11
+
10
12
  connect() {
11
13
  this.selectTargets.forEach(async (target) => {
12
14
  const url = target.dataset.filterUrlValue;
@@ -20,29 +22,30 @@ export default class extends Controller {
20
22
  searchText: 'Sorry, no results found',
21
23
  searchPlaceholder: 'Type to populate results',
22
24
  placeholderText: `Search for ${target.dataset.filterColumnValue}`,
23
- searchHighlight: true
25
+ searchHighlight: true,
24
26
  },
25
27
  events: {
26
28
  beforeOpen: async () => {
27
- if (!this.#hasData(target) & !this.#hasSelections(target) ) {
29
+ if (!this.#hasData(target) && !this.#hasSelections(target)) {
28
30
  const data = await optionsSearch();
29
31
  target.slim.setData(data);
30
32
  }
31
33
  },
32
34
  search: async (search, currentData) => {
33
35
  const data = await optionsSearch(search);
34
- const filteredData = data.filter(item => !currentData.some((selectedItem) => selectedItem.text == item.text ))
36
+ // eslint-disable-next-line max-len
37
+ const filteredData = data.filter((item) => !currentData.some((selectedItem) => selectedItem.text === item.text));
35
38
  return filteredData;
36
39
  },
37
40
  beforeChange: this.#beforeChange(target, url),
38
- }
41
+ },
39
42
  });
40
- const json = JSON.parse(target.dataset.filterSelected)
41
- if(json.length) {
42
- select.setData(json.map(item => ({ "text": item, "value": item })))
43
- select.setSelected(json)
43
+ const json = JSON.parse(target.dataset.filterSelected);
44
+ if (json.length) {
45
+ select.setData(json.map((item) => ({ text: item, value: item })));
46
+ select.setSelected(json);
44
47
  }
45
- })
48
+ });
46
49
  }
47
50
 
48
51
  fetchOptions(url, target) {
@@ -51,42 +54,41 @@ export default class extends Controller {
51
54
  const response = await fetch(`${url}?${query.toString()}`);
52
55
  const data = await response.json();
53
56
  return data;
54
- }
57
+ };
55
58
  }
56
59
 
57
60
  #buildQueryFilters(search, target) {
58
61
  const query = new URLSearchParams(window.location.search);
59
- const otherFilters = this.selectTargets.filter(filter => filter != target);
60
- otherFilters.forEach(function(filter) {
61
- filter.slim.getSelected().forEach(val => {
62
- const filterName = `${filter.name}[]`
63
- if(query.has(filterName)) {
64
- console.log("has the filter")
65
- if(!query.get(`${filter.name}[]`).includes(val)) {
66
- query.append(filterName, value)
62
+ const otherFilters = this.selectTargets.filter((filter) => filter !== target);
63
+ otherFilters.forEach((filter) => {
64
+ filter.slim.getSelected().forEach((val) => {
65
+ const filterName = `${filter.name}[]`;
66
+ if (query.has(filterName)) {
67
+ if (!query.get(`${filter.name}[]`).includes(val)) {
68
+ query.append(filterName, val);
67
69
  }
68
70
  } else {
69
- query.append(filterName, value)
71
+ query.append(filterName, val);
70
72
  }
71
- })
73
+ });
72
74
  });
73
- query.set(`q[${target.dataset.filterColumnValue}_i_cont]`, search || "");
75
+ query.set(`q[${target.dataset.filterColumnValue}_i_cont]`, search || '');
74
76
  return query;
75
77
  }
76
78
 
77
- #beforeChange(target, url) {
79
+ #beforeChange(target) {
78
80
  return () => {
79
- const otherFilters = this.selectTargets.filter(filter => filter != target);
80
- otherFilters.forEach(async target => {
81
- if (this.#hasData(target)) {
82
- const selected = target.slim.getSelected()
83
- target.slim.setData(selected.map(text => ({ text })))
81
+ const otherFilters = this.selectTargets.filter((filter) => filter !== target);
82
+ otherFilters.forEach(async (filter) => {
83
+ if (this.#hasData(filter)) {
84
+ const selected = filter.slim.getSelected();
85
+ filter.slim.setData(selected.map((text) => ({ text })));
84
86
  // setting data triggers a slim render, so need to also set selected again
85
- target.slim.setSelected(selected)
87
+ filter.slim.setSelected(selected);
86
88
  }
87
- })
88
- return true;
89
- }
89
+ });
90
+ return true;
91
+ };
90
92
  }
91
93
 
92
94
  #hasData(target) {
@@ -98,48 +100,52 @@ export default class extends Controller {
98
100
  }
99
101
 
100
102
  #filtersForQuery() {
101
- const predicates = this.predicateTargets.map(el => ({name: el.name, column_predicate: el.value}));
102
- const filterValues = this.selectTargets.map(el => ({name: el.name, selections: el.slim.getSelected()}));
103
- const mergedData = predicates.map(predicate => {
104
- const matchingFilter = filterValues.find(filter => filter.name === predicate.name);
103
+ // eslint-disable-next-line max-len
104
+ const predicates = this.predicateTargets.map((el) => ({ name: el.name, column_predicate: el.value }));
105
+ // eslint-disable-next-line max-len
106
+ const filterValues = this.selectTargets.map((el) => ({ name: el.name, selections: el.slim.getSelected() }));
107
+ const mergedData = predicates.map((predicate) => {
108
+ const matchingFilter = filterValues.find((filter) => filter.name === predicate.name);
105
109
  if (matchingFilter.selections.length > 0) {
106
- return {...predicate, ...matchingFilter}
110
+ return { ...predicate, ...matchingFilter };
107
111
  }
108
- }).filter(el => el !== undefined);
112
+ return undefined;
113
+ }).filter((el) => el !== undefined);
109
114
  return mergedData;
110
115
  }
111
116
 
112
117
  resetFilters(event) {
113
- this.selectTargets.forEach(el => {
114
- el.slim.setSelected([])
115
- })
116
- this.applyFilters(event)
118
+ this.selectTargets.forEach((el) => {
119
+ el.slim.setSelected([]);
120
+ });
121
+ this.applyFilters(event);
117
122
  }
118
123
 
119
124
  applyFilters(e) {
120
125
  e.preventDefault();
121
126
  const searchParams = new URLSearchParams(window.location.search);
122
127
  const filters = this.#filtersForQuery();
123
- filters.forEach(filter => {
124
- const name = `q[${filter.column_predicate}][]`
125
- const selectedValues = searchParams.getAll(name)
126
- filter.selections.forEach(selection => {
127
- if(selectedValues) {
128
- if(!selectedValues.includes(selection)) {
129
- searchParams.append(name, selection)
128
+ filters.forEach((filter) => {
129
+ const name = `q[${filter.column_predicate}][]`;
130
+ const selectedValues = searchParams.getAll(name);
131
+ filter.selections.forEach((selection) => {
132
+ if (selectedValues) {
133
+ if (!selectedValues.includes(selection)) {
134
+ searchParams.append(name, selection);
130
135
  }
131
136
  } else {
132
- searchParams.append(name, selection)
137
+ searchParams.append(name, selection);
133
138
  }
134
- })
139
+ });
135
140
  });
136
- ['input[name="start_date"]', 'input[name="end_date"]'].forEach(selector => {
137
- const el = document.querySelector(selector)
138
- if(el.value.length) {
139
- searchParams.delete("period")
141
+ ['input[name="start_date"]', 'input[name="end_date"]'].forEach((selector) => {
142
+ const el = document.querySelector(selector);
143
+ if (el.value.length) {
144
+ searchParams.delete('period');
140
145
  }
141
146
  searchParams.set(el.name, el.value);
142
147
  });
143
- Turbo.visit(window.location.pathname.replace(/\/$/, "") + `?${searchParams.toString()}`)
148
+
149
+ Turbo.visit(`${window.location.pathname.replace(/\/$/, '')}?${searchParams.toString()}`);
144
150
  }
145
151
  }
@@ -1,17 +1,20 @@
1
- import {Controller} from "@hotwired/stimulus"
1
+ import { Controller } from '@hotwired/stimulus';
2
2
 
3
3
  export default class extends Controller {
4
- static targets = ["delete"];
4
+ static targets = ['delete'];
5
+
5
6
  static values = {
6
7
  columnPredicate: String,
7
- category: String
8
- }
8
+ category: String,
9
+ };
9
10
 
10
11
  remove(event) {
11
12
  event.preventDefault();
12
- this.dispatch('remove', {detail: {
13
- paramKey: `q[${this.columnPredicateValue}]`,
14
- paramValue: this.categoryValue
15
- }})
13
+ this.dispatch('remove', {
14
+ detail: {
15
+ paramKey: `q[${this.columnPredicateValue}]`,
16
+ paramValue: this.categoryValue,
17
+ },
18
+ });
16
19
  }
17
20
  }
@@ -1,143 +1,87 @@
1
- import {Controller} from "@hotwired/stimulus"
1
+ import { Controller } from '@hotwired/stimulus';
2
2
  import 'chartjs-plugin-datalabels';
3
- const THOUSAND = 1000
4
- const HUNDRED_THOUSAND = 100000
5
- const MILLION = 1000000
6
- const HUNDRED_MILLION = 100000000
7
- const BILLION = 1000000000
8
- const HUNDRED_BILLION = 100000000000
9
- const TRILLION = 1000000000000
10
-
11
- function numberFormatter(num) {
12
- if (num >= THOUSAND && num < MILLION) {
13
- const thousands = num / THOUSAND
14
- if (thousands === Math.floor(thousands) || num >= HUNDRED_THOUSAND) {
15
- return Math.floor(thousands) + 'k'
16
- } else {
17
- return (Math.floor(thousands * 10) / 10) + 'k'
18
- }
19
- } else if (num >= MILLION && num < BILLION) {
20
- const millions = num / MILLION
21
- if (millions === Math.floor(millions) || num >= HUNDRED_MILLION) {
22
- return Math.floor(millions) + 'M'
23
- } else {
24
- return (Math.floor(millions * 10) / 10) + 'M'
25
- }
26
- } else if (num >= BILLION && num < TRILLION) {
27
- const billions = num / BILLION
28
- if (billions === Math.floor(billions) || num >= HUNDRED_BILLION) {
29
- return Math.floor(billions) + 'B'
30
- } else {
31
- return (Math.floor(billions * 10) / 10) + 'B'
32
- }
33
- } else {
34
- return num
35
- }
36
- }
37
3
 
38
4
  export default class extends Controller {
39
- connect() {
40
- const funnel = JSON.parse(this.element.dataset.data);
41
-
42
- const getPalette = () => {
43
- return {
44
- dataLabelBackground: 'rgba(25, 30, 56, 0.97)',
45
- dataLabelTextColor: 'rgb(243, 244, 246)',
46
- visitorsBackground: 'rgb(99, 102, 241)',
47
- dropoffBackground: '#2F3949',
48
- dropoffStripes: 'rgb(25, 30, 56)',
49
- stepNameLegendColor: 'rgb(228, 228, 231)',
50
- visitorsLegendClass: 'bg-indigo-500',
51
- dropoffLegendClass: 'bg-gray-600',
52
- smallBarClass: 'bg-indigo-500'
53
- }
54
- }
55
-
5
+ connect() {
6
+ const funnel = JSON.parse(this.element.dataset.data);
56
7
 
8
+ const fontFamily = 'ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"';
9
+ const labels = funnel.steps.map((step) => step.name);
10
+ const stepData = funnel.steps.map((step) => step.total_events);
11
+ const dropOffData = funnel.steps.map((step) => step.drop_off * 100);
57
12
 
58
- var fontFamily = 'ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"'
59
- const calcBarThickness = (ctx) => {
60
- if (ctx.dataset.data.length <= 3) {
61
- return 160
62
- } else {
63
- return Math.floor(650 / ctx.dataset.data.length)
64
- }
65
- }
13
+ const data = {
14
+ labels,
15
+ datasets: [
16
+ {
17
+ label: 'Visitors',
18
+ data: stepData,
19
+ borderRadius: 4,
20
+ stack: 'Stack 0',
21
+ },
22
+ {
23
+ label: 'Dropoff',
24
+ data: dropOffData,
25
+ borderRadius: 4,
26
+ stack: 'Stack 0',
27
+ },
28
+ ],
29
+ };
66
30
 
67
- const labels = funnel.steps.map((step) => step.step)
68
- const stepData = funnel.steps.map((step) => step.count)
69
- const dropOffData = funnel.steps.map((step) => step.drop_off)
70
-
71
- const data = {
72
- labels: labels,
73
- datasets: [
74
- {
75
- label: 'Visitors',
76
- data: stepData,
77
- borderRadius: 4,
78
- stack: 'Stack 0',
79
- },
80
- {
81
- label: 'Dropoff',
82
- data: dropOffData,
83
- borderRadius: 4,
84
- stack: 'Stack 0',
85
- },
86
- ],
87
- }
88
-
89
- console.log(data)
90
- const config = {
91
- responsive:true,
92
- maintainAspectRatio: false,
93
- plugins: [ChartDataLabels],
94
- type: 'bar',
95
- data: data,
96
- options: {
97
- layout: {
98
- padding: 100,
99
- },
100
- barThickness: calcBarThickness,
101
- plugins: {
102
- legend: {
103
- display: false,
104
- },
105
- tooltip: {
106
- mode: 'index',
107
- intersect: true,
108
- position: 'average',
109
- },
110
- datalabels: {
111
- anchor: 'end',
112
- align: 'end',
113
- borderRadius: 4,
114
- padding: { top: 8, bottom: 8, right: 8, left: 8 },
115
- font: { size: 12, weight: 'normal', lineHeight: 1.6, family: fontFamily },
116
- textAlign: 'center',
117
- },
118
- },
119
- scales: {
120
- y: { display: false },
121
- x: {
122
- position: 'bottom',
123
- display: true,
124
- border: { display: false },
125
- grid: { drawBorder: false, display: false },
126
- ticks: {
127
- padding: 8,
128
- font: { weight: 'bold', family: fontFamily, size: 14 },
129
- color: 'rgb(228, 228, 231)'
130
- },
131
- },
132
- },
31
+ const config = {
32
+ responsive: true,
33
+ maintainAspectRatio: false,
34
+ plugins: [ChartDataLabels],
35
+ type: 'bar',
36
+ data,
37
+ options: {
38
+ layout: {
39
+ padding: 100,
40
+ },
41
+ plugins: {
42
+ legend: {
43
+ display: false,
44
+ },
45
+ tooltip: {
46
+ mode: 'index',
47
+ intersect: true,
48
+ position: 'average',
49
+ },
50
+ datalabels: {
51
+ anchor: 'end',
52
+ align: 'end',
53
+ borderRadius: 4,
54
+ padding: {
55
+ top: 8, bottom: 8, right: 8, left: 8,
56
+ },
57
+ font: {
58
+ size: 12, weight: 'normal', lineHeight: 1.6, family: fontFamily,
59
+ },
60
+ textAlign: 'center',
61
+ },
62
+ },
63
+ scales: {
64
+ y: { display: false },
65
+ x: {
66
+ position: 'bottom',
67
+ display: true,
68
+ border: { display: false },
69
+ grid: { drawBorder: false, display: false },
70
+ ticks: {
71
+ padding: 8,
72
+ font: { weight: 'bold', family: fontFamily, size: 14 },
73
+ color: 'rgb(228, 228, 231)',
133
74
  },
134
- }
75
+ },
76
+ },
77
+ },
78
+ };
135
79
 
136
- const visitorsData = []
80
+ const visitorsData = [];
137
81
 
138
- new Chart(
139
- this.element,
140
- config
141
- );
142
- }
82
+ new Chart(
83
+ this.element,
84
+ config,
85
+ );
86
+ }
143
87
  }
@@ -1,3 +1,4 @@
1
- import {application} from "controllers/application"
2
- import {eagerLoadControllersFrom} from "@hotwired/stimulus-loading"
3
- eagerLoadControllersFrom("controllers", application)
1
+ import { application } from 'controllers/application';
2
+ import { eagerLoadControllersFrom } from '@hotwired/stimulus-loading';
3
+
4
+ eagerLoadControllersFrom('controllers', application);
@@ -0,0 +1,10 @@
1
+ import { Controller } from '@hotwired/stimulus';
2
+
3
+ export default class extends Controller {
4
+ handleChange(event) {
5
+ const url = new URL(event.target.form.action);
6
+ const interval = event.target.value;
7
+ url.searchParams.set('interval', interval);
8
+ event.target.closest('turbo-frame').src = url.href;
9
+ }
10
+ }
@@ -1,43 +1,43 @@
1
- import { Controller } from "@hotwired/stimulus"
1
+ import { Controller } from '@hotwired/stimulus';
2
2
 
3
3
  export default class extends Controller {
4
- static targets = ["countriesLink", "top_pagesLink", "devicesLink", "top_sourcesLink"]
5
- static classes = ["countries", "top_pages", "devices", "top_sources"]
4
+ static targets = ['countriesLink', 'top_pagesLink', 'devicesLink', 'top_sourcesLink'];
5
+
6
+ static classes = ['countries', 'top_pages', 'devices', 'top_sources'];
6
7
 
7
8
  changeCountries(event) {
8
- console.log(...this.countriesClasses)
9
- event.currentTarget.classList.add(...this.countriesClasses)
9
+ event.currentTarget.classList.add(...this.countriesClasses);
10
10
  this.countriesLinkTargets.forEach((target) => {
11
- if (target != event.currentTarget) {
12
- target.classList.remove(...this.countriesClasses)
11
+ if (target !== event.currentTarget) {
12
+ target.classList.remove(...this.countriesClasses);
13
13
  }
14
- })
14
+ });
15
15
  }
16
16
 
17
17
  changeTopSources(event) {
18
- event.currentTarget.classList.add(...this.top_sourcesClasses)
18
+ event.currentTarget.classList.add(...this.top_sourcesClasses);
19
19
  this.top_sourcesLinkTargets.forEach((target) => {
20
- if (target != event.currentTarget) {
21
- target.classList.remove(...this.top_sourcesClasses)
20
+ if (target !== event.currentTarget) {
21
+ target.classList.remove(...this.top_sourcesClasses);
22
22
  }
23
- })
23
+ });
24
24
  }
25
-
25
+
26
26
  changeTopPages(event) {
27
- event.currentTarget.classList.add(...this.top_pagesClasses)
27
+ event.currentTarget.classList.add(...this.top_pagesClasses);
28
28
  this.top_pagesLinkTargets.forEach((target) => {
29
- if (target != event.currentTarget) {
30
- target.classList.remove(...this.top_pagesClasses)
29
+ if (target !== event.currentTarget) {
30
+ target.classList.remove(...this.top_pagesClasses);
31
31
  }
32
- })
32
+ });
33
33
  }
34
34
 
35
35
  changeDevices(event) {
36
- event.currentTarget.classList.add(...this.devicesClasses)
36
+ event.currentTarget.classList.add(...this.devicesClasses);
37
37
  this.devicesLinkTargets.forEach((target) => {
38
- if (target != event.currentTarget) {
39
- target.classList.remove(...this.devicesClasses)
38
+ if (target !== event.currentTarget) {
39
+ target.classList.remove(...this.devicesClasses);
40
40
  }
41
- })
41
+ });
42
42
  }
43
- }
43
+ }