diff --git a/.vscode/launch.json b/.vscode/launch.json index 8aec1d53ac..46684edb43 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -11,7 +11,7 @@ "port": 9222, "url": "http://localhost:9876", "urlFilter": "http://localhost:9876*", - "webRoot": "${workspaceFolder}/packages/openneuro-app/src", + "webRoot": "${workspaceFolder}/packages/openneuro-app/src" } ] } diff --git a/__mocks__/ioredis.js b/__mocks__/ioredis.js index 1405f6faa4..799bc7fc2c 100644 --- a/__mocks__/ioredis.js +++ b/__mocks__/ioredis.js @@ -1,3 +1,3 @@ -import Redis from 'ioredis-mock' +import Redis from "ioredis-mock" export { Redis } export default Redis diff --git a/__mocks__/superagent.js b/__mocks__/superagent.js index d43db83070..49a4b4dad1 100644 --- a/__mocks__/superagent.js +++ b/__mocks__/superagent.js @@ -1,4 +1,4 @@ -import { vi } from 'vitest' +import { vi } from "vitest" // mock for superagent - __mocks__/superagent.js class MockResponse { @@ -19,7 +19,7 @@ class MockResponse { } } -const createRequestStub = obj => vi.fn(() => obj) +const createRequestStub = (obj) => vi.fn(() => obj) function Request() { this.mockResponse = new MockResponse() @@ -34,7 +34,7 @@ function Request() { this.set = createRequestStub(this) this.accept = createRequestStub(this) this.timeout = createRequestStub(this) - this.then = cb => { + this.then = (cb) => { return new Promise((resolve, reject) => { if (this.mockError) { return reject(this.mockError) @@ -42,7 +42,7 @@ function Request() { return resolve(cb(this.mockResponse)) }) } - this.end = vi.fn().mockImplementation(callback => { + this.end = vi.fn().mockImplementation((callback) => { if (this.mockDelay) { this.delayTimer = setTimeout( callback, @@ -57,13 +57,13 @@ function Request() { callback(this.mockError, this.mockResponse) }) //expose helper methods for tests to set - this.__setMockDelay = boolValue => { + this.__setMockDelay = (boolValue) => { this.mockDelay = boolValue } - this.__setMockResponse = mockRes => { + this.__setMockResponse = (mockRes) => { this.mockResponse = mockRes } - this.__setMockError = mockErr => { + this.__setMockError = (mockErr) => { this.mockError = mockErr } } diff --git a/babel.config.js b/babel.config.js index 1df5b89f9a..a249be1638 100644 --- a/babel.config.js +++ b/babel.config.js @@ -1,30 +1,30 @@ -module.exports = function(api) { +module.exports = function (api) { api.cache.never() return { plugins: [ - '@loadable/babel-plugin', - '@babel/plugin-proposal-object-rest-spread', - '@babel/plugin-proposal-class-properties', - '@babel/syntax-dynamic-import', - '@babel/plugin-proposal-optional-chaining', - ['@babel/plugin-transform-runtime', { corejs: 3 }], + "@loadable/babel-plugin", + "@babel/plugin-proposal-object-rest-spread", + "@babel/plugin-proposal-class-properties", + "@babel/syntax-dynamic-import", + "@babel/plugin-proposal-optional-chaining", + ["@babel/plugin-transform-runtime", { corejs: 3 }], ], presets: [ - '@babel/preset-react', + "@babel/preset-react", [ - '@babel/preset-env', + "@babel/preset-env", { targets: { - chrome: '63', - firefox: '60', - safari: '11', + chrome: "63", + firefox: "60", + safari: "11", }, - useBuiltIns: 'usage', + useBuiltIns: "usage", corejs: 3, }, ], - '@babel/preset-typescript', + "@babel/preset-typescript", ], - sourceType: 'unambiguous', + sourceType: "unambiguous", } } diff --git a/deno.json b/deno.json index e3bc82bc48..3e151e5aca 100644 --- a/deno.json +++ b/deno.json @@ -1,6 +1,16 @@ { "fmt": { "semiColons": false, - "singleQuote": true + "proseWrap": "preserve", + "exclude": [ + ".yarn/", + ".pnp.cjs", + ".pnp.loader.mjs", + "packages/openneuro-app/src/scripts/utils/schema-validator.js", + "dist", + "packages/**/dist", + "services/datalad/tests/.pytest_cache", + "**/__pycache/**" + ] } } diff --git a/docs/api.md b/docs/api.md index 8ad7627f13..11eba63d85 100644 --- a/docs/api.md +++ b/docs/api.md @@ -210,7 +210,7 @@ mutation { ### Deleting Files/Folders You can remove files or folders from the currend draft with the `deleteFiles` mutation. Multiple -arguments can be provided in the changes array for batch deletion of paths. Paths provided in +arguments can be provided in the changes array for batch deletion of paths. Paths provided in argument are relative to the dataset root, omitting a filename a with `""` will delete the folder provided via the path argument. For examples see below: diff --git a/docs/faq.md b/docs/faq.md index e734fd4d07..7970b6968a 100644 --- a/docs/faq.md +++ b/docs/faq.md @@ -10,12 +10,12 @@ Yes! ## Are there any restrictions on the uploaded data? -Yes. By uploading this dataset to OpenNeuro you have to agree to the following conditions: +Yes. By uploading this dataset to OpenNeuro you have to agree to the following conditions: -* You are the owner of this dataset and have any necessary ethics permissions to share the data publicly. -* This dataset does not include any identifiable personal health information as defined by the Health Insurance Portability and Accountability Act of 1996 (including names, zip codes, dates of birth, acquisition dates, facial features on structural scans, etc.). -* You agree that this dataset will become publicly available under a Creative Commons CC0 license after a grace period of 36 months counted from the first successful version of the dataset. -* This dataset is not subject to GDPR protections. +- You are the owner of this dataset and have any necessary ethics permissions to share the data publicly. +- This dataset does not include any identifiable personal health information as defined by the Health Insurance Portability and Accountability Act of 1996 (including names, zip codes, dates of birth, acquisition dates, facial features on structural scans, etc.). +- You agree that this dataset will become publicly available under a Creative Commons CC0 license after a grace period of 36 months counted from the first successful version of the dataset. +- This dataset is not subject to GDPR protections. ## What if I will not be able to publish my paper in 36 months? @@ -25,7 +25,7 @@ You can apply for up to two 6-month long extensions of the grace period. To appl Yes! We recommend using the Open Brain Consent - [Ultimate consent form](https://open-brain-consent.readthedocs.io/en/stable/ultimate.html). -* For GDPR protected studies, they have a [Ultimate consent form GDPR edition](https://open-brain-consent.readthedocs.io/en/stable/gdpr/ultimate_gdpr.html). +- For GDPR protected studies, they have a [Ultimate consent form GDPR edition](https://open-brain-consent.readthedocs.io/en/stable/gdpr/ultimate_gdpr.html). ## Do I need to format my data in some special way before uploading it to OpenNeuro? @@ -47,9 +47,9 @@ We offer two options for uploading data onto OpenNeuro. The first is to upload v When OpenNeuro first began accepting data, we hosted datasets that were dedicated to the public domain (CC0 or PDDL) or released under the CC-BY license. The idea of accepting CC-BY licenses was to reflect the academic norm of citing sources, but it fails to achieve that goal. In [CC BY and data: Not always a good fit](https://osc.universityofcalifornia.edu/2016/09/cc-by-and-data-not-always-a-good-fit/), it is argued: -> **CC licenses are not sufficient for ensuring proper attribution in many cases because their restrictions — including attribution — do not apply to facts.** +> **CC licenses are not sufficient for ensuring proper attribution in many cases because their restrictions — including attribution — do not apply to facts.** > ... > **CC licenses' attribution requirements aren't necessary because scholars have very good reasons to provide attribution that has nothing to do with copyright** \[...] Data that comes from nowhere has little credibility. If someone wants to use data as persuasive evidence, they need to refer readers and reviewers back to its source: who it came from and how it was produced. -CC-BY places an ambiguous legal hurdle between researchers and data they are considering using, even if only intended to enforce standard academic practice. To reduce uncertainty for data consumers, all newly published datasets are released under CC0, as are new versions of previously released datasets. We do nonetheless want to encourage proper attribution of datasets hosted on OpenNeuro. Each version of a dataset is assigned a unique DOI, enabling researchers to cite the version of a dataset they analyzed. Additionally, all OpenNeuro datasets may include a ["HowToAcknowledge" field](https://bids-specification.readthedocs.io/en/stable/03-modality-agnostic-files.html#dataset_descriptionjson), in which dataset providers may provide specific instructions for users for what they consider an appropriate citation. +CC-BY places an ambiguous legal hurdle between researchers and data they are considering using, even if only intended to enforce standard academic practice. To reduce uncertainty for data consumers, all newly published datasets are released under CC0, as are new versions of previously released datasets. We do nonetheless want to encourage proper attribution of datasets hosted on OpenNeuro. Each version of a dataset is assigned a unique DOI, enabling researchers to cite the version of a dataset they analyzed. Additionally, all OpenNeuro datasets may include a ["HowToAcknowledge" field](https://bids-specification.readthedocs.io/en/stable/03-modality-agnostic-files.html#dataset_descriptionjson), in which dataset providers may provide specific instructions for users for what they consider an appropriate citation. diff --git a/docs/index.md b/docs/index.md index 6665f344cc..c80fc98a0b 100644 --- a/docs/index.md +++ b/docs/index.md @@ -5,7 +5,7 @@ for sharing neuroimaging data. ## Getting started -If you are a looking for datasets, [OpenNeuro Search][] allows you to search public +If you are a looking for datasets, [OpenNeuro Search][OpenNeuro Search] allows you to search public datasets. If you are considering uploading datasets to OpenNeuro, or recommending OpenNeuro as diff --git a/docs/policy/data_management_plans.md b/docs/policy/data_management_plans.md index 46b084d8b2..8a8983b4cc 100644 --- a/docs/policy/data_management_plans.md +++ b/docs/policy/data_management_plans.md @@ -37,54 +37,54 @@ upload data to OpenNeuro: The National Institutes of Health has published a list of "desirable characteristics" for data repositories: - **Unique Persistent Identifiers** : > Assigns datasets a citable, unique persistent identifier, such as a digital object identifier (DOI) or accession number, to support data discovery, reporting, and research assessment. The identifier points to a persistent landing page that remains accessible even if the dataset is de-accessioned or no longer available. - OpenNeuro: - * assigns internal accession numbers (for example, `ds000001`) on initial upload - * generates a [DOI](https://www.doi.org/) for each version of a dataset - * maintains landing pages for published datasets that have been removed +OpenNeuro: + +- assigns internal accession numbers (for example, `ds000001`) on initial upload +- generates a [DOI](https://www.doi.org/) for each version of a dataset +- maintains landing pages for published datasets that have been removed **Long-Term Sustainability** : > Has a plan for long-term management of data, including maintaining integrity, authenticity, and availability of datasets; building on a stable technical infrastructure and funding plans; and having contingency plans to ensure data are available and maintained during and after unforeseen events. - OpenNeuro's [](data_retention.md) detail the technical approaches to maintaining integrity, authenticity - and availability, including redundant distribution mechanisms that do not rely upon the availability of - the primary website. - The source code and deployment specifications are maintained in public repositories, - using industry-standard technologies that are not tied to any one hosting provider. - OpenNeuro is funded through an NIMH R24 grant, a five-year funding mechanism that enables long-term - stability. +OpenNeuro's [](data_retention.md) detail the technical approaches to maintaining integrity, authenticity +and availability, including redundant distribution mechanisms that do not rely upon the availability of +the primary website. +The source code and deployment specifications are maintained in public repositories, +using industry-standard technologies that are not tied to any one hosting provider. +OpenNeuro is funded through an NIMH R24 grant, a five-year funding mechanism that enables long-term +stability. **Metadata** : > Ensures datasets are accompanied by metadata to enable discovery, reuse, and citation of datasets, using schema that are appropriate to, and ideally widely used across, the community(ies) the repository serves. Domain-specific repositories would generally have more detailed metadata than generalist repositories. - OpenNeuro requires all datasets be uploaded in [BIDS][] format, a community-developed - standard for organizing data and metadata. OpenNeuro extracts metadata from the datasets - to populate the dataset landing page and enable search queries. +OpenNeuro requires all datasets be uploaded in [BIDS][BIDS] format, a community-developed +standard for organizing data and metadata. OpenNeuro extracts metadata from the datasets +to populate the dataset landing page and enable search queries. **Curation and Quality Assurance** : > Provides, or has a mechanism for others to provide, expert curation and quality assurance to improve the accuracy and integrity of datasets and metadata. - OpenNeuro permits dataset owners to grant anonymous review access to private datasets, - as well as write access to trusted users, if they wish to seek expert assistance. - OpenNeuro *does not* currently provide curation assistance or a method for interested third - parties to annotate datasets without the author's consent. - Users may leave comments on datasets. +OpenNeuro permits dataset owners to grant anonymous review access to private datasets, +as well as write access to trusted users, if they wish to seek expert assistance. +OpenNeuro _does not_ currently provide curation assistance or a method for interested third +parties to annotate datasets without the author's consent. +Users may leave comments on datasets. **Free and Easy Access** : > Provides broad, equitable, and maximally open access to datasets and their metadata free of charge in a timely manner after submission, consistent with legal and ethical limits required to maintain privacy and confidentiality, Tribal sovereignty, and protection of other sensitive data. - OpenNeuro provides on-demand access to all published datasets through multiple download mechanisms, - free of charge, over the Internet. - All data and metadata are released into the Public Domain. +OpenNeuro provides on-demand access to all published datasets through multiple download mechanisms, +free of charge, over the Internet. +All data and metadata are released into the Public Domain. **Broad and Measured Reuse** : > Makes datasets and their metadata available with broadest possible terms of reuse; and provides the ability to measure attribution, citation, and reuse of data (i.e., through assignment of adequate metadata and unique PIDs). - OpenNeuro is working toward providing mechanisms for measuring dataset downloads and - linking to related digital objects like journal articles. +OpenNeuro is working toward providing mechanisms for measuring dataset downloads and +linking to related digital objects like journal articles. **Clear Use Guidance** : > Provides accompanying documentation describing terms of dataset access and use (e.g., particular licenses, need for approval by a data use committee). @@ -92,8 +92,8 @@ for data repositories: **Security and Integrity** : > Has documented measures in place to meet generally accepted criteria for preventing unauthorized access to, modification of, or release of data, with levels of security that are appropriate to the sensitivity of data. - OpenNeuro currently only hosts datasets that do not require restricted access, as attested by - the dataset uploader. +OpenNeuro currently only hosts datasets that do not require restricted access, as attested by +the dataset uploader. **Confidentiality** : > Has documented capabilities for ensuring that administrative, technical, and physical safeguards are employed to comply with applicable confidentiality, risk management, and continuous monitoring requirements for sensitive data. @@ -101,19 +101,18 @@ for data repositories: **Common Format** : > Allows datasets and metadata downloaded, accessed, or exported from the repository to be in widely used, preferably non-proprietary, formats consistent with those used in the community(ies) the repository serves. - All OpenNeuro datasets are formatted according to the [BIDS][] standard. - The DataLad access mechanism retrieves the dataset in the form of a [git][]/[git-annex][] repository. +All OpenNeuro datasets are formatted according to the [BIDS][BIDS] standard. +The DataLad access mechanism retrieves the dataset in the form of a [git][git]/[git-annex][git-annex] repository. **Provenance** : > Has mechanisms in place to record the origin, chain of custody, and any modifications to submitted datasets and metadata. - All changes to datasets are recorded in the [git][] history of each dataset. +All changes to datasets are recorded in the [git][git] history of each dataset. **Retention Policy** : > Provides documentation on policies for data retention within the repository. - These policies may be found at [](data_retention.md). - +These policies may be found at [](data_retention.md). [BIDS]: https://bids.neuroimaging.io [NIHbmic]: https://www.nlm.nih.gov/NIHbmic/domain_specific_repositories.html diff --git a/docs/user_guide.md b/docs/user_guide.md index 3e9dea74d5..d07229b376 100644 --- a/docs/user_guide.md +++ b/docs/user_guide.md @@ -19,9 +19,9 @@ To access public datasets, click on the PUBLIC DASHBOARD button in the navigatio ![navigation bar, logged in, public dashboard selected](./assets/nav-bar-logged-in-public-dashboard.png) -On the Public Dashboard, datasets can be searched for by keyword or sorted by various criteria. +On the Public Dashboard, datasets can be searched for by keyword or sorted by various criteria. -Visible from the dashboard are each dataset's high-level stats and information. To access a dataset's landing page, click on its name. +Visible from the dashboard are each dataset's high-level stats and information. To access a dataset's landing page, click on its name. ![public dashboard, dataset item](./assets/public-dashboard-dataset-item.png) @@ -34,6 +34,7 @@ A dataset's landing page has four major sections: [a toolbar](#the-toolbar), a [ ![dataset page toolbar](./assets/dataset-page-toolbar.png) Located at the top of the page, the toolbar contains the following options (icons left to right): + - Follow/Subscribe: Get email updates when a new version is posted. - Like/Save: Add dataset to your Dashboard under Saved Datasets. - View metadata: View data not necessarily contained within the dataset, including it's DOI, OpenNeuro ID and URL, study type and design, number of trials, etc. This data helps make datasets searchable, and a compilation of the metadata of all datasets on OpenNeuro can be found at [metadata.openneuro.org](http://metadata.openneuro.org/). @@ -51,6 +52,7 @@ Changes to dataset information are tracked in the dataset's snapshots (versions) ### Dataset Snapshot This is the main section of the dataset landing page, containing almost all of its information and files at the given snapshot. Here, you can: + - View a dataset's information (extracted from its README and description.json files). - View and download individual files. The file viewer supports the following file extensions: csv, html, txt, json, tsv, nii, and nii.gz. - Download the dataset via the browser. @@ -66,15 +68,15 @@ Once you've signed into OpenNeuro, you'll be able to upload your dataset by hitt There are a few things to keep in mind before uploading a dataset: - 1. You must be the owner of the dataset and have the necessary ethics permissions to share the data publicly. +1. You must be the owner of the dataset and have the necessary ethics permissions to share the data publicly. - 2. The dataset may not be subject to GDPR protections. +2. The dataset may not be subject to GDPR protections. - 3. The dataset must follow [BIDS](https://bids.neuroimaging.io/) specifications. Your dataset will be validated on upload, but if you want to check it for yourself, a web-based tool is available [here](https://bids-standard.github.io/bids-validator/), and a cli tool can be found [here](https://github.com/bids-standard/bids-validator). +3. The dataset must follow [BIDS](https://bids.neuroimaging.io/) specifications. Your dataset will be validated on upload, but if you want to check it for yourself, a web-based tool is available [here](https://bids-standard.github.io/bids-validator/), and a cli tool can be found [here](https://github.com/bids-standard/bids-validator). - 4. You will need to ensure that either all structural scans have been defaced (we recommend the [pydeface](https://github.com/poldracklab/pydeface) tool) or that you have explicit participant consent and ethical authorization to publish without defacing. +4. You will need to ensure that either all structural scans have been defaced (we recommend the [pydeface](https://github.com/poldracklab/pydeface) tool) or that you have explicit participant consent and ethical authorization to publish without defacing. - 5. You must be willing that the dataset becomes publicly available under the [Creative Commons CC0 license](https://creativecommons.org/share-your-work/public-domain/cc0/) upon publishing or after a grace period of 36 months from the date of its first snapshot creation. +5. You must be willing that the dataset becomes publicly available under the [Creative Commons CC0 license](https://creativecommons.org/share-your-work/public-domain/cc0/) upon publishing or after a grace period of 36 months from the date of its first snapshot creation. The first upload prompt will ask you to select the dataset's folder from your computer. @@ -82,7 +84,7 @@ The first upload prompt will ask you to select the dataset's folder from your co Once you've selected and uploaded the dataset, it will be checked by the BIDS validator. The dataset may be uploaded with warnings, but errors must be resolved before the upload can be finalized. -The next step is to fill out the metadata form, which contains information not accessible from the dataset itself and is used to facilitate search results. This is optional, but highly recommended. +The next step is to fill out the metadata form, which contains information not accessible from the dataset itself and is used to facilitate search results. This is optional, but highly recommended. Finally, you will need to accept the Terms and Conditions and affirm that the data has been either defaced or given consent to be made public otherwise. Once you have done so, the dataset will begin uploading in the background and you'll be free to navigate the site (but don't reload it or close the window, or the process will be cut short). When the dataset has finished uploading, you'll see a notification pop up in the bottom-right corner of the page. @@ -101,7 +103,8 @@ The draft page information is nearly identical to the snapshot pages, the differ ![owned dataset page toolbar](./assets/own-dataset-page-toolbar.png) The dataset toolbar will have a few new items, including: -- Publish: Make your dataset public. This will make it available under the CC0 license. + +- Publish: Make your dataset public. This will make it available under the CC0 license. - Delete: Permanently remove your dataset from OpenNeuro. If you are deleting the dataset to replace it, a redirect url can be submitted with the delete form. - Manage Permissions: Add collaborators to the dataset and manage their read/write permissions. - Create Snapshot: Create a new version of this dataset with changes made to Draft. diff --git a/packages/openneuro-app/__mocks__/fileMock.js b/packages/openneuro-app/__mocks__/fileMock.js index 0e56c5b5f7..ebf20155e6 100644 --- a/packages/openneuro-app/__mocks__/fileMock.js +++ b/packages/openneuro-app/__mocks__/fileMock.js @@ -1 +1 @@ -module.exports = 'test-file-stub' +module.exports = "test-file-stub" diff --git a/packages/openneuro-app/pluralize-esm.js b/packages/openneuro-app/pluralize-esm.js index 1cd134cc57..d044eaf9a8 100644 --- a/packages/openneuro-app/pluralize-esm.js +++ b/packages/openneuro-app/pluralize-esm.js @@ -16,8 +16,8 @@ var irregularSingles = {} * @return {RegExp} */ function sanitizeRule(rule) { - if (typeof rule === 'string') { - return new RegExp('^' + rule + '$', 'i') + if (typeof rule === "string") { + return new RegExp("^" + rule + "$", "i") } return rule @@ -59,7 +59,7 @@ function restoreCase(word, token) { */ function interpolate(str, args) { return str.replace(/\$(\d{1,2})/g, function (match, index) { - return args[index] || '' + return args[index] || "" }) } @@ -74,7 +74,7 @@ function replace(word, rule) { return word.replace(rule[0], function (match, index) { var result = interpolate(rule[1], arguments) - if (match === '') { + if (match === "") { return restoreCase(word[index - 1], result) } @@ -159,10 +159,11 @@ function checkWord(replaceMap, keepMap, rules, bool) { * @return {string} */ function pluralize(word, count, inclusive) { - var pluralized = - count === 1 ? pluralize.singular(word) : pluralize.plural(word) + var pluralized = count === 1 + ? pluralize.singular(word) + : pluralize.plural(word) - return (inclusive ? count + ' ' : '') + pluralized + return (inclusive ? count + " " : "") + pluralized } /** @@ -227,14 +228,14 @@ pluralize.addSingularRule = function (rule, replacement) { * @param {(string|RegExp)} word */ pluralize.addUncountableRule = function (word) { - if (typeof word === 'string') { + if (typeof word === "string") { uncountables[word.toLowerCase()] = true return } // Set singular and plural references for the word. - pluralize.addPluralRule(word, '$0') - pluralize.addSingularRule(word, '$0') + pluralize.addPluralRule(word, "$0") + pluralize.addSingularRule(word, "$0") } /** @@ -249,258 +250,250 @@ pluralize.addIrregularRule = function (single, plural) { irregularSingles[single] = plural irregularPlurals[plural] = single -} - -/** +} /** * Irregular rules. */ ;[ // Pronouns. - ['I', 'we'], - ['me', 'us'], - ['he', 'they'], - ['she', 'they'], - ['them', 'them'], - ['myself', 'ourselves'], - ['yourself', 'yourselves'], - ['itself', 'themselves'], - ['herself', 'themselves'], - ['himself', 'themselves'], - ['themself', 'themselves'], - ['is', 'are'], - ['was', 'were'], - ['has', 'have'], - ['this', 'these'], - ['that', 'those'], + ["I", "we"], + ["me", "us"], + ["he", "they"], + ["she", "they"], + ["them", "them"], + ["myself", "ourselves"], + ["yourself", "yourselves"], + ["itself", "themselves"], + ["herself", "themselves"], + ["himself", "themselves"], + ["themself", "themselves"], + ["is", "are"], + ["was", "were"], + ["has", "have"], + ["this", "these"], + ["that", "those"], // Words ending in with a consonant and `o`. - ['echo', 'echoes'], - ['dingo', 'dingoes'], - ['volcano', 'volcanoes'], - ['tornado', 'tornadoes'], - ['torpedo', 'torpedoes'], + ["echo", "echoes"], + ["dingo", "dingoes"], + ["volcano", "volcanoes"], + ["tornado", "tornadoes"], + ["torpedo", "torpedoes"], // Ends with `us`. - ['genus', 'genera'], - ['viscus', 'viscera'], + ["genus", "genera"], + ["viscus", "viscera"], // Ends with `ma`. - ['stigma', 'stigmata'], - ['stoma', 'stomata'], - ['dogma', 'dogmata'], - ['lemma', 'lemmata'], - ['schema', 'schemata'], - ['anathema', 'anathemata'], + ["stigma", "stigmata"], + ["stoma", "stomata"], + ["dogma", "dogmata"], + ["lemma", "lemmata"], + ["schema", "schemata"], + ["anathema", "anathemata"], // Other irregular rules. - ['ox', 'oxen'], - ['axe', 'axes'], - ['die', 'dice'], - ['yes', 'yeses'], - ['foot', 'feet'], - ['eave', 'eaves'], - ['goose', 'geese'], - ['tooth', 'teeth'], - ['quiz', 'quizzes'], - ['human', 'humans'], - ['proof', 'proofs'], - ['carve', 'carves'], - ['valve', 'valves'], - ['looey', 'looies'], - ['thief', 'thieves'], - ['groove', 'grooves'], - ['pickaxe', 'pickaxes'], - ['passerby', 'passersby'], + ["ox", "oxen"], + ["axe", "axes"], + ["die", "dice"], + ["yes", "yeses"], + ["foot", "feet"], + ["eave", "eaves"], + ["goose", "geese"], + ["tooth", "teeth"], + ["quiz", "quizzes"], + ["human", "humans"], + ["proof", "proofs"], + ["carve", "carves"], + ["valve", "valves"], + ["looey", "looies"], + ["thief", "thieves"], + ["groove", "grooves"], + ["pickaxe", "pickaxes"], + ["passerby", "passersby"], ].forEach(function (rule) { return pluralize.addIrregularRule(rule[0], rule[1]) -}) - -/** +}) /** * Pluralization rules. */ ;[ - [/s?$/i, 's'], - [/[^\u0000-\u007F]$/i, '$0'], - [/([^aeiou]ese)$/i, '$1'], - [/(ax|test)is$/i, '$1es'], - [/(alias|[^aou]us|t[lm]as|gas|ris)$/i, '$1es'], - [/(e[mn]u)s?$/i, '$1s'], - [/([^l]ias|[aeiou]las|[ejzr]as|[iu]am)$/i, '$1'], + [/s?$/i, "s"], + [/[^\u0000-\u007F]$/i, "$0"], + [/([^aeiou]ese)$/i, "$1"], + [/(ax|test)is$/i, "$1es"], + [/(alias|[^aou]us|t[lm]as|gas|ris)$/i, "$1es"], + [/(e[mn]u)s?$/i, "$1s"], + [/([^l]ias|[aeiou]las|[ejzr]as|[iu]am)$/i, "$1"], [ /(alumn|syllab|vir|radi|nucle|fung|cact|stimul|termin|bacill|foc|uter|loc|strat)(?:us|i)$/i, - '$1i', + "$1i", ], - [/(alumn|alg|vertebr)(?:a|ae)$/i, '$1ae'], - [/(seraph|cherub)(?:im)?$/i, '$1im'], - [/(her|at|gr)o$/i, '$1oes'], + [/(alumn|alg|vertebr)(?:a|ae)$/i, "$1ae"], + [/(seraph|cherub)(?:im)?$/i, "$1im"], + [/(her|at|gr)o$/i, "$1oes"], [ /(agend|addend|millenni|dat|extrem|bacteri|desiderat|strat|candelabr|errat|ov|symposi|curricul|automat|quor)(?:a|um)$/i, - '$1a', + "$1a", ], [ /(apheli|hyperbat|periheli|asyndet|noumen|phenomen|criteri|organ|prolegomen|hedr|automat)(?:a|on)$/i, - '$1a', + "$1a", ], - [/sis$/i, 'ses'], - [/(?:(kni|wi|li)fe|(ar|l|ea|eo|oa|hoo)f)$/i, '$1$2ves'], - [/([^aeiouy]|qu)y$/i, '$1ies'], - [/([^ch][ieo][ln])ey$/i, '$1ies'], - [/(x|ch|ss|sh|zz)$/i, '$1es'], - [/(matr|cod|mur|sil|vert|ind|append)(?:ix|ex)$/i, '$1ices'], - [/\b((?:tit)?m|l)(?:ice|ouse)$/i, '$1ice'], - [/(pe)(?:rson|ople)$/i, '$1ople'], - [/(child)(?:ren)?$/i, '$1ren'], - [/eaux$/i, '$0'], - [/m[ae]n$/i, 'men'], - ['thou', 'you'], + [/sis$/i, "ses"], + [/(?:(kni|wi|li)fe|(ar|l|ea|eo|oa|hoo)f)$/i, "$1$2ves"], + [/([^aeiouy]|qu)y$/i, "$1ies"], + [/([^ch][ieo][ln])ey$/i, "$1ies"], + [/(x|ch|ss|sh|zz)$/i, "$1es"], + [/(matr|cod|mur|sil|vert|ind|append)(?:ix|ex)$/i, "$1ices"], + [/\b((?:tit)?m|l)(?:ice|ouse)$/i, "$1ice"], + [/(pe)(?:rson|ople)$/i, "$1ople"], + [/(child)(?:ren)?$/i, "$1ren"], + [/eaux$/i, "$0"], + [/m[ae]n$/i, "men"], + ["thou", "you"], ].forEach(function (rule) { return pluralize.addPluralRule(rule[0], rule[1]) -}) - -/** +}) /** * Singularization rules. */ ;[ - [/s$/i, ''], - [/(ss)$/i, '$1'], - [/(wi|kni|(?:after|half|high|low|mid|non|night|[^\w]|^)li)ves$/i, '$1fe'], - [/(ar|(?:wo|[ae])l|[eo][ao])ves$/i, '$1f'], - [/ies$/i, 'y'], - [/(dg|ss|ois|lk|ok|wn|mb|th|ch|ec|oal|is|ck|ix|sser|ts|wb)ies$/i, '$1ie'], + [/s$/i, ""], + [/(ss)$/i, "$1"], + [/(wi|kni|(?:after|half|high|low|mid|non|night|[^\w]|^)li)ves$/i, "$1fe"], + [/(ar|(?:wo|[ae])l|[eo][ao])ves$/i, "$1f"], + [/ies$/i, "y"], + [/(dg|ss|ois|lk|ok|wn|mb|th|ch|ec|oal|is|ck|ix|sser|ts|wb)ies$/i, "$1ie"], [ /\b(l|(?:neck|cross|hog|aun)?t|coll|faer|food|gen|goon|group|hipp|junk|vegg|(?:pork)?p|charl|calor|cut)ies$/i, - '$1ie', + "$1ie", ], - [/\b(mon|smil)ies$/i, '$1ey'], - [/\b((?:tit)?m|l)ice$/i, '$1ouse'], - [/(seraph|cherub)im$/i, '$1'], + [/\b(mon|smil)ies$/i, "$1ey"], + [/\b((?:tit)?m|l)ice$/i, "$1ouse"], + [/(seraph|cherub)im$/i, "$1"], [ /(x|ch|ss|sh|zz|tto|go|cho|alias|[^aou]us|t[lm]as|gas|(?:her|at|gr)o|[aeiou]ris)(?:es)?$/i, - '$1', + "$1", ], [ /(analy|diagno|parenthe|progno|synop|the|empha|cri|ne)(?:sis|ses)$/i, - '$1sis', + "$1sis", ], - [/(movie|twelve|abuse|e[mn]u)s$/i, '$1'], - [/(test)(?:is|es)$/i, '$1is'], + [/(movie|twelve|abuse|e[mn]u)s$/i, "$1"], + [/(test)(?:is|es)$/i, "$1is"], [ /(alumn|syllab|vir|radi|nucle|fung|cact|stimul|termin|bacill|foc|uter|loc|strat)(?:us|i)$/i, - '$1us', + "$1us", ], [ /(agend|addend|millenni|dat|extrem|bacteri|desiderat|strat|candelabr|errat|ov|symposi|curricul|quor)a$/i, - '$1um', + "$1um", ], [ /(apheli|hyperbat|periheli|asyndet|noumen|phenomen|criteri|organ|prolegomen|hedr|automat)a$/i, - '$1on', + "$1on", ], - [/(alumn|alg|vertebr)ae$/i, '$1a'], - [/(cod|mur|sil|vert|ind)ices$/i, '$1ex'], - [/(matr|append)ices$/i, '$1ix'], - [/(pe)(rson|ople)$/i, '$1rson'], - [/(child)ren$/i, '$1'], - [/(eau)x?$/i, '$1'], - [/men$/i, 'man'], + [/(alumn|alg|vertebr)ae$/i, "$1a"], + [/(cod|mur|sil|vert|ind)ices$/i, "$1ex"], + [/(matr|append)ices$/i, "$1ix"], + [/(pe)(rson|ople)$/i, "$1rson"], + [/(child)ren$/i, "$1"], + [/(eau)x?$/i, "$1"], + [/men$/i, "man"], ].forEach(function (rule) { return pluralize.addSingularRule(rule[0], rule[1]) -}) - -/** +}) /** * Uncountable rules. */ ;[ // Singular words with no plurals. - 'adulthood', - 'advice', - 'agenda', - 'aid', - 'aircraft', - 'alcohol', - 'ammo', - 'analytics', - 'anime', - 'athletics', - 'audio', - 'bison', - 'blood', - 'bream', - 'buffalo', - 'butter', - 'carp', - 'cash', - 'chassis', - 'chess', - 'clothing', - 'cod', - 'commerce', - 'cooperation', - 'corps', - 'debris', - 'diabetes', - 'digestion', - 'elk', - 'energy', - 'equipment', - 'excretion', - 'expertise', - 'firmware', - 'flounder', - 'fun', - 'gallows', - 'garbage', - 'graffiti', - 'hardware', - 'headquarters', - 'health', - 'herpes', - 'highjinks', - 'homework', - 'housework', - 'information', - 'jeans', - 'justice', - 'kudos', - 'labour', - 'literature', - 'machinery', - 'mackerel', - 'mail', - 'media', - 'mews', - 'moose', - 'music', - 'mud', - 'manga', - 'news', - 'only', - 'personnel', - 'pike', - 'plankton', - 'pliers', - 'police', - 'pollution', - 'premises', - 'rain', - 'research', - 'rice', - 'salmon', - 'scissors', - 'series', - 'sewage', - 'shambles', - 'shrimp', - 'software', - 'staff', - 'swine', - 'tennis', - 'traffic', - 'transportation', - 'trout', - 'tuna', - 'wealth', - 'welfare', - 'whiting', - 'wildebeest', - 'wildlife', - 'you', + "adulthood", + "advice", + "agenda", + "aid", + "aircraft", + "alcohol", + "ammo", + "analytics", + "anime", + "athletics", + "audio", + "bison", + "blood", + "bream", + "buffalo", + "butter", + "carp", + "cash", + "chassis", + "chess", + "clothing", + "cod", + "commerce", + "cooperation", + "corps", + "debris", + "diabetes", + "digestion", + "elk", + "energy", + "equipment", + "excretion", + "expertise", + "firmware", + "flounder", + "fun", + "gallows", + "garbage", + "graffiti", + "hardware", + "headquarters", + "health", + "herpes", + "highjinks", + "homework", + "housework", + "information", + "jeans", + "justice", + "kudos", + "labour", + "literature", + "machinery", + "mackerel", + "mail", + "media", + "mews", + "moose", + "music", + "mud", + "manga", + "news", + "only", + "personnel", + "pike", + "plankton", + "pliers", + "police", + "pollution", + "premises", + "rain", + "research", + "rice", + "salmon", + "scissors", + "series", + "sewage", + "shambles", + "shrimp", + "software", + "staff", + "swine", + "tennis", + "traffic", + "transportation", + "trout", + "tuna", + "wealth", + "welfare", + "whiting", + "wildebeest", + "wildlife", + "you", /pok[eé]mon$/i, // Regexes. /[^aeiou]ese$/i, // "chinese", "japanese" diff --git a/packages/openneuro-app/src/@types/custom.d.ts b/packages/openneuro-app/src/@types/custom.d.ts index bf78eb1f8d..b0d0ea9ab4 100644 --- a/packages/openneuro-app/src/@types/custom.d.ts +++ b/packages/openneuro-app/src/@types/custom.d.ts @@ -1,18 +1,18 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ // Allow .png imports -declare module '*.png' { +declare module "*.png" { const value: string export = value } // Allow .svg imports -declare module '*.svg' { +declare module "*.svg" { const value: string export = value } // Allow .scss imports -declare module '*.scss' { +declare module "*.scss" { const value: string export = value } diff --git a/packages/openneuro-app/src/@types/react.d.ts b/packages/openneuro-app/src/@types/react.d.ts index aa7ed8f9bb..d30b029519 100644 --- a/packages/openneuro-app/src/@types/react.d.ts +++ b/packages/openneuro-app/src/@types/react.d.ts @@ -1,12 +1,11 @@ -import React from 'react' +import React from "react" /** * Required to use the webkitdirectory attribute on */ -declare module 'react' { +declare module "react" { interface InputHTMLAttributes - extends AriaAttributes, - DOMAttributes { + extends AriaAttributes, DOMAttributes { directory?: string webkitdirectory?: string } diff --git a/packages/openneuro-app/src/client.jsx b/packages/openneuro-app/src/client.jsx index d5a0154a90..52d1c644a1 100644 --- a/packages/openneuro-app/src/client.jsx +++ b/packages/openneuro-app/src/client.jsx @@ -1,20 +1,20 @@ /** * Browser client entrypoint - see server.tsx for SSR entrypoint */ -import './scripts/utils/global-polyfill' -import './scripts/apm.js' -import { ApolloProvider, InMemoryCache } from '@apollo/client' -import { createClient } from '@openneuro/client' -import React from 'react' -import ReactDOM from 'react-dom' -import { BrowserRouter, Route, Routes } from 'react-router-dom' -import App from './scripts/app' -import Index from './scripts/index' -import { version } from './lerna.json' -import { config } from './scripts/config' -import * as gtag from './scripts/utils/gtag' -import { relayStylePagination } from '@apollo/client/utilities' -import '@openneuro/components/page/page.scss' +import "./scripts/utils/global-polyfill" +import "./scripts/apm.js" +import { ApolloProvider, InMemoryCache } from "@apollo/client" +import { createClient } from "@openneuro/client" +import React from "react" +import ReactDOM from "react-dom" +import { BrowserRouter, Route, Routes } from "react-router-dom" +import App from "./scripts/app" +import Index from "./scripts/index" +import { version } from "./lerna.json" +import { config } from "./scripts/config" +import * as gtag from "./scripts/utils/gtag" +import { relayStylePagination } from "@apollo/client/utilities" +import "@openneuro/components/page/page.scss" gtag.initialize(config.analytics.trackingIds) @@ -32,7 +32,8 @@ ReactDOM.render( }, }, }), - })}> + })} + > } /> @@ -40,5 +41,5 @@ ReactDOM.render( , - document.getElementById('main'), + document.getElementById("main"), ) diff --git a/packages/openneuro-app/src/scripts/__mocks__/config.ts b/packages/openneuro-app/src/scripts/__mocks__/config.ts index 88958c1970..6230058dac 100644 --- a/packages/openneuro-app/src/scripts/__mocks__/config.ts +++ b/packages/openneuro-app/src/scripts/__mocks__/config.ts @@ -2,10 +2,10 @@ export const config = { /** * CRN */ - url: 'localhost:9876/crn/', + url: "localhost:9876/crn/", graphql: { - uri: 'http://server:8111', + uri: "http://server:8111", }, /** @@ -13,17 +13,17 @@ export const config = { */ auth: { google: { - clientID: 'google-client-id', + clientID: "google-client-id", }, orcid: { - clientID: 'orcid-client-id', + clientID: "orcid-client-id", }, }, sentry: { - environment: 'unit-tests', + environment: "unit-tests", }, support: { - url: 'https://example.com/test-suite', + url: "https://example.com/test-suite", }, } diff --git a/packages/openneuro-app/src/scripts/__utils__/mock-app-shell.tsx b/packages/openneuro-app/src/scripts/__utils__/mock-app-shell.tsx index a389c24498..ddda202af0 100644 --- a/packages/openneuro-app/src/scripts/__utils__/mock-app-shell.tsx +++ b/packages/openneuro-app/src/scripts/__utils__/mock-app-shell.tsx @@ -1,19 +1,20 @@ -import React from 'react' -import { MemoryRouter } from 'react-router-dom' -import { MockedProvider } from '@apollo/client/testing' -import { SearchParamsCtx } from '../search/search-params-ctx' -import { UserModalOpenCtx } from '../utils/user-login-modal-ctx' -import initialSearchParams from '../search/initial-search-params' +import React from "react" +import { MemoryRouter } from "react-router-dom" +import { MockedProvider } from "@apollo/client/testing" +import { SearchParamsCtx } from "../search/search-params-ctx" +import { UserModalOpenCtx } from "../utils/user-login-modal-ctx" +import initialSearchParams from "../search/initial-search-params" /** * Reusable shell component that provides any context required by major component trees */ -export const MockAppShell = ({ children, route = '/' }) => ( +export const MockAppShell = ({ children, route = "/" }) => ( + }} + > {children} diff --git a/packages/openneuro-app/src/scripts/apm.js b/packages/openneuro-app/src/scripts/apm.js index 46b55a603e..b29c0559bb 100644 --- a/packages/openneuro-app/src/scripts/apm.js +++ b/packages/openneuro-app/src/scripts/apm.js @@ -1,17 +1,17 @@ -import { init as initApm } from '@elastic/apm-rum' -import { config } from './config' -import { version } from '../lerna.json' +import { init as initApm } from "@elastic/apm-rum" +import { config } from "./config" +import { version } from "../lerna.json" export let apm export function setupApm() { if ( - config.sentry.environment === 'production' || - config.sentry.environment === 'staging' + config.sentry.environment === "production" || + config.sentry.environment === "staging" ) { apm = initApm({ serverUrl: config.ELASTIC_APM_SERVER_URL, - serviceName: 'openneuro-app', + serviceName: "openneuro-app", serviceVersion: version, environment: config.sentry.environment, }) diff --git a/packages/openneuro-app/src/scripts/app.tsx b/packages/openneuro-app/src/scripts/app.tsx index 3b3d968582..344ae8c594 100644 --- a/packages/openneuro-app/src/scripts/app.tsx +++ b/packages/openneuro-app/src/scripts/app.tsx @@ -1,10 +1,10 @@ -import React, { FC, ReactNode } from 'react' -import Helmet from 'react-helmet' -import { frontPage } from './pages/front-page/front-page-content' -import { CookiesProvider, Cookies } from 'react-cookie' -import { ToastContainer } from 'react-toastify' -import 'react-toastify/dist/ReactToastify.css' -import { MediaContextProvider } from './styles/media' +import React, { FC, ReactNode } from "react" +import Helmet from "react-helmet" +import { frontPage } from "./pages/front-page/front-page-content" +import { Cookies, CookiesProvider } from "react-cookie" +import { ToastContainer } from "react-toastify" +import "react-toastify/dist/ReactToastify.css" +import { MediaContextProvider } from "./styles/media" interface AppProps { children: ReactNode diff --git a/packages/openneuro-app/src/scripts/authentication/__tests__/profile.spec.js b/packages/openneuro-app/src/scripts/authentication/__tests__/profile.spec.js index 97cba319b4..cdf6bdb7c4 100644 --- a/packages/openneuro-app/src/scripts/authentication/__tests__/profile.spec.js +++ b/packages/openneuro-app/src/scripts/authentication/__tests__/profile.spec.js @@ -1,22 +1,22 @@ -import { parseJwt } from '../profile' +import { parseJwt } from "../profile" const asciiToken = - 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c' + "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c" const utf8Token = - 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6Iuelnue1jOenkeWtpuiAhSIsImlhdCI6MTUxNjIzOTAyMn0.pUw2ARoXv4LkJXB1ZR3Th6xG83URT6mn1TftC7ac_O8' + "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6Iuelnue1jOenkeWtpuiAhSIsImlhdCI6MTUxNjIzOTAyMn0.pUw2ARoXv4LkJXB1ZR3Th6xG83URT6mn1TftC7ac_O8" -describe('authentication/profile', () => { - it('decodes a JWT to Javascript object', () => { +describe("authentication/profile", () => { + it("decodes a JWT to Javascript object", () => { expect(parseJwt(asciiToken)).toEqual({ - sub: '1234567890', - name: 'John Doe', + sub: "1234567890", + name: "John Doe", iat: 1516239022, }) }) - it('decodes a JWT with Unicode strings', () => { + it("decodes a JWT with Unicode strings", () => { expect(parseJwt(utf8Token)).toEqual({ - sub: '1234567890', - name: '神経科学者', + sub: "1234567890", + name: "神経科学者", iat: 1516239022, }) }) diff --git a/packages/openneuro-app/src/scripts/authentication/admin-user.jsx b/packages/openneuro-app/src/scripts/authentication/admin-user.jsx index 7587736f4c..1308bfc10c 100644 --- a/packages/openneuro-app/src/scripts/authentication/admin-user.jsx +++ b/packages/openneuro-app/src/scripts/authentication/admin-user.jsx @@ -1,5 +1,5 @@ -import { useCookies } from 'react-cookie' -import { getProfile } from './profile' +import { useCookies } from "react-cookie" +import { getProfile } from "./profile" export const isAdmin = () => { const [cookies] = useCookies() diff --git a/packages/openneuro-app/src/scripts/authentication/logged-in.jsx b/packages/openneuro-app/src/scripts/authentication/logged-in.jsx index d4edafe850..7ac6a26bd9 100644 --- a/packages/openneuro-app/src/scripts/authentication/logged-in.jsx +++ b/packages/openneuro-app/src/scripts/authentication/logged-in.jsx @@ -1,5 +1,5 @@ -import { useCookies } from 'react-cookie' -import { loginCheck } from './loginCheck.js' +import { useCookies } from "react-cookie" +import { loginCheck } from "./loginCheck.js" /** * Render children if logged in diff --git a/packages/openneuro-app/src/scripts/authentication/logged-out.jsx b/packages/openneuro-app/src/scripts/authentication/logged-out.jsx index f9f6cd4ddb..87b35fae58 100644 --- a/packages/openneuro-app/src/scripts/authentication/logged-out.jsx +++ b/packages/openneuro-app/src/scripts/authentication/logged-out.jsx @@ -1,5 +1,5 @@ -import { useCookies } from 'react-cookie' -import { loginCheck } from './loginCheck.js' +import { useCookies } from "react-cookie" +import { loginCheck } from "./loginCheck.js" /** * Render children if logged out diff --git a/packages/openneuro-app/src/scripts/authentication/loginCheck.js b/packages/openneuro-app/src/scripts/authentication/loginCheck.js index 9b9c77272b..798ed38387 100644 --- a/packages/openneuro-app/src/scripts/authentication/loginCheck.js +++ b/packages/openneuro-app/src/scripts/authentication/loginCheck.js @@ -1,4 +1,4 @@ -import { getProfile, guardExpired } from './profile' +import { getProfile, guardExpired } from "./profile" // Expects a universal cookie -export const loginCheck = cookies => guardExpired(getProfile(cookies)) +export const loginCheck = (cookies) => guardExpired(getProfile(cookies)) diff --git a/packages/openneuro-app/src/scripts/authentication/loginUrls.ts b/packages/openneuro-app/src/scripts/authentication/loginUrls.ts index eb508caa95..cc0803a7e8 100644 --- a/packages/openneuro-app/src/scripts/authentication/loginUrls.ts +++ b/packages/openneuro-app/src/scripts/authentication/loginUrls.ts @@ -1,4 +1,4 @@ -import { config } from '../config' +import { config } from "../config" export default { google: `${config.api}auth/google`, diff --git a/packages/openneuro-app/src/scripts/authentication/profile.ts b/packages/openneuro-app/src/scripts/authentication/profile.ts index 593ab92c62..9327a33092 100644 --- a/packages/openneuro-app/src/scripts/authentication/profile.ts +++ b/packages/openneuro-app/src/scripts/authentication/profile.ts @@ -1,4 +1,4 @@ -import jwtDecode from 'jwt-decode' +import jwtDecode from "jwt-decode" interface OpenNeuroTokenProfile { sub: string @@ -20,7 +20,7 @@ export const parseJwt = jwtDecode * Retrieve the user profile from JWT cookie */ export function getProfile(cookies): OpenNeuroTokenProfile { - const accessToken = cookies['accessToken'] + const accessToken = cookies["accessToken"] return accessToken ? parseJwt(accessToken) : null } @@ -28,7 +28,7 @@ export function getProfile(cookies): OpenNeuroTokenProfile { * Return profile if token is not expired. * @param {*} cookies */ -export const getUnexpiredProfile = cookies => { +export const getUnexpiredProfile = (cookies) => { const profile = getProfile(cookies) if (guardExpired(profile)) return profile } @@ -51,10 +51,10 @@ export const guardExpired = (profile: OpenNeuroTokenProfile): boolean => { * Returns true if active user has at least one of the permissions in expectedLevels * @param {string[]} expectedLevels */ -const hasDatasetPermissions = expectedLevels => (permissions, userId) => { +const hasDatasetPermissions = (expectedLevels) => (permissions, userId) => { if (userId) { const permission = permissions.userPermissions.find( - perm => perm.user.id === userId, + (perm) => perm.user.id === userId, ) return (permission && expectedLevels.includes(permission.level)) || false } @@ -62,7 +62,7 @@ const hasDatasetPermissions = expectedLevels => (permissions, userId) => { } // Return true if the active user has write permission -export const hasEditPermissions = hasDatasetPermissions(['admin', 'rw']) +export const hasEditPermissions = hasDatasetPermissions(["admin", "rw"]) // -export const hasDatasetAdminPermissions = hasDatasetPermissions(['admin']) +export const hasDatasetAdminPermissions = hasDatasetPermissions(["admin"]) diff --git a/packages/openneuro-app/src/scripts/authentication/regular-user.tsx b/packages/openneuro-app/src/scripts/authentication/regular-user.tsx index a6fc43aa1d..695732920e 100644 --- a/packages/openneuro-app/src/scripts/authentication/regular-user.tsx +++ b/packages/openneuro-app/src/scripts/authentication/regular-user.tsx @@ -1,6 +1,6 @@ -import React from 'react' -import { useCookies } from 'react-cookie' -import { getProfile } from './profile' +import React from "react" +import { useCookies } from "react-cookie" +import { getProfile } from "./profile" interface RegularUserProps { children?: React.ReactNode diff --git a/packages/openneuro-app/src/scripts/authentication/signOut.ts b/packages/openneuro-app/src/scripts/authentication/signOut.ts index 76e5643d8b..abe57536bf 100644 --- a/packages/openneuro-app/src/scripts/authentication/signOut.ts +++ b/packages/openneuro-app/src/scripts/authentication/signOut.ts @@ -1,8 +1,8 @@ -import cookies from '../utils/cookies.js' +import cookies from "../utils/cookies.js" const signOut = () => { // Delete the token will reset client login state - cookies.remove('accessToken') + cookies.remove("accessToken") } export default signOut diff --git a/packages/openneuro-app/src/scripts/authentication/withProfile.jsx b/packages/openneuro-app/src/scripts/authentication/withProfile.jsx index c7f252ea9a..8a73967cc4 100644 --- a/packages/openneuro-app/src/scripts/authentication/withProfile.jsx +++ b/packages/openneuro-app/src/scripts/authentication/withProfile.jsx @@ -1,11 +1,11 @@ /* eslint-disable */ -import React from 'react' -import { useCookies } from 'react-cookie' -import { getProfile, guardExpired } from './profile' +import React from "react" +import { useCookies } from "react-cookie" +import { getProfile, guardExpired } from "./profile" -const withProfile = WrappedComponent => { - return props => { - const [cookies] = useCookies(['accessToken']) +const withProfile = (WrappedComponent) => { + return (props) => { + const [cookies] = useCookies(["accessToken"]) const profile = getProfile(cookies) // If we have a profile and it is unexpired if (profile && guardExpired(profile)) { diff --git a/packages/openneuro-app/src/scripts/common/block-navigation.jsx b/packages/openneuro-app/src/scripts/common/block-navigation.jsx index d0c03bc2c5..e593bef82a 100644 --- a/packages/openneuro-app/src/scripts/common/block-navigation.jsx +++ b/packages/openneuro-app/src/scripts/common/block-navigation.jsx @@ -1,11 +1,10 @@ -import React from 'react' -import PropTypes from 'prop-types' +import React from "react" +import PropTypes from "prop-types" class BlockNavigation extends React.Component { componentDidMount() { // Attempt to set a message even though browsers do not display it - window.onbeforeunload = () => - this.props.message ? this.props.message : true + window.onbeforeunload = () => this.props.message ? this.props.message : true } componentWillUnmount() { diff --git a/packages/openneuro-app/src/scripts/common/containers/__tests__/header.spec.tsx b/packages/openneuro-app/src/scripts/common/containers/__tests__/header.spec.tsx index f38f983f61..35bbbc49db 100644 --- a/packages/openneuro-app/src/scripts/common/containers/__tests__/header.spec.tsx +++ b/packages/openneuro-app/src/scripts/common/containers/__tests__/header.spec.tsx @@ -1,31 +1,31 @@ -import { vi } from 'vitest' -import React from 'react' -import { MockAppShell } from '../../../__utils__/mock-app-shell' -import { render, screen, fireEvent, waitFor } from '@testing-library/react' -import { HeaderContainer } from '../header' +import { vi } from "vitest" +import React from "react" +import { MockAppShell } from "../../../__utils__/mock-app-shell" +import { fireEvent, render, screen, waitFor } from "@testing-library/react" +import { HeaderContainer } from "../header" const navigate = vi.fn() -vi.mock('../../../config.ts') -vi.mock('../../../uploader/uploader-view.jsx', () => ({ - default: () => 'mocked UploaderView', +vi.mock("../../../config.ts") +vi.mock("../../../uploader/uploader-view.jsx", () => ({ + default: () => "mocked UploaderView", })) -vi.mock('react-router-dom', async () => ({ +vi.mock("react-router-dom", async () => ({ // @ts-ignore-check - ...(await vi.importActual('react-router-dom')), + ...(await vi.importActual("react-router-dom")), useNavigate: () => navigate, })) -describe('HeaderContainer component', () => { - it('navigates prepopulated search when you use the home page search box', async () => { +describe("HeaderContainer component", () => { + it("navigates prepopulated search when you use the home page search box", async () => { render(, { wrapper: MockAppShell }) - const searchbox = screen.getByRole('textbox') - const button = screen.getByLabelText('Search') - await fireEvent.change(searchbox, { target: { value: 'test argument' } }) + const searchbox = screen.getByRole("textbox") + const button = screen.getByLabelText("Search") + await fireEvent.change(searchbox, { target: { value: "test argument" } }) await fireEvent.click(button) await waitFor(() => expect(navigate).toHaveBeenCalledWith( '/search?query={"keywords":["test argument"]}', - ), + ) ) }) }) diff --git a/packages/openneuro-app/src/scripts/common/containers/footer.tsx b/packages/openneuro-app/src/scripts/common/containers/footer.tsx index 3edc307a3f..94c4739320 100644 --- a/packages/openneuro-app/src/scripts/common/containers/footer.tsx +++ b/packages/openneuro-app/src/scripts/common/containers/footer.tsx @@ -1,6 +1,6 @@ -import React, { FC } from 'react' -import { Footer } from '@openneuro/components/footer' -import { version as openneuroVersion } from '../../../lerna.json' +import React, { FC } from "react" +import { Footer } from "@openneuro/components/footer" +import { version as openneuroVersion } from "../../../lerna.json" const FooterContainer: FC = () => { return ( diff --git a/packages/openneuro-app/src/scripts/common/containers/header.tsx b/packages/openneuro-app/src/scripts/common/containers/header.tsx index 0b39799561..976780452a 100644 --- a/packages/openneuro-app/src/scripts/common/containers/header.tsx +++ b/packages/openneuro-app/src/scripts/common/containers/header.tsx @@ -1,45 +1,45 @@ -import React, { FC, useContext } from 'react' -import useState from 'react-usestateref' -import UploaderContext from '../../uploader/uploader-context.js' -import UploadProgress from '../../uploader/upload-progress.jsx' -import { Header, LandingExpandedHeader } from '@openneuro/components/header' -import { Input } from '@openneuro/components/input' -import ModalitySelect from '../../search/inputs/modality-select' -import { UserModalOpenCtx } from '../../utils/user-login-modal-ctx' -import { useLocation, useNavigate } from 'react-router-dom' -import { useCookies } from 'react-cookie' -import signOut from '../../authentication/signOut' -import { getUnexpiredProfile } from '../../authentication/profile' -import FreshdeskWidget from '../partials/freshdesk-widget' -import AggregateCountsContainer from '../../pages/front-page/aggregate-queries/aggregate-counts-container' -import loginUrls from '../../authentication/loginUrls' -import UploaderView from '../../uploader/uploader-view.jsx' -import UploadButton from '../../uploader/upload-button.jsx' -import UploadProgressButton from '../../uploader/upload-progress-button.jsx' +import React, { FC, useContext } from "react" +import useState from "react-usestateref" +import UploaderContext from "../../uploader/uploader-context.js" +import UploadProgress from "../../uploader/upload-progress.jsx" +import { Header, LandingExpandedHeader } from "@openneuro/components/header" +import { Input } from "@openneuro/components/input" +import ModalitySelect from "../../search/inputs/modality-select" +import { UserModalOpenCtx } from "../../utils/user-login-modal-ctx" +import { useLocation, useNavigate } from "react-router-dom" +import { useCookies } from "react-cookie" +import signOut from "../../authentication/signOut" +import { getUnexpiredProfile } from "../../authentication/profile" +import FreshdeskWidget from "../partials/freshdesk-widget" +import AggregateCountsContainer from "../../pages/front-page/aggregate-queries/aggregate-counts-container" +import loginUrls from "../../authentication/loginUrls" +import UploaderView from "../../uploader/uploader-view.jsx" +import UploadButton from "../../uploader/upload-button.jsx" +import UploadProgressButton from "../../uploader/upload-progress-button.jsx" export const HeaderContainer: FC = () => { const navigate = useNavigate() const { pathname: currentPath } = useLocation() - const expanded = currentPath === '/' + const expanded = currentPath === "/" const [cookies] = useCookies() const profile = getUnexpiredProfile(cookies) const { userModalOpen, setUserModalOpen } = useContext(UserModalOpenCtx) - const [newKeyword, setNewKeyword, newKeywordRef] = useState('') + const [newKeyword, setNewKeyword, newKeywordRef] = useState("") const handleSubmit = () => { const query = JSON.stringify({ keywords: newKeywordRef.current ? [newKeywordRef.current] : [], }) - setNewKeyword('') + setNewKeyword("") navigate(`/search?query=${query}`) } const toggleLoginModal = (): void => { - setUserModalOpen(prevState => ({ + setUserModalOpen((prevState) => ({ ...prevState, userModalOpen: !prevState.userModalOpen, })) @@ -47,19 +47,19 @@ export const HeaderContainer: FC = () => { const signOutAndRedirect = () => { signOut() - const homepage = '/' + const homepage = "/" if (window.location.pathname === homepage) window.location.reload() else window.location.pathname = homepage } const [isOpenSupport, setSupportIsOpen] = React.useState(false) - const toggleSupport = () => setSupportIsOpen(prevIsOpen => !prevIsOpen) + const toggleSupport = () => setSupportIsOpen((prevIsOpen) => !prevIsOpen) return ( <> - {uploader => { + {(uploader) => { if (uploader?.uploading) { return ( @@ -81,13 +81,13 @@ export const HeaderContainer: FC = () => { navigateToNewSearch={handleSubmit} renderUploader={() => ( - {uploader => { + {(uploader) => { if (uploader?.uploading) { return } else { return ( uploader.setLocation('/upload')} + onClick={() => uploader.setLocation("/upload")} /> ) } @@ -95,7 +95,7 @@ export const HeaderContainer: FC = () => { )} renderOnFreshDeskWidget={() => } - renderOnExpanded={profile => ( + renderOnExpanded={(profile) => ( { labelStyle="default" value={newKeyword} setValue={setNewKeyword} - onKeyDown={e => { + onKeyDown={(e) => { if (e.keyCode === 13) { handleSubmit() } @@ -132,7 +132,7 @@ export const HeaderContainer: FC = () => { )} /> - {uploader => } + {(uploader) => } ) diff --git a/packages/openneuro-app/src/scripts/common/forms/__tests__/warn-button.spec.jsx b/packages/openneuro-app/src/scripts/common/forms/__tests__/warn-button.spec.jsx index ec6ee37653..d1846b6187 100644 --- a/packages/openneuro-app/src/scripts/common/forms/__tests__/warn-button.spec.jsx +++ b/packages/openneuro-app/src/scripts/common/forms/__tests__/warn-button.spec.jsx @@ -1,13 +1,13 @@ -import React from 'react' -import { render } from '@testing-library/react' -import WarnButton from '../warn-button' +import React from "react" +import { render } from "@testing-library/react" +import WarnButton from "../warn-button" -describe('common/forms/WarnButton', () => { - it('renders successfully', () => { +describe("common/forms/WarnButton", () => { + it("renders successfully", () => { const { asFragment } = render() expect(asFragment()).toMatchSnapshot() }) - it('renders with warnings disable', () => { + it("renders with warnings disable", () => { const { asFragment } = render( , ) diff --git a/packages/openneuro-app/src/scripts/common/forms/warn-button.jsx b/packages/openneuro-app/src/scripts/common/forms/warn-button.jsx index f3441b9df5..104e350fe9 100644 --- a/packages/openneuro-app/src/scripts/common/forms/warn-button.jsx +++ b/packages/openneuro-app/src/scripts/common/forms/warn-button.jsx @@ -1,10 +1,10 @@ // dependencies ------------------------------------------------------- -import React from 'react' -import PropTypes from 'prop-types' -import { Tooltip } from '@openneuro/components/tooltip' -import { toast } from 'react-toastify' -import ToastContent from '../partials/toast-content.jsx' +import React from "react" +import PropTypes from "prop-types" +import { Tooltip } from "@openneuro/components/tooltip" +import { toast } from "react-toastify" +import ToastContent from "../partials/toast-content.jsx" class WarnButton extends React.Component { constructor(props) { @@ -49,7 +49,8 @@ class WarnButton extends React.Component { + href={this.state.link} + > {confirm} ) @@ -57,8 +58,9 @@ class WarnButton extends React.Component { const confirmBtn = ( ) @@ -67,7 +69,8 @@ class WarnButton extends React.Component { {link ? link : confirmBtn} @@ -75,12 +78,13 @@ class WarnButton extends React.Component { ) const hideAction = ( - + ) @@ -130,7 +134,7 @@ class WarnButton extends React.Component { // generate download links if (this.props.prepDownload) { this.setState({ loading: true }) - this.props.prepDownload(link => { + this.props.prepDownload((link) => { this.setState({ displayOptions: true, link: link, loading: false }) }) return @@ -154,9 +158,9 @@ class WarnButton extends React.Component { } } - if (typeof action === 'function') { + if (typeof action === "function") { this.setState({ loading: true }) - action(e => { + action((e) => { if (e && e.error) { toast.error() } @@ -190,10 +194,10 @@ WarnButton.propTypes = { } WarnButton.defaultProps = { - message: '', + message: "", cancel: , confirm: , - icon: 'fa-trash-o', + icon: "fa-trash-o", warn: true, tooltip: null, } diff --git a/packages/openneuro-app/src/scripts/common/partials/block-navigation.jsx b/packages/openneuro-app/src/scripts/common/partials/block-navigation.jsx index d0c03bc2c5..e593bef82a 100644 --- a/packages/openneuro-app/src/scripts/common/partials/block-navigation.jsx +++ b/packages/openneuro-app/src/scripts/common/partials/block-navigation.jsx @@ -1,11 +1,10 @@ -import React from 'react' -import PropTypes from 'prop-types' +import React from "react" +import PropTypes from "prop-types" class BlockNavigation extends React.Component { componentDidMount() { // Attempt to set a message even though browsers do not display it - window.onbeforeunload = () => - this.props.message ? this.props.message : true + window.onbeforeunload = () => this.props.message ? this.props.message : true } componentWillUnmount() { diff --git a/packages/openneuro-app/src/scripts/common/partials/freshdesk-widget.jsx b/packages/openneuro-app/src/scripts/common/partials/freshdesk-widget.jsx index 19fc37f325..3e1fa7bd3b 100644 --- a/packages/openneuro-app/src/scripts/common/partials/freshdesk-widget.jsx +++ b/packages/openneuro-app/src/scripts/common/partials/freshdesk-widget.jsx @@ -1,8 +1,8 @@ -import React from 'react' -import PropTypes from 'prop-types' -import { useCookies } from 'react-cookie' -import { getProfile } from '../../authentication/profile' -import { config } from '../../config' +import React from "react" +import PropTypes from "prop-types" +import { useCookies } from "react-cookie" +import { getProfile } from "../../authentication/profile" +import { config } from "../../config" const buildCustomQuery = (customText, prepopulatedFields) => { const customizerQueries = [ @@ -11,7 +11,7 @@ const buildCustomQuery = (customText, prepopulatedFields) => { .filter(([, value]) => value) .map(([key, value]) => `helpdesk_ticket[${key}]=${value}`), ] - return customizerQueries.length ? `&${customizerQueries.join(';')}` : '' + return customizerQueries.length ? `&${customizerQueries.join(";")}` : "" } function FreshdeskWidget({ subject, error, sentryId, description }) { @@ -19,17 +19,17 @@ function FreshdeskWidget({ subject, error, sentryId, description }) { const profile = getProfile(cookies) const sentry = sentryId && `Sentry ID: ${sentryId}` const joinedDescription = [sentry, description, error] - .filter(item => item) - .join(' \u2014 ') + .filter((item) => item) + .join(" \u2014 ") const customText = { - widgetType: 'embedded', - formTitle: 'Report+an+Issue', - submitTitle: 'Request+Support', + widgetType: "embedded", + formTitle: "Report+an+Issue", + submitTitle: "Request+Support", submitThanks: - 'Thank+you+for+taking+the+time+to+report+your+case.+A+support+representative+will+be+reviewing+your+request+and+will+send+you+a+personal+response+within+24+to+48+hours.', - screenshot: 'No', - captcha: 'yes', + "Thank+you+for+taking+the+time+to+report+your+case.+A+support+representative+will+be+reviewing+your+request+and+will+send+you+a+personal+response+within+24+to+48+hours.", + screenshot: "No", + captcha: "yes", } const prepopulatedFields = { requester: profile && profile.email, @@ -43,17 +43,14 @@ function FreshdeskWidget({ subject, error, sentryId, description }) { src="https://s3.amazonaws.com/assets.freshdesk.com/widget/freshwidget.js" />