diff --git a/.github/workflows/push_tests.yml b/.github/workflows/push_tests.yml index 8ed6803f..0e84a43b 100644 --- a/.github/workflows/push_tests.yml +++ b/.github/workflows/push_tests.yml @@ -4,6 +4,7 @@ on: push: branches: master pull_request: + branches: '*' env: JEST_ENV: prod @@ -31,20 +32,15 @@ jobs: run: | pip install flake8 cd client - npm i "eslint" "eslint-config-airbnb" "eslint-config-prettier" "eslint-loader" "eslint-plugin-filenames" "eslint-plugin-import" "eslint-plugin-jest" "eslint-plugin-jsx-a11y" "eslint-plugin-react" "eslint-plugin-react-hooks" + npm i "eslint" "eslint-config-airbnb" "eslint-config-prettier" "eslint-loader" "eslint-plugin-filenames" "eslint-plugin-import" "eslint-plugin-jest" "eslint-plugin-jsx-a11y" "eslint-plugin-react" "eslint-plugin-react-hooks" "eslint-plugin-prettier" - name: Lint with flake8 run: | make lint-server - # - name: Lint all with eslint - # working-directory: ./client - # if: github.event_name != 'pull_request' - # run: | - # make lint - # - name: Lint diff with eslint - # working-directory: ./client - # if: github.event_name == 'pull_request' - # run: | - # make lint-diff + - name: Lint src with eslint + working-directory: ./client + run: | + make lint + unit-test: runs-on: ubuntu-latest diff --git a/.gitignore b/.gitignore index 9c2e4450..4f93ac98 100644 --- a/.gitignore +++ b/.gitignore @@ -51,3 +51,5 @@ data # Jekyll docs/_site/ docs/Gemfile.lock + +client/.eslintcache diff --git a/Makefile b/Makefile index 18ef2865..8da0edd3 100644 --- a/Makefile +++ b/Makefile @@ -84,11 +84,6 @@ lint-server: lint-client: cd client && $(MAKE) lint -.PHONY: lint-diff-client -lint-diff-client: - cd client && $(MAKE) lint-diff - - # CREATING DISTRIBUTION RELEASE .PHONY: pydist diff --git a/client/Makefile b/client/Makefile index ff8eb1b5..3a9acb5b 100644 --- a/client/Makefile +++ b/client/Makefile @@ -25,16 +25,7 @@ build: .PHONY: lint lint: - npx eslint . - -.PHONY: lint-diff -lint-diff: -# Get client diff against master, find all js/jsx files, remove the client prefex, run eslinst against result - git diff --name-only --diff-filter=d origin/master...HEAD -- ../client/ \ - | grep -E "(.*)\.(jsx|js)$$" \ - | sed "s/client\///" \ - | $(if $(IS_DARWIN),xargs ./node_modules/.bin/eslint,xargs -r ./node_modules/.bin/eslint) - + npx eslint ./src/ # Development convenience methods .PHONY: start-frontend diff --git a/client/__tests__/e2e/__snapshots__/e2e.test.js.snap b/client/__tests__/e2e/__snapshots__/e2e.test.js.snap new file mode 100644 index 00000000..9f866bf8 --- /dev/null +++ b/client/__tests__/e2e/__snapshots__/e2e.test.js.snap @@ -0,0 +1,5 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`did launch page launched 1`] = `"pbmc3kc3k"`; + +exports[`metadata loads categories and values from dataset appear 1`] = `"
louvainvain
tint
"`; diff --git a/client/__tests__/e2e/cellxgeneActions.js b/client/__tests__/e2e/cellxgeneActions.js index fb8b12f4..e6b5afb6 100644 --- a/client/__tests__/e2e/cellxgeneActions.js +++ b/client/__tests__/e2e/cellxgeneActions.js @@ -44,9 +44,10 @@ export const cellxgeneActions = (page, utils) => ({ (rows) => Object.fromEntries( rows.map((row) => { - const cat = row.querySelector( - "[data-testclass='categorical-value']" - ).innerText; + const cat = row + .querySelector("[data-testclass='categorical-value']") + .getAttribute("aria-label"); + const count = row.querySelector( "[data-testclass='categorical-value-count']" ).innerText; diff --git a/client/__tests__/e2e/e2e.test.js b/client/__tests__/e2e/e2e.test.js index 76e1b4d0..58b4367e 100644 --- a/client/__tests__/e2e/e2e.test.js +++ b/client/__tests__/e2e/e2e.test.js @@ -30,12 +30,12 @@ describe("did launch", () => { const element = await utils.getOneElementInnerHTML( "[data-testid='header']" ); - expect(element).toBe(data.title); + expect(element).toMatchSnapshot(); }); test("terms of service, if they are there", async () => { try { - await utils.clickOn("tos-cookies-accept", { timeout: 500 }); + await utils.clickOn("tos-cookies-accept", { timeout: 3000 }); } catch { console.warn("No terms of service footer detected."); } @@ -48,10 +48,10 @@ describe("did launch", () => { describe("metadata loads", () => { test("categories and values from dataset appear", async () => { for (const label in data.categorical) { - const categoryName = await utils.getOneElementInnerText( + const elem = await utils.getOneElementInnerHTML( `[data-testid="category-${label}"]` ); - expect(categoryName).toMatch(label); + expect(elem).toMatchSnapshot(); await utils.clickOn(`${label}:category-expand`); const categories = await cxgActions.getAllCategoriesAndCounts(label); expect(Object.keys(categories)).toMatchObject( @@ -326,13 +326,16 @@ describe("ui elements don't error", () => { describe("centroid labels", () => { test("labels are created", async () => { - await utils.clickOn("centroid-label-toggle"); const labels = Object.keys(data.categorical); + + await utils.clickOn(`colorby-${labels[0]}`); + await utils.clickOn("centroid-label-toggle"); /* eslint-disable no-await-in-loop */ // Toggle colorby for each category and check to see if labels are generated for (let i = 0, { length } = labels; i < length; i += 1) { const label = labels[i]; - await utils.clickOn(`colorby-${label}`); + // first label is already enabled + if (i !== 0) await utils.clickOn(`colorby-${label}`); const generatedLabels = await utils.getAllByClass("centroid-label"); // Number of labels generated should be equal to size of the object expect(generatedLabels).toHaveLength( @@ -346,8 +349,8 @@ describe("centroid labels", () => { describe("graph overlay", () => { test("transform centroids correctly", async () => { const category = Object.keys(data.categorical)[0]; - await utils.clickOn("centroid-label-toggle"); await utils.clickOn(`colorby-${category}`); + await utils.clickOn("centroid-label-toggle"); await utils.clickOn("mode-pan-zoom"); const panCoords = await cxgActions.calcDragCoordinates( "layout-graph", diff --git a/client/__tests__/e2e/e2eAnnotations.test.js b/client/__tests__/e2e/e2eAnnotations.test.js index f0041529..3678b872 100644 --- a/client/__tests__/e2e/e2eAnnotations.test.js +++ b/client/__tests__/e2e/e2eAnnotations.test.js @@ -201,16 +201,18 @@ describe.each([ }); async function assertCategoryExists(categoryName) { - const handle = await utils.waitByID(`${categoryName}:category-expand`); - const result = await handle.evaluate((node) => node.innerText); - // slice beginning and end of category name result to account for truncation of long names - expect(result.slice(0, 10)).toBe(categoryName.slice(0, 10)); - expect(result.slice(-10)).toBe(categoryName.slice(-10)); + const handle = await utils.waitByID(`${categoryName}:category-label`); + + const result = await handle.evaluate((node) => + node.getAttribute("aria-label") + ); + + expect(result).toBe(categoryName); } async function assertCategoryDoesNotExist(categoryName) { const result = await page.$( - `[data-testid='${categoryName}:category-expand']` + `[data-testid='${categoryName}:category-label']` ); expect(result).toBeNull(); } @@ -222,7 +224,9 @@ describe.each([ const previous = await utils.waitByID( `categorical-value-${categoryName}-${labelName}` ); - expect(await previous.evaluate((node) => node.innerText)).toBe(labelName); + expect( + await previous.evaluate((node) => node.getAttribute("aria-label")) + ).toBe(labelName); } async function assertLabelDoesNotExist(categoryName, labelName) { diff --git a/client/__tests__/e2e/puppeteerUtils.js b/client/__tests__/e2e/puppeteerUtils.js index e7374f8b..d9dc82c6 100644 --- a/client/__tests__/e2e/puppeteerUtils.js +++ b/client/__tests__/e2e/puppeteerUtils.js @@ -50,8 +50,8 @@ export const puppeteerUtils = (page) => ({ return click; }, - async getOneElementInnerHTML(selector) { - await page.waitForSelector(selector); + async getOneElementInnerHTML(selector, options = {}) { + await page.waitForSelector(selector, options); return page.$eval(selector, (el) => el.innerHTML); }, diff --git a/client/__tests__/util/centroid.test.js b/client/__tests__/util/centroid.test.js index 28467cde..9ed75c3a 100644 --- a/client/__tests__/util/centroid.test.js +++ b/client/__tests__/util/centroid.test.js @@ -4,6 +4,7 @@ import calcCentroid from "../../src/util/centroid"; import quantile from "../../src/util/quantile"; import * as Universe from "../../src/util/stateManager/universe"; +import { matrixFBSToDataframe } from "../../src/util/stateManager/matrix"; import * as World from "../../src/util/stateManager/world"; import * as REST from "./stateManager/sampleResponses"; import { ControlsHelpers as CH } from "../../src/util/stateManager"; @@ -22,16 +23,13 @@ describe("centroid", () => { ...universe, ...Universe.addObsAnnotations( universe, - Universe.matrixFBSToDataframe(REST.annotationsObs) + matrixFBSToDataframe(REST.annotationsObs) ), ...Universe.addVarAnnotations( universe, - Universe.matrixFBSToDataframe(REST.annotationsVar) - ), - ...Universe.addObsLayout( - universe, - Universe.matrixFBSToDataframe(REST.layoutObs) + matrixFBSToDataframe(REST.annotationsVar) ), + ...Universe.addObsLayout(universe, matrixFBSToDataframe(REST.layoutObs)), }; world = World.createWorldFromEntireUniverse(universe); diff --git a/client/__tests__/util/dataframe/dataframe.test.js b/client/__tests__/util/dataframe/dataframe.test.js index 3ad843a9..b15df2e1 100644 --- a/client/__tests__/util/dataframe/dataframe.test.js +++ b/client/__tests__/util/dataframe/dataframe.test.js @@ -38,8 +38,8 @@ describe("dataframe constructor", () => { expect(df.rowIndex).toBeInstanceOf(Dataframe.DenseInt32Index); expect(df.colIndex).toBeInstanceOf(Dataframe.KeyIndex); - expect(df.rowIndex.keys()).toEqual(new Int32Array([2, 1, 0])); - expect(df.colIndex.keys()).toEqual(["A", "B"]); + expect(df.rowIndex.labels()).toEqual(new Int32Array([2, 1, 0])); + expect(df.colIndex.labels()).toEqual(["A", "B"]); expect(df.at(0, "A")).toEqual(2); expect(df.at(2, "B")).toEqual(3); @@ -138,7 +138,7 @@ describe("dataframe subsetting", () => { new Float32Array([4.4, 5.5, 6.6]), ["red", "green", "blue"], ], - null, + null, // identity index new Dataframe.KeyIndex(["int32", "string", "float32", "colors"]) ); @@ -153,12 +153,12 @@ describe("dataframe subsetting", () => { expect(dfA.col("colors").asArray()).toEqual( sourceDf.col("colors").asArray() ); - expect(dfA.rowIndex.keys()).toEqual(sourceDf.rowIndex.keys()); - expect(dfA.colIndex.keys()).toEqual(["colors"]); + expect(dfA.rowIndex.labels()).toEqual(sourceDf.rowIndex.labels()); + expect(dfA.colIndex.labels()).toEqual(["colors"]); }); test("all rows, two columns", () => { - const dfB = sourceDf.subset(null, ["colors", "float32"]); + const dfB = sourceDf.subset(null, ["float32", "colors"]); expect(dfB).toBeDefined(); expect(dfB.dims).toEqual([3, 2]); expect(dfB.iat(0, 0)).toBeCloseTo(4.4); @@ -177,8 +177,8 @@ describe("dataframe subsetting", () => { expect(dfB.col("float32").asArray()).toEqual( sourceDf.col("float32").asArray() ); - expect(dfB.rowIndex.keys()).toEqual(sourceDf.rowIndex.keys()); - expect(dfB.colIndex.keys()).toEqual(["float32", "colors"]); + expect(dfB.rowIndex.labels()).toEqual(sourceDf.rowIndex.labels()); + expect(dfB.colIndex.labels()).toEqual(["float32", "colors"]); }); test("one row, all columns", () => { @@ -189,8 +189,8 @@ describe("dataframe subsetting", () => { expect(dfC.iat(0, 1)).toEqual("B"); expect(dfC.iat(0, 2)).toBeCloseTo(5.5); expect(dfC.iat(0, 3)).toEqual("green"); - expect(dfC.rowIndex.keys()).toEqual(new Int32Array([1])); - expect(dfC.colIndex.keys()).toEqual(sourceDf.colIndex.keys()); + expect(dfC.rowIndex.labels()).toEqual(new Int32Array([1])); + expect(dfC.colIndex.labels()).toEqual(sourceDf.colIndex.labels()); }); test("two rows, all columns", () => { @@ -201,8 +201,17 @@ describe("dataframe subsetting", () => { expect(dfD.icol(1).asArray()).toEqual(["A", "C"]); expect(dfD.icol(2).asArray()).toEqual(new Float32Array([4.4, 6.6])); expect(dfD.icol(3).asArray()).toEqual(["red", "blue"]); - expect(dfD.rowIndex.keys()).toEqual(new Int32Array([0, 2])); - expect(dfD.colIndex.keys()).toEqual(sourceDf.colIndex.keys()); + expect(dfD.rowIndex.labels()).toEqual(new Int32Array([0, 2])); + expect(dfD.colIndex.labels()).toEqual(sourceDf.colIndex.labels()); + + // reverse the row order + const dfDr = sourceDf.subset([2, 0], null); + expect(dfDr.icol(0).asArray()).toEqual(new Int32Array([2, 0])); + expect(dfDr.icol(1).asArray()).toEqual(["C", "A"]); + expect(dfDr.icol(2).asArray()).toEqual(new Float32Array([6.6, 4.4])); + expect(dfDr.icol(3).asArray()).toEqual(["blue", "red"]); + expect(dfDr.rowIndex.labels()).toEqual(new Int32Array([2, 0])); + expect(dfDr.colIndex.labels()).toEqual(sourceDf.colIndex.labels()); }); test("all rows, all columns", () => { @@ -213,8 +222,8 @@ describe("dataframe subsetting", () => { expect(dfE.icol(1).asArray()).toEqual(sourceDf.icol(1).asArray()); expect(dfE.icol(2).asArray()).toEqual(sourceDf.icol(2).asArray()); expect(dfE.icol(3).asArray()).toEqual(sourceDf.icol(3).asArray()); - expect(dfE.rowIndex.keys()).toEqual(sourceDf.rowIndex.keys()); - expect(dfE.colIndex.keys()).toEqual(sourceDf.colIndex.keys()); + expect(dfE.rowIndex.labels()).toEqual(sourceDf.rowIndex.labels()); + expect(dfE.colIndex.labels()).toEqual(sourceDf.colIndex.labels()); }); test("two rows, two colums", () => { @@ -223,8 +232,17 @@ describe("dataframe subsetting", () => { expect(dfF.dims).toEqual([2, 2]); expect(dfF.icol(0).asArray()).toEqual(new Int32Array([0, 2])); expect(dfF.icol(1).asArray()).toEqual(new Float32Array([4.4, 6.6])); - expect(dfF.rowIndex.keys()).toEqual(new Int32Array([0, 2])); - expect(dfF.colIndex.keys()).toEqual(["int32", "float32"]); + expect(dfF.rowIndex.labels()).toEqual(new Int32Array([0, 2])); + expect(dfF.colIndex.labels()).toEqual(["int32", "float32"]); + + // reverse the row and column order + const dfFr = sourceDf.subset([2, 0], ["float32", "int32"]); + expect(dfFr).toBeDefined(); + expect(dfFr.dims).toEqual([2, 2]); + expect(dfFr.icol(0).asArray()).toEqual(new Float32Array([6.6, 4.4])); + expect(dfFr.icol(1).asArray()).toEqual(new Int32Array([2, 0])); + expect(dfFr.rowIndex.labels()).toEqual(new Int32Array([2, 0])); + expect(dfFr.colIndex.labels()).toEqual(["float32", "int32"]); }); test("withRowIndex", () => { @@ -271,8 +289,49 @@ describe("dataframe subsetting", () => { expect(dfA.dims).toEqual([2, 2]); expect(dfA.icol(0).asArray()).toEqual(new Int32Array([1, 2])); expect(dfA.icol(1).asArray()).toEqual(["green", "blue"]); - expect(dfA.rowIndex.keys()).toEqual(new Int32Array([4, 6])); - expect(dfA.colIndex.keys()).toEqual(["int32", "colors"]); + expect(dfA.rowIndex.labels()).toEqual(new Int32Array([4, 6])); + expect(dfA.colIndex.labels()).toEqual(["int32", "colors"]); + }); + + describe("isubset", () => { + const sourceDf = new Dataframe.Dataframe( + [3, 4], + [ + new Int32Array([0, 1, 2]), + ["A", "B", "C"], + new Float32Array([4.4, 5.5, 6.6]), + ["red", "green", "blue"], + ], + null, // identity index + new Dataframe.KeyIndex(["int32", "string", "float32", "colors"]) + ); + + test("one row, all cols", () => { + const dfA = sourceDf.isubset([1], null); + expect(dfA.dims).toEqual([1, 4]); + expect(dfA.icol(0).asArray()).toEqual(new Int32Array([1])); + expect(dfA.icol(1).asArray()).toEqual(["B"]); + expect(dfA.icol(2).asArray()).toEqual(new Float32Array([5.5])); + expect(dfA.icol(3).asArray()).toEqual(["green"]); + }); + + test("all rows, two cols", () => { + const dfA = sourceDf.isubset(null, [1, 2]); + expect(dfA.dims).toEqual([3, 2]); + expect(dfA.icol(0).asArray()).toEqual(["A", "B", "C"]); + expect(dfA.icol(1).asArray()).toEqual(new Float32Array([4.4, 5.5, 6.6])); + expect(dfA.col("string")).toBe(dfA.icol(0)); + expect(dfA.col("float32")).toBe(dfA.icol(1)); + }); + + test("out of order rows", () => { + const dfA = sourceDf.isubset([2, 0], null); + expect(dfA.dims).toEqual([2, 4]); + expect(dfA.icol(0).asArray()).toEqual(new Int32Array([2, 0])); + expect(dfA.icol(1).asArray()).toEqual(["C", "A"]); + expect(dfA.icol(2).asArray()).toEqual(new Float32Array([6.6, 4.4])); + expect(dfA.icol(3).asArray()).toEqual(["blue", "red"]); + }); }); }); @@ -310,8 +369,8 @@ describe("dataframe factories", () => { expect(dfB).not.toBe(dfA); expect(dfB.dims).toEqual(dfA.dims); expect(dfB).toHaveLength(dfA.length); - expect(dfB.rowIndex.keys()).toEqual(dfA.rowIndex.keys()); - expect(dfB.colIndex.keys()).toEqual(dfA.colIndex.keys()); + expect(dfB.rowIndex.labels()).toEqual(dfA.rowIndex.labels()); + expect(dfB.colIndex.labels()).toEqual(dfA.colIndex.labels()); for (let i = 0, l = dfB.dims[1]; i < l; i += 1) { expect(dfB.icol(i).asArray()).toEqual(dfA.icol(i).asArray()); } @@ -336,9 +395,9 @@ describe("dataframe factories", () => { expect(dfA.icol(1).asArray()).toEqual([true, false]); expect(dfA.icol(2).asArray()).toEqual([1, 0]); expect(dfA.col("numbers").asArray()).toEqual([1, 0]); - expect(dfA.colIndex.keys()).toEqual(["colors", "bools", "numbers"]); - expect(df.colIndex.keys()).toEqual(["colors", "bools"]); - expect(df.rowIndex.keys()).toEqual(dfA.rowIndex.keys()); + expect(dfA.colIndex.labels()).toEqual(["colors", "bools", "numbers"]); + expect(df.colIndex.labels()).toEqual(["colors", "bools"]); + expect(df.rowIndex.labels()).toEqual(dfA.rowIndex.labels()); }); test("DenseInt32Index", () => { @@ -361,9 +420,9 @@ describe("dataframe factories", () => { expect(dfA.col(74).asArray()).toEqual(["red", "blue"]); expect(dfA.col(75).asArray()).toEqual([true, false]); expect(dfA.col(72).asArray()).toEqual([1, 0]); - expect(dfA.colIndex.keys()).toEqual(new Int32Array([74, 75, 72])); - expect(df.colIndex.keys()).toEqual(new Int32Array([74, 75])); - expect(df.rowIndex.keys()).toEqual(dfA.rowIndex.keys()); + expect(dfA.colIndex.labels()).toEqual(new Int32Array([74, 75, 72])); + expect(df.colIndex.labels()).toEqual(new Int32Array([74, 75])); + expect(df.rowIndex.labels()).toEqual(dfA.rowIndex.labels()); }); test("DenseInt32Index promote", () => { @@ -386,9 +445,9 @@ describe("dataframe factories", () => { expect(dfA.col(74).asArray()).toEqual(["red", "blue"]); expect(dfA.col(75).asArray()).toEqual([true, false]); expect(dfA.col(999).asArray()).toEqual([1, 0]); - expect(dfA.colIndex.keys()).toEqual(new Int32Array([74, 75, 999])); - expect(df.colIndex.keys()).toEqual(new Int32Array([74, 75])); - expect(df.rowIndex.keys()).toEqual(dfA.rowIndex.keys()); + expect(dfA.colIndex.labels()).toEqual(new Int32Array([74, 75, 999])); + expect(df.colIndex.labels()).toEqual(new Int32Array([74, 75])); + expect(df.rowIndex.labels()).toEqual(dfA.rowIndex.labels()); }); test("IdentityInt32Index with last", () => { @@ -411,9 +470,9 @@ describe("dataframe factories", () => { expect(dfA.col(0).asArray()).toEqual(["red", "blue"]); expect(dfA.col(1).asArray()).toEqual([true, false]); expect(dfA.col(2).asArray()).toEqual([1, 0]); - expect(dfA.colIndex.keys()).toEqual(new Int32Array([0, 1, 2])); - expect(df.colIndex.keys()).toEqual(new Int32Array([0, 1])); - expect(df.rowIndex.keys()).toEqual(dfA.rowIndex.keys()); + expect(dfA.colIndex.labels()).toEqual(new Int32Array([0, 1, 2])); + expect(df.colIndex.labels()).toEqual(new Int32Array([0, 1])); + expect(df.rowIndex.labels()).toEqual(dfA.rowIndex.labels()); }); test("IdentityInt32Index promote", () => { @@ -436,9 +495,9 @@ describe("dataframe factories", () => { expect(dfA.col(0).asArray()).toEqual(["red", "blue"]); expect(dfA.col(1).asArray()).toEqual([true, false]); expect(dfA.col(99).asArray()).toEqual([1, 0]); - expect(dfA.colIndex.keys()).toEqual(new Int32Array([0, 1, 99])); - expect(df.colIndex.keys()).toEqual(new Int32Array([0, 1])); - expect(df.rowIndex.keys()).toEqual(dfA.rowIndex.keys()); + expect(dfA.colIndex.labels()).toEqual(new Int32Array([0, 1, 99])); + expect(df.colIndex.labels()).toEqual(new Int32Array([0, 1])); + expect(df.rowIndex.labels()).toEqual(dfA.rowIndex.labels()); }); describe("handle column dimensions correctly", () => { @@ -517,25 +576,25 @@ describe("dataframe factories", () => { const dfLikeA = dfEmpty.withColsFrom(dfA); expect(dfLikeA).toBeDefined(); expect(dfLikeA.dims).toEqual(dfA.dims); - expect(dfLikeA.colIndex.keys()).toEqual(dfA.colIndex.keys()); + expect(dfLikeA.colIndex.labels()).toEqual(dfA.colIndex.labels()); expect(dfLikeA.rowIndex).toEqual(dfA.rowIndex); - expect(dfLikeA.rowIndex.keys()).toEqual(dfA.rowIndex.keys()); + expect(dfLikeA.rowIndex.labels()).toEqual(dfA.rowIndex.labels()); expect(dfLikeA.icol(0).asArray()).toEqual(dfA.icol(0).asArray()); const dfAlsoLikeA = dfA.withColsFrom(dfEmpty); expect(dfAlsoLikeA).toBeDefined(); expect(dfAlsoLikeA.dims).toEqual(dfA.dims); - expect(dfAlsoLikeA.colIndex.keys()).toEqual(dfA.colIndex.keys()); + expect(dfAlsoLikeA.colIndex.labels()).toEqual(dfA.colIndex.labels()); expect(dfAlsoLikeA.rowIndex).toEqual(dfA.rowIndex); - expect(dfAlsoLikeA.rowIndex.keys()).toEqual(dfA.rowIndex.keys()); + expect(dfAlsoLikeA.rowIndex.labels()).toEqual(dfA.rowIndex.labels()); expect(dfAlsoLikeA.icol(0).asArray()).toEqual(dfA.icol(0).asArray()); const dfC = dfA.withColsFrom(dfB); expect(dfC).toBeDefined(); expect(dfC.dims).toEqual([2, 2]); - expect(dfC.colIndex.keys()).toEqual(["colors", "bools"]); + expect(dfC.colIndex.labels()).toEqual(["colors", "bools"]); expect(dfC.rowIndex).toEqual(dfA.rowIndex); - expect(dfC.rowIndex.keys()).toEqual(dfA.rowIndex.keys()); + expect(dfC.rowIndex.labels()).toEqual(dfA.rowIndex.labels()); expect(dfC.col("colors").asArray()).toEqual(["red", "blue"]); expect(dfC.col("bools").asArray()).toEqual([true, false]); }); @@ -562,21 +621,21 @@ describe("dataframe factories", () => { const dfX = dfEmpty.withColsFrom(dfB, ["colors", "bools"]); expect(dfX).toBeDefined(); expect(dfX.dims).toEqual([2, 2]); - expect(dfX.colIndex.keys()).toEqual(["colors", "bools"]); + expect(dfX.colIndex.labels()).toEqual(["colors", "bools"]); expect(dfX.rowIndex).toEqual(dfB.rowIndex); expect(dfX.icol(0).asArray()).toEqual(dfB.icol(0).asArray()); const dfY = dfA.withColsFrom(dfB, ["numbers"]); expect(dfY).toBeDefined(); expect(dfY.dims).toEqual([2, 2]); - expect(dfY.colIndex.keys()).toEqual(["colors", "numbers"]); + expect(dfY.colIndex.labels()).toEqual(["colors", "numbers"]); expect(dfY.rowIndex).toEqual(dfA.rowIndex); expect(dfY.icol(0).asArray()).toEqual(dfA.icol(0).asArray()); const dfZ = dfA.withColsFrom(dfEmpty, []); expect(dfZ).toBeDefined(); expect(dfZ.dims).toEqual(dfA.dims); - expect(dfZ.colIndex.keys()).toEqual(dfA.colIndex.keys()); + expect(dfZ.colIndex.labels()).toEqual(dfA.colIndex.labels()); expect(dfZ.rowIndex).toEqual(dfA.rowIndex); expect(dfZ.icol(0).asArray()).toEqual(dfA.icol(0).asArray()); @@ -604,7 +663,7 @@ describe("dataframe factories", () => { const dfX = dfA.withColsFrom(dfB, { colors: "_colors", bools: "_bools" }); expect(dfX).toBeDefined(); expect(dfX.dims).toEqual([2, 3]); - expect(dfX.colIndex.keys()).toEqual(["colors", "_colors", "_bools"]); + expect(dfX.colIndex.labels()).toEqual(["colors", "_colors", "_bools"]); expect(dfX.rowIndex).toEqual(dfA.rowIndex); expect(dfX.icol(0).asArray()).toEqual(dfA.icol(0).asArray()); expect(dfX.col("_colors").asArray()).toBe(dfB.col("colors").asArray()); @@ -630,9 +689,9 @@ describe("dataframe factories", () => { expect(dfA.icol(0).asArray()).toEqual([true, false]); expect(dfA.icol(1).asArray()).toEqual([1, 0]); expect(dfA.col("numbers").asArray()).toEqual([1, 0]); - expect(dfA.colIndex.keys()).toEqual(["bools", "numbers"]); - expect(df.colIndex.keys()).toEqual(["colors", "bools", "numbers"]); - expect(df.rowIndex.keys()).toEqual(dfA.rowIndex.keys()); + expect(dfA.colIndex.labels()).toEqual(["bools", "numbers"]); + expect(df.colIndex.labels()).toEqual(["colors", "bools", "numbers"]); + expect(df.rowIndex.labels()).toEqual(dfA.rowIndex.labels()); }); test("IdentityInt32Index drop first", () => { @@ -654,9 +713,9 @@ describe("dataframe factories", () => { expect(dfA.icol(1).asArray()).toEqual([1, 0]); expect(df.col(1).asArray()).toEqual(dfA.col(1).asArray()); expect(df.col(2).asArray()).toEqual(dfA.col(2).asArray()); - expect(dfA.colIndex.keys()).toEqual(new Int32Array([1, 2])); - expect(df.colIndex.keys()).toEqual(new Int32Array([0, 1, 2])); - expect(df.rowIndex.keys()).toEqual(dfA.rowIndex.keys()); + expect(dfA.colIndex.labels()).toEqual(new Int32Array([1, 2])); + expect(df.colIndex.labels()).toEqual(new Int32Array([0, 1, 2])); + expect(df.rowIndex.labels()).toEqual(dfA.rowIndex.labels()); }); test("IdentityInt32Index drop last", () => { @@ -678,9 +737,9 @@ describe("dataframe factories", () => { expect(dfA.icol(1).asArray()).toEqual([true, false]); expect(df.col(0).asArray()).toEqual(dfA.col(0).asArray()); expect(df.col(1).asArray()).toEqual(dfA.col(1).asArray()); - expect(dfA.colIndex.keys()).toEqual(new Int32Array([0, 1])); - expect(df.colIndex.keys()).toEqual(new Int32Array([0, 1, 2])); - expect(df.rowIndex.keys()).toEqual(dfA.rowIndex.keys()); + expect(dfA.colIndex.labels()).toEqual(new Int32Array([0, 1])); + expect(df.colIndex.labels()).toEqual(new Int32Array([0, 1, 2])); + expect(df.rowIndex.labels()).toEqual(dfA.rowIndex.labels()); }); test("DenseInt32Index", () => { @@ -702,9 +761,9 @@ describe("dataframe factories", () => { expect(dfA.icol(1).asArray()).toEqual([1, 0]); expect(dfA.col(100).asArray()).toEqual([1, 0]); expect(dfA.col(102).asArray()).toEqual(["red", "blue"]); - expect(dfA.colIndex.keys()).toEqual(new Int32Array([102, 100])); - expect(df.colIndex.keys()).toEqual(new Int32Array([102, 101, 100])); - expect(df.rowIndex.keys()).toEqual(dfA.rowIndex.keys()); + expect(dfA.colIndex.labels()).toEqual(new Int32Array([102, 100])); + expect(df.colIndex.labels()).toEqual(new Int32Array([102, 101, 100])); + expect(df.rowIndex.labels()).toEqual(dfA.rowIndex.labels()); }); }); @@ -766,8 +825,8 @@ describe("dataframe factories", () => { new Dataframe.KeyIndex(["A", "B"]) ); const dfB = dfA.renameCol("B", "C"); - expect(dfA.colIndex.keys()).toEqual(["A", "B"]); - expect(dfB.colIndex.keys()).toEqual(["A", "C"]); + expect(dfA.colIndex.labels()).toEqual(["A", "B"]); + expect(dfB.colIndex.labels()).toEqual(["A", "C"]); expect(dfA.dims).toMatchObject(dfB.dims); expect(dfA.columns()).toMatchObject(dfB.columns()); }); @@ -857,3 +916,84 @@ describe("dataframe col", () => { expect(df.col("B").indexOf(true)).toBeUndefined(); }); }); + +describe("label indexing", () => { + + test("IdentityInt32Index", () => { + const idx = new Dataframe.IdentityInt32Index(12); // [0, 12) + + expect(Dataframe.isLabelIndex(idx)).toBeTruthy(); + + expect(idx.labels()).toEqual(new Int32Array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11])); + expect(idx.getLabel(1)).toEqual(1); + expect(idx.getOffset(1)).toEqual(1); + expect(idx.getOffsets([1,3])).toEqual([1,3]) + expect(idx.getLabels([1, 3])).toEqual([1,3]) + expect(idx.size()).toEqual(12); + + expect(idx.subset([2]).labels()).toEqual([2]); + expect(idx.subset([2, 3, 4]).labels()).toEqual(new Int32Array([2, 3, 4])); + expect(idx.subset([0, 1, 2, 3]).labels()).toEqual(new Int32Array([0, 1, 2, 3])); + + expect(idx.isubset([2]).labels()).toEqual([2]); + expect(idx.isubset([2, 3, 4]).labels()).toEqual(new Int32Array([2, 3, 4])); + expect(idx.isubset([0, 1, 2, 3]).labels()).toEqual(new Int32Array([0, 1, 2, 3])); + + expect(idx.subset([0, 1, 2, 3, 4])).toBeInstanceOf(Dataframe.IdentityInt32Index); + expect(idx.subset([2, 1, 0])).toBeInstanceOf(Dataframe.IdentityInt32Index); + expect(idx.subset([1, 2, 3, 4])).toBeInstanceOf(Dataframe.DenseInt32Index); + expect(idx.subset([0, 1, 3, 4])).toBeInstanceOf(Dataframe.DenseInt32Index); + expect(idx.subset([0, 1, 2, 3, 10])).toBeInstanceOf(Dataframe.DenseInt32Index); + expect(idx.subset([4, 3, 2, 1])).toBeInstanceOf(Dataframe.DenseInt32Index); + expect(idx.subset([4])).toBeInstanceOf(Dataframe.KeyIndex); + + expect(idx.withLabel(99).labels()).toEqual(new Int32Array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 99])); + expect(idx.dropLabel(0).labels()).toEqual(new Int32Array([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11])); + expect(idx.dropLabel(11).labels()).toEqual(new Int32Array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10])); + expect(idx.dropLabel(5).labels()).toEqual(new Int32Array([0, 1, 2, 3, 4, 6, 7, 8, 9, 10, 11])); + }); + + test("DenseInt32Index", () => { + + const idx = new Dataframe.DenseInt32Index([99, 1002, 48, 0, 22]); + + expect(Dataframe.isLabelIndex(idx)).toBeTruthy(); + + expect(idx.labels()).toEqual(new Int32Array([99, 1002, 48, 0, 22])); + expect(idx.size()).toEqual(5); + expect(idx.getOffset(1002)).toEqual(1); + expect(idx.getOffset(0)).toEqual(3); + expect(idx.getLabel(0)).toEqual(99); + expect(idx.getLabels(new Int32Array([2, 4]))).toEqual(new Int32Array([48, 22])); + expect(idx.getLabels([2, 4])).toEqual([48, 22]); + expect(idx.getOffsets([0, 48])).toEqual([3, 2]); + + expect(idx.subset([1002, 0, 99]).labels()).toEqual(new Int32Array([1002, 0, 99])) + expect(idx.getOffsets(idx.subset([1002, 0, 99]).labels())).toEqual(new Int32Array([1, 3, 0])); + expect(idx.isubset([4, 1, 2]).labels()).toEqual(new Int32Array([22, 1002, 48])); + + expect(idx.withLabel(88).labels()).toEqual(new Int32Array([99, 1002, 48, 0, 22, 88])); + expect(idx.withLabel(88).getOffset(88)).toEqual(5); + expect(idx.dropLabel(48).labels()).toEqual(new Int32Array([99, 1002, 0, 22])); + }); + + test("KeyIndex", () => { + const idx = new Dataframe.KeyIndex(["red", "green", "blue"]); + + expect(Dataframe.isLabelIndex(idx)).toBeTruthy(); + + expect(idx.labels()).toEqual(["red", "green", "blue"]); + expect(idx.size()).toEqual(3); + expect(idx.getOffset("blue")).toEqual(2); + expect(idx.getLabel(1)).toEqual("green"); + + expect(idx.subset(["green"]).labels()).toEqual(["green"]); + expect(idx.subset(["green", "red"]).labels()).toEqual(["green", "red"]); + expect(idx.isubset([2, 1, 0]).labels()).toEqual(["blue", "green", "red"]); + + expect(idx.withLabel("yo").labels()).toEqual(["red", "green", "blue", "yo"]); + expect(idx.withLabel("yo").getOffset("yo")).toEqual(3); + expect(idx.dropLabel("blue").labels()).toEqual(["red", "green"]); +}); + +}) \ No newline at end of file diff --git a/client/__tests__/util/stateManager/controlsHelpers.test.js b/client/__tests__/util/stateManager/controlsHelpers.test.js index eed02a14..923d7f5a 100644 --- a/client/__tests__/util/stateManager/controlsHelpers.test.js +++ b/client/__tests__/util/stateManager/controlsHelpers.test.js @@ -4,11 +4,21 @@ test controls helpers import { subsetAndResetGeneLists } from "../../../src/util/stateManager/controlsHelpers"; import * as globals from "../../../src/globals"; + describe("controls helpers", () => { test("subsetAndResetGeneLists", () => { - const geneList = [...Array(150).keys()].map( - () => Math.random().toString(36).substring(2, 6) // random string of 4 characters - ); + const geneList = []; + const genRandGene = () => Math.random().toString(36).substring(2, 6); + + // build a unique set of genes + for (let i = 0; i < 150; i += 1) { + let randGene = genRandGene(); + while (geneList.includes(randGene)) randGene = genRandGene(); + geneList.push(randGene); + } + // insert duplicates + geneList[0] = "dupl"; + geneList[20] = "dupl"; const state = { userDefinedGenes: geneList.slice(0, 20), diffexpGenes: geneList.slice(20), @@ -16,12 +26,14 @@ describe("controls helpers", () => { const [newUserDefinedGenes, newDiffExpGenes] = subsetAndResetGeneLists( state ); + const expectedNewUserDefinedGenes = [ + ...geneList.slice(0, 20), + ...geneList.slice(21) + ].slice(0, globals.maxGenes); expect(globals.maxUserDefinedGenes).toBeLessThan(globals.maxGenes); expect(geneList.length).toBeGreaterThan(globals.maxGenes); expect(newUserDefinedGenes).toHaveLength(globals.maxGenes); - expect(newUserDefinedGenes).toStrictEqual( - geneList.slice(0, globals.maxGenes) - ); + expect(newUserDefinedGenes).toStrictEqual(expectedNewUserDefinedGenes); expect(newDiffExpGenes).toStrictEqual([]); }); }); diff --git a/client/__tests__/util/stateManager/fbs.test.js b/client/__tests__/util/stateManager/fbs.test.js index c7c639e7..b8bd3742 100644 --- a/client/__tests__/util/stateManager/fbs.test.js +++ b/client/__tests__/util/stateManager/fbs.test.js @@ -27,7 +27,7 @@ describe("encode/decode", () => { const dfWithColIdx = new Dataframe([3, 4], columns, null, colIndex); const dfB = decodeMatrixFBS(encodeMatrixFBS(dfWithColIdx)); expect([dfB.nRows, dfB.nCols]).toEqual(dfWithColIdx.dims); - expect(dfB.colIdx).toEqual(colIndex.keys()); + expect(dfB.colIdx).toEqual(colIndex.labels()); expect(dfB.rowIdx).toBeNull(); expect(dfB.columns).toEqual(columns); }); diff --git a/client/__tests__/util/stateManager/universe.test.js b/client/__tests__/util/stateManager/universe.test.js index 76964723..bf89058c 100644 --- a/client/__tests__/util/stateManager/universe.test.js +++ b/client/__tests__/util/stateManager/universe.test.js @@ -1,4 +1,5 @@ import * as Universe from "../../../src/util/stateManager/universe"; +import { matrixFBSToDataframe } from "../../../src/util/stateManager/matrix"; import * as Dataframe from "../../../src/util/dataframe"; import * as REST from "./sampleResponses"; @@ -51,16 +52,13 @@ describe("createUniverseFromResponse", () => { ...universe, ...Universe.addObsAnnotations( universe, - Universe.matrixFBSToDataframe(REST.annotationsObs) + matrixFBSToDataframe(REST.annotationsObs) ), ...Universe.addVarAnnotations( universe, - Universe.matrixFBSToDataframe(REST.annotationsVar) - ), - ...Universe.addObsLayout( - universe, - Universe.matrixFBSToDataframe(REST.layoutObs) + matrixFBSToDataframe(REST.annotationsVar) ), + ...Universe.addObsLayout(universe, matrixFBSToDataframe(REST.layoutObs)), }; expect(universe).toMatchObject( @@ -80,7 +78,7 @@ describe("createUniverseFromResponse", () => { REST.schema.schema.annotations.obs.columns.length, ]); expect(universe.obsLayout.dims).toEqual([nObs, 2]); - expect(universe.obsLayout.colIndex.keys()).toEqual( + expect(universe.obsLayout.colIndex.labels()).toEqual( universe.schema.layout.obs[0].dims ); expect(universe.varAnnotations.dims).toEqual([ diff --git a/client/__tests__/util/stateManager/world.test.js b/client/__tests__/util/stateManager/world.test.js index 6b6a5bfb..ec6adc76 100644 --- a/client/__tests__/util/stateManager/world.test.js +++ b/client/__tests__/util/stateManager/world.test.js @@ -1,5 +1,6 @@ import _ from "lodash"; import * as Universe from "../../../src/util/stateManager/universe"; +import { matrixFBSToDataframe } from "../../../src/util/stateManager/matrix"; import * as World from "../../../src/util/stateManager/world"; import * as Dataframe from "../../../src/util/dataframe"; import Crossfilter from "../../../src/util/typedCrossfilter"; @@ -26,16 +27,13 @@ const defaultBigBang = () => { ...universe, ...Universe.addObsAnnotations( universe, - Universe.matrixFBSToDataframe(REST.annotationsObs) + matrixFBSToDataframe(REST.annotationsObs) ), ...Universe.addVarAnnotations( universe, - Universe.matrixFBSToDataframe(REST.annotationsVar) - ), - ...Universe.addObsLayout( - universe, - Universe.matrixFBSToDataframe(REST.layoutObs) + matrixFBSToDataframe(REST.annotationsVar) ), + ...Universe.addObsLayout(universe, matrixFBSToDataframe(REST.layoutObs)), }; /* create world */ @@ -59,9 +57,9 @@ describe("createWorldFromEntireUniverse", () => { const universe = Universe.createUniverseFromResponse( _.cloneDeep(REST.config), _.cloneDeep(REST.schema), - Universe.matrixFBSToDataframe(_.cloneDeep(REST.annotationsObs)), - Universe.matrixFBSToDataframe(_.cloneDeep(REST.annotationsVar)), - Universe.matrixFBSToDataframe(_.cloneDeep(REST.layoutObs)) + matrixFBSToDataframe(_.cloneDeep(REST.annotationsObs)), + matrixFBSToDataframe(_.cloneDeep(REST.annotationsVar)), + matrixFBSToDataframe(_.cloneDeep(REST.layoutObs)) ); expect(universe).toBeDefined(); @@ -144,16 +142,16 @@ describe("createWorldFromCurrentSelection", () => { }) ); - expect(world.obsAnnotations.rowIndex.keys()).toEqual( + expect(world.obsAnnotations.rowIndex.labels()).toEqual( new Int32Array(matchingIndices) ); - expect(world.obsAnnotations.colIndex.keys()).toEqual( - universe.obsAnnotations.colIndex.keys() + expect(world.obsAnnotations.colIndex.labels()).toEqual( + universe.obsAnnotations.colIndex.labels() ); - expect(world.obsLayout.rowIndex.keys()).toEqual( + expect(world.obsLayout.rowIndex.labels()).toEqual( new Int32Array(matchingIndices) ); - expect(world.obsLayout.colIndex.keys()).toEqual( + expect(world.obsLayout.colIndex.labels()).toEqual( world.schema.layout.obs[0].dims ); }); diff --git a/client/configuration/eslint/eslint.js b/client/configuration/eslint/eslint.js index dd3c4a20..5ebc32ef 100644 --- a/client/configuration/eslint/eslint.js +++ b/client/configuration/eslint/eslint.js @@ -1,7 +1,7 @@ module.exports = { root: true, parser: "babel-eslint", - extends: ["airbnb", "prettier", "prettier/react"], + extends: ["airbnb", "plugin:prettier/recommended", "prettier/react"], env: { browser: true, commonjs: true, es6: true }, globals: { expect: true }, parserOptions: { @@ -21,13 +21,7 @@ module.exports = { "react/jsx-filename-extension": "off", "comma-dangle": "off", "no-underscore-dangle": "off", - quotes: ["error", "double"], "implicit-arrow-linebreak": "off", - "operator-linebreak": [ - "error", - "after", - { overrides: { "?": "before", ":": "before" } }, - ], "no-console": "off", "spaced-comment": ["error", "always", { exceptions: ["*"] }], "no-param-reassign": "off", diff --git a/client/configuration/lint-staged/lint-staged.config.js b/client/configuration/lint-staged/lint-staged.config.js new file mode 100644 index 00000000..8d9c56b8 --- /dev/null +++ b/client/configuration/lint-staged/lint-staged.config.js @@ -0,0 +1,3 @@ +module.exports = { + "./src/**/*.js": "eslint --fix", +}; diff --git a/client/package-lock.json b/client/package-lock.json index e26d8b33..8a1d16c6 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -2547,6 +2547,15 @@ } } }, + "@samverschueren/stream-to-observable": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@samverschueren/stream-to-observable/-/stream-to-observable-0.3.0.tgz", + "integrity": "sha512-MI4Xx6LHs4Webyvi6EbspgyAb4D2Q2VtnCQ1blOJcoLS6mVa8lNN2rkIy1CVxfTUpoyIbCTkXES1rLXztFD1lg==", + "dev": true, + "requires": { + "any-observable": "^0.3.0" + } + }, "@sentry/cli": { "version": "1.52.3", "resolved": "https://registry.npmjs.org/@sentry/cli/-/cli-1.52.3.tgz", @@ -2744,6 +2753,12 @@ "integrity": "sha512-uM4mnmsIIPK/yeO+42F2RQhGUIs39K2RFmugcJANppXe6J1nvH87PvzPZYpza7Xhhs8Yn9yIAVdLZ84z61+0xQ==", "dev": true }, + "@types/parse-json": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.0.tgz", + "integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==", + "dev": true + }, "@types/prettier": { "version": "1.19.1", "resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-1.19.1.tgz", @@ -3263,6 +3278,12 @@ "integrity": "sha512-uMgjozySS8adZZYePpaWs8cxB9/kdzmpX6SgJZ+wbz1K5eYk5QMYDVJaZKhxyIHUdnnJkfR7SVgStgH7LkGUyg==", "dev": true }, + "any-observable": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/any-observable/-/any-observable-0.3.0.tgz", + "integrity": "sha512-/FQM1EDkTsf63Ub2C6O7GuYFDsSXUwsaZDurV0np41ocwq0jthUAYCmhBX9f+KwlaCgIuWyr/4WlUQUBfKfZog==", + "dev": true + }, "anymatch": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.1.tgz", @@ -5378,6 +5399,60 @@ "restore-cursor": "^3.1.0" } }, + "cli-truncate": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-2.1.0.tgz", + "integrity": "sha512-n8fOixwDD6b/ObinzTrp1ZKFzbgvKZvuz/TvejnLn1aQfC6r52XEx85FmuC+3HI+JM7coBRXUvNqEU2PHVrHpg==", + "dev": true, + "requires": { + "slice-ansi": "^3.0.0", + "string-width": "^4.2.0" + }, + "dependencies": { + "ansi-styles": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", + "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", + "dev": true, + "requires": { + "@types/color-name": "^1.1.1", + "color-convert": "^2.0.1" + } + }, + "astral-regex": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", + "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", + "dev": true + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "slice-ansi": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-3.0.0.tgz", + "integrity": "sha512-pSyv7bSTC7ig9Dcgbw9AuRNUb5k5V6oDudjZoMBSr13qpLBG7tB+zgCkARjq7xIUgdz5P1Qe8u+rSGdouOOIyQ==", + "dev": true, + "requires": { + "ansi-styles": "^4.0.0", + "astral-regex": "^2.0.0", + "is-fullwidth-code-point": "^3.0.0" + } + } + } + }, "cli-width": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-2.2.0.tgz", @@ -5585,6 +5660,12 @@ "integrity": "sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=", "dev": true }, + "compare-versions": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/compare-versions/-/compare-versions-3.6.0.tgz", + "integrity": "sha512-W6Af2Iw1z4CB7q4uU4hv646dW9GQuBM+YpC0UvUCWSD8w90SJjp+ujJuXaEMtAXBtSqGfMPuFOVn4/+FlaqfBA==", + "dev": true + }, "component-emitter": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz", @@ -6517,6 +6598,12 @@ "mimic-response": "^2.0.0" } }, + "dedent": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/dedent/-/dedent-0.7.0.tgz", + "integrity": "sha1-JJXduvbrh0q7Dhvp3yLS5aVEMmw=", + "dev": true + }, "deep-equal": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.1.1.tgz", @@ -6548,6 +6635,23 @@ "integrity": "sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==", "dev": true }, + "defaults": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.3.tgz", + "integrity": "sha1-xlYFHpgX2f8I7YgUd/P+QBnz730=", + "dev": true, + "requires": { + "clone": "^1.0.2" + }, + "dependencies": { + "clone": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", + "integrity": "sha1-2jCcwmPfFZlMaIypAheco8fNfH4=", + "dev": true + } + } + }, "define-properties": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", @@ -6911,6 +7015,12 @@ "integrity": "sha512-zcUd1p/7yzTSdWkCTrqGvbnEOASy96d0RJL/lc5BDJoO23Z3G/VHd0yIPbguDU9n8QNUTCigLO7oEdtOb7fp2A==", "dev": true }, + "elegant-spinner": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/elegant-spinner/-/elegant-spinner-2.0.0.tgz", + "integrity": "sha512-5YRYHhvhYzV/FC4AiMdeSIg3jAYGq9xFvbhZMpPlJoBsfYgrw2DSCYeXfat6tYBu45PWiyRr3+flaCPPmviPaA==", + "dev": true + }, "elliptic": { "version": "6.5.2", "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.2.tgz", @@ -6976,6 +7086,15 @@ } } }, + "enquirer": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.3.5.tgz", + "integrity": "sha512-BNT1C08P9XD0vNg3J475yIUG+mVdp9T6towYFHUv897X0KoHBjB1shyrNmhmtHWKP17iSWgo7Gqh7BBuzLZMSA==", + "dev": true, + "requires": { + "ansi-colors": "^3.2.1" + } + }, "entities": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/entities/-/entities-2.0.0.tgz", @@ -7411,6 +7530,15 @@ } } }, + "eslint-plugin-prettier": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-3.1.3.tgz", + "integrity": "sha512-+HG5jmu/dN3ZV3T6eCD7a4BlAySdN7mLIbJYo0z1cFQuI+r2DiTJEFeF68ots93PsnrMxbzIZ2S/ieX+mkrBeQ==", + "dev": true, + "requires": { + "prettier-linter-helpers": "^1.0.0" + } + }, "eslint-plugin-react": { "version": "7.19.0", "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.19.0.tgz", @@ -7907,6 +8035,12 @@ "integrity": "sha512-8UEa58QDLauDNfpbrX55Q9jrGHThw2ZMdOky5Gl1CDtVeJDPVrG4Jxx1N8jw2gkWaff5UUuX1KJd+9zGe2B+ZA==", "dev": true }, + "fast-diff": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.2.0.tgz", + "integrity": "sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w==", + "dev": true + }, "fast-json-stable-stringify": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", @@ -8290,6 +8424,15 @@ "locate-path": "^3.0.0" } }, + "find-versions": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/find-versions/-/find-versions-3.2.0.tgz", + "integrity": "sha512-P8WRou2S+oe222TOCHitLy8zj+SIsVJh52VP4lvXkaFVnOFFdoWv1H1Jjvel1aI6NCFOAaeAVm8qrI0odiLcww==", + "dev": true, + "requires": { + "semver-regex": "^2.0.0" + } + }, "findup-sync": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/findup-sync/-/findup-sync-3.0.0.tgz", @@ -8694,6 +8837,12 @@ "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", "dev": true }, + "get-own-enumerable-property-symbols": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/get-own-enumerable-property-symbols/-/get-own-enumerable-property-symbols-3.0.2.tgz", + "integrity": "sha512-I0UBV/XOz1XkIJHEUDMZAbzCThU/H8DxmSfmdGcKPnVhu2VfFqr34jr9777IyaTYvxjedWhqVIilEDsCdP5G6g==", + "dev": true + }, "get-stdin": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-6.0.0.tgz", @@ -9301,6 +9450,100 @@ "integrity": "sha512-SEQu7vl8KjNL2eoGBLF3+wAjpsNfA9XMlXAYj/3EdaNfAlxKthD1xjEQfGOUhllCGGJVNY34bRr6lPINhNjyZw==", "dev": true }, + "husky": { + "version": "4.2.5", + "resolved": "https://registry.npmjs.org/husky/-/husky-4.2.5.tgz", + "integrity": "sha512-SYZ95AjKcX7goYVZtVZF2i6XiZcHknw50iXvY7b0MiGoj5RwdgRQNEHdb+gPDPCXKlzwrybjFjkL6FOj8uRhZQ==", + "dev": true, + "requires": { + "chalk": "^4.0.0", + "ci-info": "^2.0.0", + "compare-versions": "^3.6.0", + "cosmiconfig": "^6.0.0", + "find-versions": "^3.2.0", + "opencollective-postinstall": "^2.0.2", + "pkg-dir": "^4.2.0", + "please-upgrade-node": "^3.2.0", + "slash": "^3.0.0", + "which-pm-runs": "^1.0.0" + }, + "dependencies": { + "cosmiconfig": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-6.0.0.tgz", + "integrity": "sha512-xb3ZL6+L8b9JLLCx3ZdoZy4+2ECphCMo2PwqgP1tlfVq6M6YReyzBJtvWWtbDSpNr9hn96pkCiZqUcFEc+54Qg==", + "dev": true, + "requires": { + "@types/parse-json": "^4.0.0", + "import-fresh": "^3.1.0", + "parse-json": "^5.0.0", + "path-type": "^4.0.0", + "yaml": "^1.7.2" + } + }, + "find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "requires": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + } + }, + "locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "requires": { + "p-locate": "^4.1.0" + } + }, + "p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "requires": { + "p-limit": "^2.2.0" + } + }, + "parse-json": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.0.0.tgz", + "integrity": "sha512-OOY5b7PAEFV0E2Fir1KOkxchnZNCdowAJgQ5NuxjpBKTRP3pQhwkrkxqQjeoKJ+fO7bCpmIZaogI4eZGDMEGOw==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-better-errors": "^1.0.1", + "lines-and-columns": "^1.1.6" + } + }, + "path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true + }, + "path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "dev": true + }, + "pkg-dir": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "dev": true, + "requires": { + "find-up": "^4.0.0" + } + } + } + }, "iconv-lite": { "version": "0.4.24", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", @@ -9869,6 +10112,12 @@ "has": "^1.0.3" } }, + "is-regexp": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-regexp/-/is-regexp-1.0.0.tgz", + "integrity": "sha1-/S2INUXEa6xaYz57mgnof6LLUGk=", + "dev": true + }, "is-resolvable": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/is-resolvable/-/is-resolvable-1.1.0.tgz", @@ -11739,6 +11988,208 @@ "type-check": "~0.3.2" } }, + "lines-and-columns": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.1.6.tgz", + "integrity": "sha1-HADHQ7QzzQpOgHWPe2SldEDZ/wA=", + "dev": true + }, + "lint-staged": { + "version": "10.2.4", + "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-10.2.4.tgz", + "integrity": "sha512-doTMGKXQAT34c3S3gwDrTnXmCZp/z1/92D8suPqqh755sKPT18ew1NoPNHxJdrvv1D4WrJ7CEnx79Ns3EdEFbg==", + "dev": true, + "requires": { + "chalk": "^4.0.0", + "commander": "^5.1.0", + "cosmiconfig": "^6.0.0", + "debug": "^4.1.1", + "dedent": "^0.7.0", + "execa": "^4.0.1", + "listr2": "^2.0.2", + "log-symbols": "^4.0.0", + "micromatch": "^4.0.2", + "normalize-path": "^3.0.0", + "please-upgrade-node": "^3.2.0", + "string-argv": "0.3.1", + "stringify-object": "^3.3.0" + }, + "dependencies": { + "commander": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-5.1.0.tgz", + "integrity": "sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg==", + "dev": true + }, + "cosmiconfig": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-6.0.0.tgz", + "integrity": "sha512-xb3ZL6+L8b9JLLCx3ZdoZy4+2ECphCMo2PwqgP1tlfVq6M6YReyzBJtvWWtbDSpNr9hn96pkCiZqUcFEc+54Qg==", + "dev": true, + "requires": { + "@types/parse-json": "^4.0.0", + "import-fresh": "^3.1.0", + "parse-json": "^5.0.0", + "path-type": "^4.0.0", + "yaml": "^1.7.2" + } + }, + "cross-spawn": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.2.tgz", + "integrity": "sha512-PD6G8QG3S4FK/XCGFbEQrDqO2AnMMsy0meR7lerlIOHAAbkuavGU/pOqprrlvfTNjvowivTeBsjebAL0NSoMxw==", + "dev": true, + "requires": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + } + }, + "execa": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-4.0.1.tgz", + "integrity": "sha512-SCjM/zlBdOK8Q5TIjOn6iEHZaPHFsMoTxXQ2nvUvtPnuohz3H2dIozSg+etNR98dGoYUp2ENSKLL/XaMmbxVgw==", + "dev": true, + "requires": { + "cross-spawn": "^7.0.0", + "get-stream": "^5.0.0", + "human-signals": "^1.1.1", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.0", + "onetime": "^5.1.0", + "signal-exit": "^3.0.2", + "strip-final-newline": "^2.0.0" + } + }, + "get-stream": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.1.0.tgz", + "integrity": "sha512-EXr1FOzrzTfGeL0gQdeFEvOMm2mzMOglyiOXSTpPC+iAjAKftbr3jpCMWynogwYnM+eSj9sHGc6wjIcDvYiygw==", + "dev": true, + "requires": { + "pump": "^3.0.0" + } + }, + "is-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.0.tgz", + "integrity": "sha512-XCoy+WlUr7d1+Z8GgSuXmpuUFC9fOhRXglJMx+dwLKTkL44Cjd4W1Z5P+BQZpr+cR93aGP4S/s7Ftw6Nd/kiEw==", + "dev": true + }, + "npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "dev": true, + "requires": { + "path-key": "^3.0.0" + } + }, + "parse-json": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.0.0.tgz", + "integrity": "sha512-OOY5b7PAEFV0E2Fir1KOkxchnZNCdowAJgQ5NuxjpBKTRP3pQhwkrkxqQjeoKJ+fO7bCpmIZaogI4eZGDMEGOw==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-better-errors": "^1.0.1", + "lines-and-columns": "^1.1.6" + } + }, + "path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true + }, + "path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "dev": true + }, + "shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "requires": { + "shebang-regex": "^3.0.0" + } + }, + "shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true + }, + "which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + } + } + }, + "listr2": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/listr2/-/listr2-2.0.4.tgz", + "integrity": "sha512-oJaAcplPsa72rKW0eg4P4LbEJjhH+UO2I8uqR/I2wzHrVg16ohSfUy0SlcHS21zfYXxtsUpL8YXGHjyfWMR0cg==", + "dev": true, + "requires": { + "@samverschueren/stream-to-observable": "^0.3.0", + "chalk": "^4.0.0", + "cli-cursor": "^3.1.0", + "cli-truncate": "^2.1.0", + "elegant-spinner": "^2.0.0", + "enquirer": "^2.3.5", + "figures": "^3.2.0", + "indent-string": "^4.0.0", + "log-update": "^4.0.0", + "p-map": "^4.0.0", + "pad": "^3.2.0", + "rxjs": "^6.5.5", + "through": "^2.3.8", + "uuid": "^7.0.2" + }, + "dependencies": { + "indent-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "dev": true + }, + "p-map": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz", + "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==", + "dev": true, + "requires": { + "aggregate-error": "^3.0.0" + } + }, + "rxjs": { + "version": "6.5.5", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.5.5.tgz", + "integrity": "sha512-WfQI+1gohdf0Dai/Bbmk5L5ItH5tYqm3ki2c5GdWhKjalzjg93N3avFjVStyZZz+A2Em+ZxKH5bNghw9UeylGQ==", + "dev": true, + "requires": { + "tslib": "^1.9.0" + } + }, + "uuid": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-7.0.3.tgz", + "integrity": "sha512-DPSke0pXhTZgoF/d+WSt2QaKMCFSfx7QegxEWT+JOuHF5aWrKEn0G+ztjuJg/gG8/ItK+rbPCD/yNv8yyih6Cg==", + "dev": true + } + } + }, "load-bmfont": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/load-bmfont/-/load-bmfont-1.4.0.tgz", @@ -11948,6 +12399,71 @@ "integrity": "sha1-E2Xt9DFIBIHvDRxolXpe2Z1J984=", "dev": true }, + "log-symbols": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.0.0.tgz", + "integrity": "sha512-FN8JBzLx6CzeMrB0tg6pqlGU1wCrXW+ZXGH481kfsBqer0hToTIiHdjH4Mq8xJUbvATujKCvaREGWpGUionraA==", + "dev": true, + "requires": { + "chalk": "^4.0.0" + } + }, + "log-update": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/log-update/-/log-update-4.0.0.tgz", + "integrity": "sha512-9fkkDevMefjg0mmzWFBW8YkFP91OrizzkW3diF7CpG+S2EYdy4+TVfGwz1zeF8x7hCx1ovSPTOE9Ngib74qqUg==", + "dev": true, + "requires": { + "ansi-escapes": "^4.3.0", + "cli-cursor": "^3.1.0", + "slice-ansi": "^4.0.0", + "wrap-ansi": "^6.2.0" + }, + "dependencies": { + "ansi-styles": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", + "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", + "dev": true, + "requires": { + "@types/color-name": "^1.1.1", + "color-convert": "^2.0.1" + } + }, + "astral-regex": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", + "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", + "dev": true + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "slice-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz", + "integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==", + "dev": true, + "requires": { + "ansi-styles": "^4.0.0", + "astral-regex": "^2.0.0", + "is-fullwidth-code-point": "^3.0.0" + } + } + } + }, "lolex": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/lolex/-/lolex-5.1.2.tgz", @@ -12903,6 +13419,12 @@ "mimic-fn": "^2.1.0" } }, + "opencollective-postinstall": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/opencollective-postinstall/-/opencollective-postinstall-2.0.2.tgz", + "integrity": "sha512-pVOEP16TrAO2/fjej1IdOyupJY8KDUM1CvsaScRbw6oddvpQoOfGk4ywha0HKKVAD6RkW4x6Q+tNBwhf3Bgpuw==", + "dev": true + }, "optimize-css-assets-webpack-plugin": { "version": "5.0.3", "resolved": "https://registry.npmjs.org/optimize-css-assets-webpack-plugin/-/optimize-css-assets-webpack-plugin-5.0.3.tgz", @@ -13022,6 +13544,15 @@ "semver": "^5.1.0" } }, + "pad": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/pad/-/pad-3.2.0.tgz", + "integrity": "sha512-2u0TrjcGbOjBTJpyewEl4hBO3OeX5wWue7eIFPzQTg6wFSvoaHcBTTUY5m+n0hd04gmTCPuY0kCpVIVuw5etwg==", + "dev": true, + "requires": { + "wcwidth": "^1.0.1" + } + }, "pako": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", @@ -13376,6 +13907,15 @@ } } }, + "please-upgrade-node": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/please-upgrade-node/-/please-upgrade-node-3.2.0.tgz", + "integrity": "sha512-gQR3WpIgNIKwBMVLkpMUeR3e1/E1y42bqDQZfql+kDeXd8COYfM8PQA4X6y7a8u9Ua9FHmsrrmirW2vHs45hWg==", + "dev": true, + "requires": { + "semver-compare": "^1.0.0" + } + }, "pn": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/pn/-/pn-1.1.0.tgz", @@ -14041,6 +14581,21 @@ "integrity": "sha1-1PRWKwzjaW5BrFLQ4ALlemNdxtw=", "dev": true }, + "prettier": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.0.5.tgz", + "integrity": "sha512-7PtVymN48hGcO4fGjybyBSIWDsLU4H4XlvOHfq91pz9kkGlonzwTfYkaIEwiRg/dAJF9YlbsduBAgtYLi+8cFg==", + "dev": true + }, + "prettier-linter-helpers": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz", + "integrity": "sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==", + "dev": true, + "requires": { + "fast-diff": "^1.1.2" + } + }, "pretty-bytes": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-4.0.2.tgz", @@ -15456,6 +16011,12 @@ "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", "dev": true }, + "semver-compare": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/semver-compare/-/semver-compare-1.0.0.tgz", + "integrity": "sha1-De4hahyUGrN+nvsXiPavxf9VN/w=", + "dev": true + }, "semver-diff": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/semver-diff/-/semver-diff-2.1.0.tgz", @@ -15465,6 +16026,12 @@ "semver": "^5.0.3" } }, + "semver-regex": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/semver-regex/-/semver-regex-2.0.0.tgz", + "integrity": "sha512-mUdIBBvdn0PLOeP3TEkMH7HHeUP3GjsXCwKarjv/kGmUFOYg1VqEemKhoQpWMu6X2I8kHeuVdGibLGkVK+/5Qw==", + "dev": true + }, "send": { "version": "0.17.1", "resolved": "https://registry.npmjs.org/send/-/send-0.17.1.tgz", @@ -16154,6 +16721,12 @@ "integrity": "sha1-J5siXfHVgrH1TmWt3UNS4Y+qBxM=", "dev": true }, + "string-argv": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/string-argv/-/string-argv-0.3.1.tgz", + "integrity": "sha512-a1uQGz7IyVy9YwhqjZIZu1c8JO8dNIe20xBmSS6qu9kv++k3JGzCVmprbNN5Kn+BgzD5E7YYwg1CcjuJMRNsvg==", + "dev": true + }, "string-length": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/string-length/-/string-length-3.1.0.tgz", @@ -16227,6 +16800,17 @@ "safe-buffer": "~5.1.0" } }, + "stringify-object": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/stringify-object/-/stringify-object-3.3.0.tgz", + "integrity": "sha512-rHqiFh1elqCQ9WPLIC8I0Q/g/wj5J1eMkyoiD6eoQApWHP0FtlK7rqnhmabL5VUY9JQCcqwwvlOaSuutekgyrw==", + "dev": true, + "requires": { + "get-own-enumerable-property-symbols": "^3.0.0", + "is-obj": "^1.0.1", + "is-regexp": "^1.0.0" + } + }, "strip-ansi": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", @@ -17775,6 +18359,15 @@ "neo-async": "^2.5.0" } }, + "wcwidth": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz", + "integrity": "sha1-8LDc+RW8X/FSivrbLA4XtTLaL+g=", + "dev": true, + "requires": { + "defaults": "^1.0.3" + } + }, "webidl-conversions": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-4.0.2.tgz", @@ -18535,6 +19128,12 @@ "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", "dev": true }, + "yaml": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.0.tgz", + "integrity": "sha512-yr2icI4glYaNG+KWONODapy2/jDdMSDnrONSjblABjD9B4Z5LgiircSt8m8sRZFNi08kG9Sm0uSHtEmP3zaEGg==", + "dev": true + }, "yargs": { "version": "15.3.1", "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.3.1.tgz", diff --git a/client/package.json b/client/package.json index 449a136a..545ddadb 100644 --- a/client/package.json +++ b/client/package.json @@ -84,6 +84,7 @@ "eslint-plugin-import": "^2.20.2", "eslint-plugin-jest": "^23.8.2", "eslint-plugin-jsx-a11y": "^6.2.3", + "eslint-plugin-prettier": "^3.1.3", "eslint-plugin-react": "^7.19.0", "eslint-plugin-react-hooks": "^2.5.1", "express": "^4.17.1", @@ -91,11 +92,14 @@ "file-loader": "^6.0.0", "html-webpack-inline-source-plugin": "^1.0.0-beta.2", "html-webpack-plugin": "^4.0.0", + "husky": "^4.2.5", "jest": "^25.2.7", "jest-puppeteer": "^4.4.0", "json-loader": "^0.5.7", + "lint-staged": "^10.2.4", "mini-css-extract-plugin": "^0.9.0", "optimize-css-assets-webpack-plugin": "^5.0.3", + "prettier": "^2.0.5", "puppeteer": "^2.1.1", "rimraf": "^3.0.2", "serve-favicon": "^2.5.0", @@ -146,5 +150,10 @@ ] } } + }, + "husky": { + "hooks": { + "pre-commit": "lint-staged --config \"./configuration/lint-staged/lint-staged.config.js\"" + } } } diff --git a/client/src/actions/index.js b/client/src/actions/index.js index 0bfffd51..ada975d3 100644 --- a/client/src/actions/index.js +++ b/client/src/actions/index.js @@ -29,7 +29,7 @@ async function obsAnnotationFetchAndLoad(dispatch, schema) { fetchBinary( `annotations/obs?annotation-name=${encodeURIComponent(col.name)}` ) - .then((buffer) => Universe.matrixFBSToDataframe(buffer)) + .then((buffer) => MatrixFBS.matrixFBSToDataframe(buffer)) .then((df) => dispatch({ type: "universe: column load success", @@ -52,7 +52,7 @@ async function varAnnotationFetchAndLoad(dispatch, schema) { return Promise.all( names.map((name) => fetchBinary(`annotations/var?annotation-name=${encodeURIComponent(name)}`) - .then((buffer) => Universe.matrixFBSToDataframe(buffer)) + .then((buffer) => MatrixFBS.matrixFBSToDataframe(buffer)) .then((df) => dispatch({ type: "universe: column load success", @@ -77,7 +77,7 @@ function layoutFetchAndLoad(dispatch, schema) { plimit.add(() => fetchBinary( `layout/obs?layout-name=${encodeURIComponent(e)}` - ).then((buffer) => Universe.matrixFBSToDataframe(buffer)) + ).then((buffer) => MatrixFBS.matrixFBSToDataframe(buffer)) ) ) ).then((dfs) => diff --git a/client/src/actions/reembed.js b/client/src/actions/reembed.js index f337d70d..7d73337e 100644 --- a/client/src/actions/reembed.js +++ b/client/src/actions/reembed.js @@ -1,5 +1,5 @@ import { API } from "../globals"; -import { Universe } from "../util/stateManager"; +import { MatrixFBS } from "../util/stateManager"; import { postNetworkErrorToast, postAsyncSuccessToast, @@ -24,7 +24,7 @@ function abortableFetch(request, opts, timeout = 0) { async function doReembedFetch(dispatch, getState) { const state = getState(); - let cells = state.world.obsAnnotations.rowIndex.keys(); + let cells = state.world.obsAnnotations.rowIndex.labels(); // These lines ensure that we convert any TypedArray to an Array. // This is necessary because JSON.stringify() does some very strange @@ -80,7 +80,7 @@ export function requestReembed() { const res = await doReembedFetch(dispatch, getState); const schema = JSON.parse(res.headers.get("CxG-Schema")); const buffer = await res.arrayBuffer(); - const df = Universe.matrixFBSToDataframe(buffer); + const df = MatrixFBS.matrixFBSToDataframe(buffer); dispatch({ type: "reembed: request completed", }); diff --git a/client/src/components/app.js b/client/src/components/app.js index ad949228..14ea5dc2 100644 --- a/client/src/components/app.js +++ b/client/src/components/app.js @@ -21,7 +21,6 @@ import actions from "../actions"; graphRenderCounter: state.controls.graphRenderCounter, })) class App extends React.Component { - componentDidMount() { const { dispatch } = this.props; @@ -68,22 +67,21 @@ class App extends React.Component { error loading ) : null} - {loading ? null : - - {viewportRef => - <> - - - - - - - } - - } + {loading ? null : ( + + + {(viewportRef) => ( + <> + + + + + + + )} + + + )} ); } diff --git a/client/src/components/categorical/category/annoMenuCategory.js b/client/src/components/categorical/category/annoMenuCategory.js index 4df30db2..b1190f67 100644 --- a/client/src/components/categorical/category/annoMenuCategory.js +++ b/client/src/components/categorical/category/annoMenuCategory.js @@ -6,9 +6,13 @@ import { MenuItem, Popover, Position, + Tooltip, + Icon, PopoverInteractionKind, } from "@blueprintjs/core"; +import * as globals from "../../../globals"; + @connect((state) => ({ annotations: state.annotations, })) @@ -56,46 +60,57 @@ class AnnoMenuCategory extends React.PureComponent { return ( <> {isUserAnno ? ( - - - - - - } - > -