ahoy_captain 0.76 → 0.81

Sign up to get free protection for your applications and to get access to all the features.
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: bdbe217ea7df263239572663d05816b79847f65f7c4ec9dfa120749c0f5b45d3
4
- data.tar.gz: 6176d55f8d2502a46891032ddfd51043fe4c4e7bd5efb9ac754d7e1576e4f866
3
+ metadata.gz: be1cf9a1c2b19db594abc06b05d15085bc180fe720986671f9016f1d660feec5
4
+ data.tar.gz: e447a70fc694ae39ad8500661a5726eb73ecd4777a0c6d9b4f4bc7d1fdee0541
5
5
  SHA512:
6
- metadata.gz: 6ec21ec3864bfb7b6d87fa65df0b1cb67879cd8cdd9b25c246bec2ccf9dac72d9858bfef19e9d0fb590dac11d5cb4dad491d81f5a81ca87532254051c2cb335b
7
- data.tar.gz: 9a0a6fba58462a0aaea1ee6d5536e21c7ca5c40c4332c89688ae99409e94495ee7215b31aa948ee9123b10f821b9405742f3f4946ee5c50b96f156476f6ae80a
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
+ }