diff --git a/app/components/stats.hbs b/app/components/stats.hbs
new file mode 100644
index 0000000..b6ad9fc
--- /dev/null
+++ b/app/components/stats.hbs
@@ -0,0 +1,123 @@
+
+ Stats
+
+ {{#if this.removedToday.size}}
+
+ 🎉🌅 Rules removed today
+
+
+ {{#each this.removedToday as |rule|}}
+ -
+ {{rule}}
+
+ {{/each}}
+
+
+ {{/if}}
+
+ {{#if this.removedThisWeek.size}}
+
+ 🎉🗓️ Rules removed this week
+
+
+ {{#each this.removedThisWeek as |rule|}}
+ -
+ {{rule}}
+
+ {{/each}}
+
+
+ {{/if}}
+
+ {{#if this.improvedToday}}
+
+ 📉🌅 Biggest improvement since yesterday:
+
+ {{this.improvedToday.rule}}
+ which has removed
+ {{absolute this.improvedToday.value}}
+ files
+
+ {{/if}}
+
+ {{#if this.improvedThisWeek}}
+
+ 📉🗓️ Biggest improvment this week:
+
+
+
+ {{this.improvedThisWeek.rule}}
+
+ which has removed
+ {{absolute this.improvedThisWeek.value}}
+ files
+
+
+ {{/if}}
+
+
+
+ {{#if this.newToday.size}}
+
+ 🆕🌅 New rules added since yesterday:
+
+
+ {{#each this.newToday as |rule|}}
+ - {{rule}}
+ {{/each}}
+
+
+ {{/if}}
+
+ {{#if this.newThisWeek.size}}
+
+ 🆕🗓️ New rules added this week:
+
+
+ {{#each this.newThisWeek as |rule|}}
+ - {{rule}}
+ {{/each}}
+
+
+ {{/if}}
+
+ {{#if this.mostAddedToday}}
+
+ 📈🌅 Most new files since yesterday:
+
+
+ {{this.mostAddedToday.rule}}
+
+
+ which added
+ {{this.mostAddedToday.value}}
+ files
+
+
+ {{/if}}
+
+ {{#if this.mostAddedThisWeek}}
+
+ 📈🗓️ Most new files this week:
+
+
+ {{this.mostAddedThisWeek.rule}}
+
+
+ which added
+ {{this.mostAddedThisWeek.value}}
+ files
+
+
+ {{/if}}
+
+
\ No newline at end of file
diff --git a/app/components/stats.js b/app/components/stats.js
new file mode 100644
index 0000000..1b964d9
--- /dev/null
+++ b/app/components/stats.js
@@ -0,0 +1,114 @@
+import Component from '@glimmer/component';
+
+export default class Stats extends Component {
+ get improvedToday() {
+ let biggest;
+
+ for (let [rule, value] of Object.entries(this.args.data?.today?.changed)) {
+ if (value > 0) {
+ continue;
+ }
+ // removing trumps improvement
+ if (this.removedToday.has(rule)) {
+ continue;
+ }
+ // remember smaller numbers are bigger "improvmeents";
+ if (!biggest || value < biggest.value) {
+ biggest = { rule, value };
+ }
+ }
+ return biggest;
+ }
+
+ get improvedThisWeek() {
+ let biggest;
+
+ for (let [rule, value] of Object.entries(
+ this.args.data?.thisWeek?.changed,
+ )) {
+ if (
+ value > 0 ||
+ rule === this.improvedToday?.rule ||
+ value >= this.improvedToday?.value
+ ) {
+ continue;
+ }
+
+ // removing trumps improvement
+ if (this.removedThisWeek.has(rule)) {
+ continue;
+ }
+
+ // remember smaller numbers are bigger "improvmeents";
+ if (!biggest || value < biggest.value) {
+ biggest = { rule, value };
+ }
+ }
+
+ return biggest;
+ }
+
+ get mostAddedToday() {
+ let biggest;
+
+ for (let [rule, value] of Object.entries(this.args.data?.today?.changed)) {
+ if (value < 0) {
+ continue;
+ }
+ // new trumps added
+ if (this.newToday.has(rule)) {
+ continue;
+ }
+
+ if (!biggest || value > biggest.value) {
+ biggest = { rule, value };
+ }
+ }
+ return biggest;
+ }
+
+ get mostAddedThisWeek() {
+ let biggest;
+
+ for (let [rule, value] of Object.entries(
+ this.args.data?.thisWeek?.changed,
+ )) {
+ if (
+ value < 0 ||
+ rule === this.mostAddedToday?.rule ||
+ value <= this.mostAddedToday?.value
+ ) {
+ continue;
+ }
+
+ // new trumps added
+ if (this.newToday.has(rule) || this.newThisWeek.has(rule)) {
+ continue;
+ }
+
+ if (!biggest || value > biggest.value) {
+ biggest = { rule, value };
+ }
+ }
+
+ return biggest;
+ }
+
+ get newToday() {
+ return new Set(this.args.data?.today?.added);
+ }
+
+ get newThisWeek() {
+ return new Set(this.args.data.thisWeek.added).difference(this.newToday);
+ }
+
+ get removedToday() {
+ return new Set(this.args.data?.today?.removed);
+ }
+
+ get removedThisWeek() {
+ return new Set(this.args.data.thisWeek.removed).difference(
+ this.removedToday,
+ );
+ }
+}
diff --git a/app/helpers/absolute.js b/app/helpers/absolute.js
new file mode 100644
index 0000000..a6b9387
--- /dev/null
+++ b/app/helpers/absolute.js
@@ -0,0 +1,5 @@
+import { helper } from '@ember/component/helper';
+
+export default helper(function absolute(positional /*, named*/) {
+ return Math.abs(positional);
+});
diff --git a/app/routes/application.js b/app/routes/application.js
index 40025b5..b73e6a2 100644
--- a/app/routes/application.js
+++ b/app/routes/application.js
@@ -1,30 +1,98 @@
/* eslint-disable prettier/prettier */
import Route from '@ember/routing/route';
import fetch from 'fetch';
+import { Temporal } from 'temporal-polyfill'
import env from 'lint-to-the-future/config/environment';
import timeSeries from 'lint-to-the-future/utils/time-series';
+function lengthOrValuOrZero(data) {
+ if(!data) {
+ return 0;
+ }
+ return data.length ?? data;
+}
+
+function compareData(data, today, past) {
+ const timeSeriesData = timeSeries(data);
+
+ let changed = {}
+ let removed = [];
+ let added = [];
+
+ for(let rule in timeSeriesData) {
+ let diff = lengthOrValuOrZero(timeSeriesData[rule][today]) - lengthOrValuOrZero(timeSeriesData[rule][past]);
+
+ if (diff !== 0 ) {
+ changed[rule] = diff;
+ }
+
+ if (!timeSeriesData[rule][today] && timeSeriesData[rule][past]) {
+ removed.push(rule);
+ }
+
+ if (timeSeriesData[rule][today] && !timeSeriesData[rule][past]) {
+ added.push(rule);
+ }
+ }
+ return {
+ changed,
+ removed,
+ added,
+ }
+}
+
export default class ApplicationRoute extends Route {
async model() {
let data = await (await fetch(`${env.rootURL}data.json`)).json();
+ let allDates = Object.keys(data).sort((a, b) => b.localeCompare(a))
+
let timeSeriesData = timeSeries(data);
- let highestDate;
+ const globalHighestDate = allDates[0];
+ const stats = {}
+
+ const today = Temporal.PlainDate.from(globalHighestDate);
+
+ // there is at least another date in the data
+ if (allDates[1]) {
+ const yesterday = Temporal.PlainDate.from(allDates[1]);
+ if (yesterday.until(today).days === 1) {
+ // there was a yesterday
+ stats.today = compareData({
+ [globalHighestDate]: data[globalHighestDate],
+ [allDates[1]]: data[allDates[1]]
+ }, globalHighestDate, allDates[1])
+ }
+
+ let lastWeek = yesterday;
+
+ for (let i = 2; i < allDates.length; i++) {
+ const currentDate = Temporal.PlainDate.from(allDates[i]);
+ if (currentDate.until(today).days > 7) {
+ break;
+ }
- for (const rule in timeSeriesData) {
- for (const date in timeSeriesData[rule]) {
- if(!highestDate || highestDate < date) {
- highestDate = date;
+ if (currentDate.until(lastWeek).days > 0) {
+ lastWeek = Temporal.PlainDate.from(currentDate)
}
}
+
+ // if we have a date that is bigger than yesterday but not bigger than 7 days ago
+ if (lastWeek !== yesterday) {
+ stats.thisWeek = compareData({
+ [globalHighestDate]: data[globalHighestDate],
+ [lastWeek.toString()]: data[lastWeek.toString()]
+ }, globalHighestDate, lastWeek.toString())
+ }
}
return {
data: timeSeriesData,
- highestDate,
+ highestDate: globalHighestDate,
+ stats,
}
}
}
diff --git a/app/styles/app.css b/app/styles/app.css
index 04c0ca9..2a387bf 100644
--- a/app/styles/app.css
+++ b/app/styles/app.css
@@ -20,10 +20,28 @@ a {
color: black;
}
+.stats-wrapper {
+ display: grid;
+ grid-template-columns: repeat(auto-fill, minmax(min(300px, 100%), 1fr));
+ grid-gap: 20px;
+ margin-bottom: 20px;
+}
+
+.stat-card {
+ background-color: white;
+ border-radius: var(--chart-border-radius);
+ box-shadow: 0 4px 10px rgba(0, 0, 0, 0.1);
+ padding: 20px;
+}
+
+.stat-card ul {
+ padding-left: inherit;
+}
+
.graphs {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(min(600px, 100%), 1fr));
- grid-gap: 20px
+ grid-gap: 20px;
}
.lttf-chart {
@@ -102,9 +120,11 @@ details > .graphs {
a {
color: #FBBF24;
}
- .lttf-chart {
+
+ .lttf-chart, .stat-card {
background-color: #16213E;
}
+
.chart-container .axis {
fill: white !important;
}
@@ -129,3 +149,9 @@ footer {
.frappe-chart .y-markers {
display: none;
}
+
+/* helper classes */
+.my-2 {
+ margin-top: .5em;
+ margin-bottom: .5em;
+}
diff --git a/app/templates/index.hbs b/app/templates/index.hbs
index b33ca6e..5123358 100644
--- a/app/templates/index.hbs
+++ b/app/templates/index.hbs
@@ -1,3 +1,5 @@
+
+
{{#each-in this.rules.rulesToComplete as |key value|}}
diff --git a/package.json b/package.json
index b7b093e..e3c5462 100644
--- a/package.json
+++ b/package.json
@@ -37,7 +37,8 @@
"commander": "^9.4.1",
"fs-extra": "^7.0.1",
"import-cwd": "^3.0.0",
- "node-fetch": "^2.6.0"
+ "node-fetch": "^2.6.0",
+ "temporal-polyfill": "^0.2.5"
},
"devDependencies": {
"@babel/core": "^7.25.2",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 5ca9e9d..8c29d04 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -68,6 +68,7 @@ specifiers:
stylelint-config-standard: ^34.0.0
stylelint-prettier: ^4.1.0
temp: ^0.9.4
+ temporal-polyfill: ^0.2.5
tracked-built-ins: ^3.3.0
webpack: ^5.93.0
@@ -76,6 +77,7 @@ dependencies:
fs-extra: 7.0.1
import-cwd: 3.0.0
node-fetch: 2.7.0
+ temporal-polyfill: 0.2.5
devDependencies:
'@babel/core': 7.25.2
@@ -12863,6 +12865,16 @@ packages:
rimraf: 2.6.3
dev: true
+ /temporal-polyfill/0.2.5:
+ resolution: {integrity: sha512-ye47xp8Cb0nDguAhrrDS1JT1SzwEV9e26sSsrWzVu+yPZ7LzceEcH0i2gci9jWfOfSCCgM3Qv5nOYShVUUFUXA==}
+ dependencies:
+ temporal-spec: 0.2.4
+ dev: false
+
+ /temporal-spec/0.2.4:
+ resolution: {integrity: sha512-lDMFv4nKQrSjlkHKAlHVqKrBG4DyFfa9F74cmBZ3Iy3ed8yvWnlWSIdi4IKfSqwmazAohBNwiN64qGx4y5Q3IQ==}
+ dev: false
+
/terser-webpack-plugin/5.3.10_webpack@5.95.0:
resolution: {integrity: sha512-BKFPWlPDndPs+NGGCr1U59t0XScL5317Y0UReNrHaw9/FwhPENlq6bfgs+4yPfyP51vqC1bQ4rp1EfXW5ZSH9w==}
engines: {node: '>= 10.13.0'}
diff --git a/server/mocks/data.json b/server/mocks/data.json
index f51bf81..e8c8dd0 100644
--- a/server/mocks/data.json
+++ b/server/mocks/data.json
@@ -126,12 +126,27 @@
"ember/no-empty-glimmer-component-classes": ["addon/components/es-link-card.js"],
"ember/no-classic-components": ["addon/components/es-header-navbar-link.js"],
"ember/no-classic-classes": ["addon/components/es-header-navbar-link.js", "addon/services/navbar.js", "tests/dummy/app/components/link-to.js"],
- "ember/require-tagless-components": ["addon/components/es-header-navbar-link.js", "tests/dummy/app/components/link-to.js"],
+ "ember/require-tagless-components": 10,
"ember/no-get": ["addon/components/es-header-navbar-link.js"],
"ember/no-component-lifecycle-hooks": ["addon/components/es-header-navbar-link.js"],
"ember/require-super-in-lifecycle-hooks": ["addon/components/es-header-navbar-link.js"]
}
},
+ "2021-04-09": {
+ "lint-to-the-future-ember-template": {
+ "require-valid-alt-text": 2,
+ "no-class:strange-name": 1
+ },
+ "lint-to-the-future-eslint": {
+ "prettier/prettier": 24,
+ "ember/no-classic-components": 1,
+ "ember/no-classic-classes": 6,
+ "ember/require-tagless-components": 2,
+ "ember/no-get": 1,
+ "ember/no-component-lifecycle-hooks": 1,
+ "ember/require-super-in-lifecycle-hooks": 8
+ }
+ },
"2021-04-10": {
"lint-to-the-future-ember-template": {
"require-valid-alt-text": ["addon/templates/components/es-card-content.hbs"],
@@ -139,12 +154,12 @@
},
"lint-to-the-future-eslint": {
"prettier/prettier": ["addon/components/es-button.js", "addon/components/es-card-content.js", "addon/components/es-card.js", "addon/components/es-footer-contributions.js", "addon/components/es-footer-help.js", "addon/components/es-footer-info.js", "addon/components/es-footer-statement.js", "addon/components/es-footer.js", "addon/components/es-header-navbar-link.js", "addon/components/es-link-card.js", "addon/components/es-note.js", "addon/constants/es-footer.js", "addon/services/navbar.js", "app/components/es-button.js", "app/components/es-card-content.js", "app/components/es-card.js", "app/components/es-footer.js", "app/components/es-header.js", "app/components/es-link-card.js", "app/components/es-note.js", "ember-cli-build.js", "index.js", "tests/acceptance/visual-regression-test-test.js", "tests/dummy/app/components/link-to.js", "tests/dummy/app/helpers/increment.js", "tests/dummy/config/environment.js", "tests/integration/components/es-button-test.js", "tests/integration/components/es-card-test.js", "tests/integration/components/es-footer-test.js", "tests/integration/components/es-header-test.js", "tests/integration/components/es-note-test.js", "tests/integration/components/es-progress-bar-test.js"],
- "ember/no-classic-components": ["addon/components/es-header-navbar-link.js"],
- "ember/no-classic-classes": ["addon/components/es-header-navbar-link.js", "addon/services/navbar.js", "tests/dummy/app/components/link-to.js"],
+ "ember/no-classic-components": ["addon/components/es-header-navbar-link.js", "addon/components/es-button.js", "addon/components/es-card-content.js", "addon/components/es-card.js", "addon/components/es-footer-contributions.js", "addon/components/es-footer-help.js"],
+ "ember/no-classic-classes": ["addon/components/es-header-navbar-link.js", "addon/services/navbar.js", "tests/dummy/app/components/link-to.js", "addon/components/es-button.js", "addon/components/es-card-content.js", "addon/components/es-card.js", "addon/components/es-footer-contributions.js", "addon/components/es-footer-help.js", "addon/components/es-footer-info.js", "addon/components/es-footer-statement.js", "addon/components/es-footer.js", "addon/components/es-header-navbar-link.js"],
"ember/require-tagless-components": ["addon/components/es-header-navbar-link.js", "tests/dummy/app/components/link-to.js"],
+ "completely-new-rule/only-today-ever": ["addon/components/es-header-navbar-link.js", "tests/dummy/app/components/link-to.js"],
"ember/no-get": ["addon/components/es-header-navbar-link.js"],
- "ember/no-component-lifecycle-hooks": ["addon/components/es-header-navbar-link.js"],
- "ember/require-super-in-lifecycle-hooks": ["addon/components/es-header-navbar-link.js"]
+ "ember/no-component-lifecycle-hooks": ["addon/components/es-header-navbar-link.js"]
}
}
}
diff --git a/tests/acceptance/basic-test.js b/tests/acceptance/basic-test.js
index 37fb7d6..a13ce04 100644
--- a/tests/acceptance/basic-test.js
+++ b/tests/acceptance/basic-test.js
@@ -10,7 +10,7 @@ module('Acceptance | basic', function(hooks) {
await visit('/');
assert.equal(currentURL(), '/');
- assert.dom('[data-test-chart]').exists({count: 10})
+ assert.dom('[data-test-chart]').exists({count: 11})
// await this.pauseTest();
assert.dom('[data-test-chart="lint-to-the-future-ember-template:require-valid-alt-text"] [data-test-time-button="monthly"]').isChecked();
@@ -29,6 +29,6 @@ module('Acceptance | basic', function(hooks) {
// top level charts
assert.dom('.ember-application > div > [data-test-chart]').exists({count: 9})
- assert.dom('[data-test-completed-rules] [data-test-chart]').exists({count: 1});
+ assert.dom('[data-test-completed-rules] [data-test-chart]').exists({count: 2});
})
});