diff --git a/assets/javascripts/discourse/components/data-explorer-bar-chart.js.es6 b/assets/javascripts/discourse/components/data-explorer-bar-chart.js.es6
new file mode 100644
index 0000000..a6fa2c8
--- /dev/null
+++ b/assets/javascripts/discourse/components/data-explorer-bar-chart.js.es6
@@ -0,0 +1,97 @@
+import loadScript from "discourse/lib/load-script";
+import { default as computed } from "discourse-common/utils/decorators";
+import themeColor from "../lib/themeColor";
+
+export default Ember.Component.extend({
+ barsColor: themeColor("--tertiary"),
+ barsHoverColor: themeColor("--tertiary-high"),
+ gridColor: themeColor("--primary-low"),
+ labelsColor: themeColor("--primary-medium"),
+ chart: null,
+
+ @computed("data", "options")
+ config(data, options) {
+ return {
+ type: "bar",
+ data,
+ options,
+ };
+ },
+
+ @computed("labels.[]", "values.[]", "datasetName")
+ data(labels, values, datasetName) {
+ return {
+ labels,
+ datasets: [
+ {
+ label: datasetName,
+ data: values,
+ backgroundColor: this.barsColor,
+ hoverBackgroundColor: this.barsHoverColor,
+ },
+ ],
+ };
+ },
+
+ @computed
+ options() {
+ return {
+ scales: {
+ legend: {
+ labels: {
+ fontColor: this.labelsColor,
+ },
+ },
+ xAxes: [
+ {
+ gridLines: {
+ color: this.gridColor,
+ zeroLineColor: this.gridColor,
+ },
+ ticks: {
+ fontColor: this.labelsColor,
+ },
+ },
+ ],
+ yAxes: [
+ {
+ gridLines: {
+ color: this.gridColor,
+ zeroLineColor: this.gridColor,
+ },
+ ticks: {
+ beginAtZero: true,
+ fontColor: this.labelsColor,
+ },
+ },
+ ],
+ },
+ };
+ },
+
+ didInsertElement() {
+ this._super(...arguments);
+ this._initChart();
+ },
+
+ didUpdate() {
+ this._super(...arguments);
+ this.chart.data = this.data;
+ this.chart.update();
+ },
+
+ willDestroyElement() {
+ this._super(...arguments);
+ this.chart.destroy();
+ },
+
+ _initChart() {
+ loadScript("/javascripts/Chart.min.js").then(() => {
+ const canvas = this.element.querySelector("canvas");
+ const context = canvas.getContext("2d");
+ const config = this.config;
+ // eslint-disable-next-line
+ this.chart = new Chart(context, config);
+ });
+ },
+});
diff --git a/assets/javascripts/discourse/components/query-result.js.es6 b/assets/javascripts/discourse/components/query-result.js.es6
index 5d15edc..a352377 100644
--- a/assets/javascripts/discourse/components/query-result.js.es6
+++ b/assets/javascripts/discourse/components/query-result.js.es6
@@ -32,6 +32,9 @@ const QueryResultComponent = Ember.Component.extend({
params: Ember.computed.alias("content.params"),
explainText: Ember.computed.alias("content.explain"),
hasExplain: Ember.computed.notEmpty("content.explain"),
+ chartDatasetName: Ember.computed.reads("columnDispNames.1"),
+ chartValues: Ember.computed.mapBy("content.rows", "1"),
+ showChart: false,
init() {
this._super(...arguments);
@@ -139,6 +142,51 @@ const QueryResultComponent = Ember.Component.extend({
return transformedRelTable(groups);
},
+ @computed(
+ "rows.[]",
+ "content.colrender.[]",
+ "content.result_count",
+ "colCount"
+ )
+ canShowChart(rows, colRender, resultCount, colCount) {
+ const hasTwoColumns = colCount === 2;
+ const secondColumnContainsNumber =
+ resultCount > 0 && typeof rows[0][1] === "number";
+ const secondColumnContainsId = colRender[1];
+
+ return (
+ hasTwoColumns && secondColumnContainsNumber && !secondColumnContainsId
+ );
+ },
+
+ @computed("content.rows.[]", "content.colrender.[]")
+ chartLabels(rows, colRender) {
+ const labelSelectors = {
+ user: (user) => user.username,
+ badge: (badge) => badge.name,
+ topic: (topic) => topic.title,
+ group: (group) => group.name,
+ category: (category) => category.name,
+ };
+
+ const relationName = colRender[0];
+
+ if (relationName) {
+ const lookupFunc = this[`lookup${relationName.capitalize()}`];
+ const labelSelector = labelSelectors[relationName];
+
+ if (lookupFunc && labelSelector) {
+ return rows.map((r) => {
+ const relation = lookupFunc.call(this, r[0]);
+ const label = labelSelector(relation);
+ return this._cutChartLabel(label);
+ });
+ }
+ }
+
+ return rows.map((r) => this._cutChartLabel(r[0]));
+ },
+
lookupUser(id) {
return this.transformedUserTable[id];
},
@@ -211,6 +259,15 @@ const QueryResultComponent = Ember.Component.extend({
});
},
+ _cutChartLabel(label) {
+ const labelString = label.toString();
+ if (labelString.length > 25) {
+ return `${labelString.substring(0, 25)}...`;
+ } else {
+ return labelString;
+ }
+ },
+
actions: {
downloadResultJson() {
this.downloadResult("json");
@@ -218,6 +275,12 @@ const QueryResultComponent = Ember.Component.extend({
downloadResultCsv() {
this.downloadResult("csv");
},
+ showChart() {
+ this.set("showChart", true);
+ },
+ showTable() {
+ this.set("showChart", false);
+ },
},
});
diff --git a/assets/javascripts/discourse/components/query-row-content.js.es6 b/assets/javascripts/discourse/components/query-row-content.js.es6
index 82cde2e..da30def 100644
--- a/assets/javascripts/discourse/components/query-row-content.js.es6
+++ b/assets/javascripts/discourse/components/query-row-content.js.es6
@@ -80,7 +80,7 @@ const QueryRowContentComponent = Ember.Component.extend({
const lookupFunc = parentView[`lookup${t.name.capitalize()}`];
if (lookupFunc) {
- ctx[t.name] = parentView[`lookup${t.name.capitalize()}`](id);
+ ctx[t.name] = lookupFunc.call(parentView, id);
}
if (t.name === "url") {
diff --git a/assets/javascripts/discourse/lib/binary-search.js.es6 b/assets/javascripts/discourse/lib/binary-search.js.es6
deleted file mode 100644
index 70d8792..0000000
--- a/assets/javascripts/discourse/lib/binary-search.js.es6
+++ /dev/null
@@ -1,27 +0,0 @@
-// The binarySearch() function is licensed under the UNLICENSE
-// https://github.com/Olical/binary-search
-
-// Modified for use in Discourse
-
-export default function binarySearch(list, target, keyProp) {
- let min = 0;
- let max = list.length - 1;
- let guess;
- const keyProperty = keyProp || "id";
-
- while (min <= max) {
- guess = Math.floor((min + max) / 2);
-
- if (Ember.get(list[guess], keyProperty) === target) {
- return guess;
- } else {
- if (Ember.get(list[guess], keyProperty) < target) {
- min = guess + 1;
- } else {
- max = guess - 1;
- }
- }
- }
-
- return -1;
-}
diff --git a/assets/javascripts/discourse/lib/themeColor.js.es6 b/assets/javascripts/discourse/lib/themeColor.js.es6
new file mode 100644
index 0000000..84af962
--- /dev/null
+++ b/assets/javascripts/discourse/lib/themeColor.js.es6
@@ -0,0 +1,4 @@
+export default function themeColor(name) {
+ const style = getComputedStyle(document.body);
+ return style.getPropertyValue(name);
+}
diff --git a/assets/javascripts/discourse/templates/components/data-explorer-bar-chart.hbs b/assets/javascripts/discourse/templates/components/data-explorer-bar-chart.hbs
new file mode 100644
index 0000000..aa8cc32
--- /dev/null
+++ b/assets/javascripts/discourse/templates/components/data-explorer-bar-chart.hbs
@@ -0,0 +1 @@
+
diff --git a/assets/javascripts/discourse/templates/explorer-query-result.hbs b/assets/javascripts/discourse/templates/explorer-query-result.hbs
index c773cc5..65dbdfa 100644
--- a/assets/javascripts/discourse/templates/explorer-query-result.hbs
+++ b/assets/javascripts/discourse/templates/explorer-query-result.hbs
@@ -3,6 +3,13 @@
{{d-button action=(action "downloadResultJson") icon="download" label="explorer.download_json" group=group}}
{{d-button action=(action "downloadResultCsv") icon="download" label="explorer.download_csv" group=group}}
+ {{#if canShowChart}}
+ {{#if showChart}}
+ {{d-button action=(action "showTable") icon="table" label="explorer.show_table" group=group}}
+ {{else}}
+ {{d-button action=(action "showChart") icon="chart-bar" label="explorer.show_graph" group=group}}
+ {{/if}}
+ {{/if}}
@@ -22,22 +29,29 @@
\ No newline at end of file
diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml
index 844b1c5..71488c0 100644
--- a/config/locales/client.en.yml
+++ b/config/locales/client.en.yml
@@ -34,6 +34,8 @@ en:
recover: "Undelete Query"
download_json: "JSON"
download_csv: "CSV"
+ show_table: "Table"
+ show_graph: "Graph"
others_dirty: "A query has unsaved changes that will be lost if you navigate away."
run_time: "Query completed in %{value} ms."
result_count:
diff --git a/test/javascripts/acceptance/run-query-test.js.es6 b/test/javascripts/acceptance/run-query-test.js.es6
new file mode 100644
index 0000000..b26db61
--- /dev/null
+++ b/test/javascripts/acceptance/run-query-test.js.es6
@@ -0,0 +1,194 @@
+import {
+ acceptance,
+ exists,
+ query,
+ queryAll,
+} from "discourse/tests/helpers/qunit-helpers";
+import { clearPopupMenuOptionsCallback } from "discourse/controllers/composer";
+import I18n from "I18n";
+
+acceptance("Data Explorer Plugin | Run Query", function (needs) {
+ needs.user();
+ needs.settings({ data_explorer_enabled: true });
+ needs.hooks.beforeEach(() => {
+ clearPopupMenuOptionsCallback();
+ });
+
+ needs.pretender((server, helper) => {
+ server.get("/admin/plugins/explorer/groups.json", () => {
+ return helper.response([
+ {
+ id: 1,
+ name: "admins",
+ },
+ {
+ id: 2,
+ name: "moderators",
+ },
+ {
+ id: 3,
+ name: "staff",
+ },
+ {
+ id: 0,
+ name: "everyone",
+ },
+ {
+ id: 10,
+ name: "trust_level_0",
+ },
+ {
+ id: 11,
+ name: "trust_level_1",
+ },
+ {
+ id: 12,
+ name: "trust_level_2",
+ },
+ {
+ id: 13,
+ name: "trust_level_3",
+ },
+ {
+ id: 14,
+ name: "trust_level_4",
+ },
+ ]);
+ });
+
+ server.get("/admin/plugins/explorer/schema.json", () => {
+ return helper.response({
+ anonymous_users: [
+ {
+ column_name: "id",
+ data_type: "serial",
+ primary: true,
+ },
+ {
+ column_name: "user_id",
+ data_type: "integer",
+ fkey_info: "users",
+ },
+ {
+ column_name: "master_user_id",
+ data_type: "integer",
+ fkey_info: "users",
+ },
+ {
+ column_name: "active",
+ data_type: "boolean",
+ },
+ {
+ column_name: "created_at",
+ data_type: "timestamp",
+ },
+ {
+ column_name: "updated_at",
+ data_type: "timestamp",
+ },
+ ],
+ });
+ });
+
+ server.get("/admin/plugins/explorer/queries", () => {
+ return helper.response({
+ queries: [
+ {
+ id: -6,
+ sql:
+ "-- [params]\n-- int :months_ago = 1\n\nWITH query_period AS (\n SELECT\n date_trunc('month', CURRENT_DATE) - INTERVAL ':months_ago months' as period_start,\n date_trunc('month', CURRENT_DATE) - INTERVAL ':months_ago months' + INTERVAL '1 month' - INTERVAL '1 second' as period_end\n )\n\n SELECT\n ua.user_id,\n count(1) AS like_count\n FROM user_actions ua\n INNER JOIN query_period qp\n ON ua.created_at >= qp.period_start\n AND ua.created_at <= qp.period_end\n WHERE ua.action_type = 1\n GROUP BY ua.user_id\n ORDER BY like_count DESC\n LIMIT 100\n",
+ name: "Top 100 Likers",
+ description:
+ "returns the top 100 likers for a given monthly period ordered by like_count. It accepts a ‘months_ago’ parameter, defaults to 1 to give results for the last calendar month.",
+ param_info: [
+ {
+ identifier: "months_ago",
+ type: "int",
+ default: "1",
+ nullable: false,
+ },
+ ],
+ created_at: "2021-02-02T12:21:11.449Z",
+ username: "system",
+ group_ids: [],
+ last_run_at: "2021-02-11T08:29:59.337Z",
+ hidden: false,
+ user_id: -1,
+ },
+ ],
+ });
+ });
+
+ server.post("/admin/plugins/explorer/queries/-6/run", () => {
+ return helper.response({
+ success: true,
+ errors: [],
+ duration: 27.5,
+ result_count: 2,
+ params: { months_ago: "1" },
+ columns: ["user_id", "like_count"],
+ default_limit: 1000,
+ relations: {
+ user: [
+ {
+ id: -2,
+ username: "discobot",
+ name: null,
+ avatar_template: "/user_avatar/localhost/discobot/{size}/2_2.png",
+ },
+ {
+ id: 2,
+ username: "andrey1",
+ name: null,
+ avatar_template:
+ "/letter_avatar_proxy/v4/letter/a/c0e974/{size}.png",
+ },
+ ],
+ },
+ colrender: {
+ 0: "user",
+ },
+ rows: [
+ [-2, 2],
+ [2, 2],
+ ],
+ });
+ });
+ });
+
+ test("it runs query and renders data and a chart", async function (assert) {
+ await visit("admin/plugins/explorer?id=-6");
+
+ assert.ok(
+ query("div.name h1").innerText.trim() === "Top 100 Likers",
+ "the query name was rendered"
+ );
+
+ assert.ok(exists("div.query-edit"), "the query code was rendered");
+
+ assert.ok(
+ query("form.query-run button span").innerText.trim() ===
+ I18n.t("explorer.run"),
+ "the run button was rendered"
+ );
+
+ await click("form.query-run button");
+
+ assert.ok(
+ queryAll("div.query-results table tbody tr").length === 2,
+ "the table with query results was rendered"
+ );
+ assert.ok(
+ query("div.result-info button:nth-child(3) span").innerText.trim() ===
+ I18n.t("explorer.show_graph"),
+ "the chart button was rendered"
+ );
+
+ await click("div.result-info button:nth-child(3)");
+
+ assert.ok(
+ exists("canvas.chartjs-render-monitor"),
+ "the chart was rendered"
+ );
+ });
+});
diff --git a/test/javascripts/integration/components/data-explorer-bar-chart-test.js.es6 b/test/javascripts/integration/components/data-explorer-bar-chart-test.js.es6
new file mode 100644
index 0000000..a687738
--- /dev/null
+++ b/test/javascripts/integration/components/data-explorer-bar-chart-test.js.es6
@@ -0,0 +1,30 @@
+import componentTest, {
+ setupRenderingTest,
+} from "discourse/tests/helpers/component-test";
+import { discourseModule, exists } from "discourse/tests/helpers/qunit-helpers";
+import hbs from "htmlbars-inline-precompile";
+
+discourseModule(
+ "Data Explorer Plugin | Integration | Component | data-explorer-bar-chart",
+ function (hooks) {
+ setupRenderingTest(hooks);
+
+ componentTest("it renders a chart", {
+ template: hbs`{{data-explorer-bar-chart}}`,
+
+ beforeEach() {
+ this.set("labels", ["label_1", "label_2"]);
+ this.set("values", [115, 1000]);
+ this.set("datasetName", "data");
+ },
+
+ async test(assert) {
+ assert.ok(exists("canvas"), "it renders a canvas");
+ assert.ok(
+ exists("canvas.chartjs-render-monitor"),
+ "it initializes chart.js "
+ );
+ },
+ });
+ }
+);
diff --git a/test/javascripts/integration/components/query-result-test.js.es6 b/test/javascripts/integration/components/query-result-test.js.es6
index 3a1db7f..ca7eea3 100644
--- a/test/javascripts/integration/components/query-result-test.js.es6
+++ b/test/javascripts/integration/components/query-result-test.js.es6
@@ -3,15 +3,72 @@ import componentTest, {
} from "discourse/tests/helpers/component-test";
import {
discourseModule,
- queryAll,
+ exists,
+ query,
} from "discourse/tests/helpers/qunit-helpers";
+import { click } from "@ember/test-helpers";
import hbs from "htmlbars-inline-precompile";
+import I18n from "I18n";
discourseModule(
"Data Explorer Plugin | Integration | Component | query-result",
function (hooks) {
setupRenderingTest(hooks);
+ componentTest("it renders query results", {
+ template: hbs`{{query-result content=content}}`,
+
+ beforeEach() {
+ const results = {
+ colrender: [],
+ result_count: 2,
+ columns: ["user_name", "like_count"],
+ rows: [
+ ["user1", 10],
+ ["user2", 20],
+ ],
+ };
+ this.set("content", results);
+ },
+
+ test(assert) {
+ assert.ok(
+ query("div.result-info button:nth-child(1) span").innerText ===
+ I18n.t("explorer.download_json"),
+ "it renders the JSON button"
+ );
+
+ assert.ok(
+ query("div.result-info button:nth-child(2) span").innerText ===
+ I18n.t("explorer.download_csv"),
+ "it renders the CSV button"
+ );
+
+ assert.ok(
+ query("div.result-info button:nth-child(3) span").innerText ===
+ I18n.t("explorer.show_graph"),
+ "it renders the chart button"
+ );
+
+ assert.ok(exists("div.result-about"), "it renders a query summary");
+
+ assert.ok(
+ query("table thead tr th:nth-child(1)").innerText === "user_name" &&
+ query("table thead tr th:nth-child(2)").innerText ===
+ "like_count" &&
+ query("table tbody tr:nth-child(1) td:nth-child(1)").innerText ===
+ "user1" &&
+ query("table tbody tr:nth-child(1) td:nth-child(2)").innerText ===
+ "10" &&
+ query("table tbody tr:nth-child(2) td:nth-child(1)").innerText ===
+ "user2" &&
+ query("table tbody tr:nth-child(2) td:nth-child(2)").innerText ===
+ "20",
+ "it renders a table with data"
+ );
+ },
+ });
+
componentTest("it renders badge names in query results", {
template: hbs`{{query-result content=content}}`,
@@ -38,11 +95,165 @@ discourseModule(
test(assert) {
assert.ok(
- queryAll(
- "table tbody tr:nth-child(1) td:nth-child(1) span"
- ).text() === "badge display name"
+ query("table tbody tr:nth-child(1) td:nth-child(1) span")
+ .innerText === "badge display name"
);
},
});
}
);
+
+discourseModule(
+ "Data Explorer Plugin | Integration | Component | query-result | chart",
+ function (hooks) {
+ setupRenderingTest(hooks);
+
+ componentTest("navigation between a table and a chart works", {
+ template: hbs`{{query-result content=content}}`,
+
+ beforeEach() {
+ const results = {
+ colrender: [],
+ result_count: 2,
+ columns: ["user_name", "like_count"],
+ rows: [
+ ["user1", 10],
+ ["user2", 20],
+ ],
+ };
+ this.set("content", results);
+ },
+
+ async test(assert) {
+ assert.equal(
+ query("div.result-info button:nth-child(3) span").innerText,
+ I18n.t("explorer.show_graph"),
+ "the chart button was rendered"
+ );
+ assert.ok(exists("table"), "the table was rendered");
+
+ await click("div.result-info button:nth-child(3)");
+
+ assert.equal(
+ query("div.result-info button:nth-child(3) span").innerText,
+ I18n.t("explorer.show_table"),
+ "the chart button was changed to the table button"
+ );
+ assert.ok(
+ exists("canvas.chartjs-render-monitor"),
+ "the chart was rendered"
+ );
+
+ await click("div.result-info button:nth-child(3)");
+ assert.equal(
+ query("div.result-info button:nth-child(3) span").innerText,
+ I18n.t("explorer.show_graph"),
+ "the table button was changed to the chart button"
+ );
+ assert.ok(exists("table"), "the table was rendered");
+ },
+ });
+
+ componentTest(
+ "it renders a chart button when data has two columns and numbers in the second column",
+ {
+ template: hbs`{{query-result content=content}}`,
+
+ beforeEach() {
+ const results = {
+ colrender: [],
+ result_count: 2,
+ columns: ["user_name", "like_count"],
+ rows: [
+ ["user1", 10],
+ ["user2", 20],
+ ],
+ };
+ this.set("content", results);
+ },
+
+ test(assert) {
+ assert.equal(
+ query("div.result-info button:nth-child(3) span").innerText,
+ I18n.t("explorer.show_graph")
+ );
+ },
+ }
+ );
+
+ componentTest(
+ "it doesn't render a chart button when data contains identifiers in the second column",
+ {
+ template: hbs`{{query-result content=content}}`,
+
+ beforeEach() {
+ const results = {
+ colrender: { 1: "user" },
+ relations: {
+ user: [
+ { id: 1, username: "user1" },
+ { id: 2, username: "user2" },
+ ],
+ },
+ result_count: 2,
+ columns: ["topic_id", "user_id"],
+ rows: [
+ [1, 10],
+ [2, 20],
+ ],
+ };
+ this.set("content", results);
+ },
+
+ test(assert) {
+ assert.ok(!exists("div.result-info button:nth-child(3)"));
+ },
+ }
+ );
+
+ componentTest(
+ "it doesn't render a chart button when data contains one column",
+ {
+ template: hbs`{{query-result content=content}}`,
+
+ beforeEach() {
+ const results = {
+ colrender: [],
+ result_count: 2,
+ columns: ["user_name"],
+ rows: [["user1"], ["user2"]],
+ };
+ this.set("content", results);
+ },
+
+ test(assert) {
+ assert.ok(!exists("div.result-info button:nth-child(3)"));
+ },
+ }
+ );
+
+ componentTest(
+ "it doesn't render a chart button when data contains more than two columns",
+ {
+ template: hbs`{{query-result content=content}}`,
+
+ beforeEach() {
+ const results = {
+ colrender: [],
+ result_count: 2,
+ columns: ["user_name", "like_count", "post_count"],
+ rows: [
+ ["user1", 10, 1],
+ ["user2", 20, 2],
+ ],
+ };
+ this.set("content", results);
+ },
+
+ test(assert) {
+ assert.ok(!exists("div.result-info button:nth-child(3)"));
+ },
+ }
+ );
+ }
+);
diff --git a/test/javascripts/unit/components/query-result-test.js.es6 b/test/javascripts/unit/components/query-result-test.js.es6
new file mode 100644
index 0000000..6ffb6be
--- /dev/null
+++ b/test/javascripts/unit/components/query-result-test.js.es6
@@ -0,0 +1,99 @@
+import { moduleFor } from "ember-qunit";
+import { test } from "qunit";
+
+moduleFor("component:query-result");
+
+test("it transforms data for a chart", function (assert) {
+ const results = {
+ colrender: [],
+ result_count: 2,
+ columns: ["user", "like_count"],
+ rows: [
+ ["user1", 10],
+ ["user2", 20],
+ ],
+ };
+ this.subject().setProperties({
+ content: results,
+ });
+
+ assert.deepEqual(
+ this.subject().chartLabels,
+ ["user1", "user2"],
+ "labels are correct"
+ );
+
+ assert.deepEqual(this.subject().chartValues, [10, 20], "values are correct");
+
+ assert.deepEqual(
+ this.subject().chartDatasetName,
+ "like_count",
+ "the dataset name is correct"
+ );
+});
+
+test("it uses descriptive chart labels instead of identifiers", function (assert) {
+ const results = {
+ colrender: { 0: "user" },
+ relations: {
+ user: [
+ { id: 1, username: "user1" },
+ { id: 2, username: "user2" },
+ ],
+ },
+ result_count: 2,
+ columns: ["user", "like_count"],
+ rows: [
+ [1, 10],
+ [2, 20],
+ ],
+ };
+ this.subject().setProperties({
+ content: results,
+ });
+
+ assert.deepEqual(this.subject().chartLabels, ["user1", "user2"]);
+});
+
+test("it uses an identifier as a chart label if labelSelector doesn't exist", function (assert) {
+ const results = {
+ colrender: { 0: "unknown_entity" },
+ relations: {
+ unknown_entity: [
+ { id: 1, username: "user1" },
+ { id: 2, username: "user2" },
+ ],
+ },
+ result_count: 2,
+ columns: ["user", "like_count"],
+ rows: [
+ [1, 10],
+ [2, 20],
+ ],
+ };
+ this.subject().setProperties({
+ content: results,
+ });
+
+ assert.deepEqual(this.subject().chartLabels, ["1", "2"]);
+});
+
+test("it cuts too long chart labels", function (assert) {
+ const results = {
+ colrender: [],
+ result_count: 2,
+ columns: ["user", "like_count"],
+ rows: [
+ ["This string is too long to be used as a label on a chart", 10],
+ ["This string is too long to be used as a label on a chart", 20],
+ ],
+ };
+ this.subject().setProperties({
+ content: results,
+ });
+
+ assert.deepEqual(this.subject().chartLabels, [
+ "This string is too long t...",
+ "This string is too long t...",
+ ]);
+});