From 54282d916fabf0d1e56f11767a4ea8ad2e59b7c3 Mon Sep 17 00:00:00 2001 From: Cesar Varela Date: Mon, 26 Aug 2024 17:19:19 -0300 Subject: [PATCH 1/6] Migrate classifications and taxa collections to API (#3005) * Add taxa seeds * Reapply "Add classifications collection to api" This reverts commit 5d3aed2494bafe674db2c73daa9b759f555babee. * Fix upsert mutations * Fix classification editor tests * Update components * Move to correct folder * Fix mutations handling of relationships * Update client side query variables * Undo debug comments * Update generated code * temporary silence warning * Update playwright login util to allow mocking local storage session * migrate classifications test * Update test * Update tests to use new login fixture * Fix tests * Skip data dependant test * Prevent login util from dropping users database * WIP * Fix custom data mocking * check for window to fix crash on build time * Migrate csettool * Fix tests * Make it easier to debug playwright on ci * Fix wrong query * Fix tests * Use more verbose reporter * auto restart webserver on crash * accept npx * Fix test * Fix test * Fix test * Remove confusing folder * Add clarifying comment back * Fix test * Fix test * Add comment --- .github/workflows/test-playwright-full.yml | 6 +- .github/workflows/test-playwright.yml | 5 +- .../integration/apps/classifications.cy.js | 99 - .../e2e/integration/apps/csettool.cy.js | 497 -- site/gatsby-site/package.json | 2 +- site/gatsby-site/playwright.config.ts | 12 +- .../playwright/e2e-full/admin.spec.ts | 18 +- .../e2e-full/apps/classifications.spec.ts | 54 + .../playwright/e2e-full/apps/csettool.spec.ts | 93 + .../e2e-full/apps/incidents.spec.ts | 6 +- .../playwright/e2e-full/apps/variants.spec.ts | 17 +- .../playwright/e2e-full/cite.spec.ts | 19 +- .../playwright/e2e-full/citeEdit.spec.ts | 21 +- .../e2e-full/classificationsEditor.spec.ts | 274 + .../e2e-full/incidents/edit.spec.ts | 11 +- .../playwright/e2e-full/incidents/new.spec.ts | 35 +- .../playwright/e2e-full/submit.spec.ts | 17 +- .../playwright/e2e-full/utils.spec.ts | 17 + .../integration/classificationsEditor.spec.ts | 308 - site/gatsby-site/playwright/memory-mongo.ts | 24 +- .../seeds/aiidprod/classifications.ts | 1038 ++++ .../playwright/seeds/aiidprod/taxa.ts | 4978 +++++++++++++++++ site/gatsby-site/playwright/utils.ts | 26 +- .../server/fields/classifications.ts | 27 + site/gatsby-site/server/fields/taxa.ts | 18 + site/gatsby-site/server/generated/gql.ts | 8 +- site/gatsby-site/server/generated/graphql.ts | 1311 +---- site/gatsby-site/server/local.ts | 17 + site/gatsby-site/server/remote.ts | 71 +- site/gatsby-site/server/schema.ts | 19 +- .../server/tests/fixtures/classifications.ts | 402 ++ .../server/tests/mutation-fields.spec.ts | 2 + .../server/tests/query-fields.spec.ts | 2 + .../server/types/classification.ts | 36 + site/gatsby-site/server/types/taxa.ts | 116 + site/gatsby-site/server/utils.ts | 3 +- .../components/cite/RemoveDuplicateModal.js | 10 +- .../components/classifications/CsetTable.js | 14 +- .../components/taxa/ClassificationsEditor.js | 9 +- .../src/components/taxa/TaxonomyForm.js | 28 +- .../userContext/UserContextProvider.js | 7 + .../src/graphql/classifications.js | 10 +- .../src/pages/apps/classifications.js | 6 +- .../{mongodbAiidprodIncidents.incident_id}.js | 2 +- .../src/pages/incidents/history.js | 2 +- .../src/templates/citeDynamicTemplate.js | 2 +- site/gatsby-site/tsconfig.json | 5 +- 47 files changed, 7572 insertions(+), 2132 deletions(-) delete mode 100644 site/gatsby-site/cypress/e2e/integration/apps/classifications.cy.js delete mode 100644 site/gatsby-site/cypress/e2e/integration/apps/csettool.cy.js create mode 100644 site/gatsby-site/playwright/e2e-full/apps/classifications.spec.ts create mode 100644 site/gatsby-site/playwright/e2e-full/apps/csettool.spec.ts create mode 100644 site/gatsby-site/playwright/e2e-full/classificationsEditor.spec.ts create mode 100644 site/gatsby-site/playwright/e2e-full/utils.spec.ts delete mode 100644 site/gatsby-site/playwright/e2e/integration/classificationsEditor.spec.ts create mode 100644 site/gatsby-site/playwright/seeds/aiidprod/classifications.ts create mode 100644 site/gatsby-site/playwright/seeds/aiidprod/taxa.ts create mode 100644 site/gatsby-site/server/fields/classifications.ts create mode 100644 site/gatsby-site/server/fields/taxa.ts create mode 100644 site/gatsby-site/server/tests/fixtures/classifications.ts create mode 100644 site/gatsby-site/server/types/classification.ts create mode 100644 site/gatsby-site/server/types/taxa.ts diff --git a/.github/workflows/test-playwright-full.yml b/.github/workflows/test-playwright-full.yml index 26a1b42cf0..5ed3a02b59 100644 --- a/.github/workflows/test-playwright-full.yml +++ b/.github/workflows/test-playwright-full.yml @@ -115,13 +115,17 @@ jobs: working-directory: site/gatsby-site - name: Run playwright tests - run: npx playwright test playwright/e2e-full/ --shard=${{ matrix.shardIndex }}/${{ matrix.shardTotal }} + run: npm run test:e2e:ci working-directory: site/gatsby-site env: E2E_ADMIN_PASSWORD: ${{ secrets.E2E_ADMIN_PASSWORD }} E2E_ADMIN_USERNAME: ${{ secrets.E2E_ADMIN_USERNAME }} IS_EMPTY_ENVIRONMENT: ${{ vars.IS_EMPTY_ENVIRONMENT }} MONGODB_CONNECTION_STRING: mongodb://127.0.0.1:4110/ + GATSBY_AVAILABLE_LANGUAGES: ${{ vars.GATSBY_AVAILABLE_LANGUAGES }} + SHARD_INDEX: ${{ matrix.shardIndex }} + SHARD_TOTAL: ${{ matrix.shardTotal }} + TEST_FOLDER: playwright/e2e-full/ - uses: actions/upload-artifact@v4 if: ${{ !cancelled() }} diff --git a/.github/workflows/test-playwright.yml b/.github/workflows/test-playwright.yml index 0ecb4f0818..91017d53cf 100644 --- a/.github/workflows/test-playwright.yml +++ b/.github/workflows/test-playwright.yml @@ -71,13 +71,16 @@ jobs: working-directory: site/gatsby-site - name: Run playwright tests - run: npx playwright test playwright/e2e/ --shard=${{ matrix.shardIndex }}/${{ matrix.shardTotal }} + run: npm run test:e2e:ci working-directory: site/gatsby-site env: E2E_ADMIN_PASSWORD: ${{ secrets.E2E_ADMIN_PASSWORD }} E2E_ADMIN_USERNAME: ${{ secrets.E2E_ADMIN_USERNAME }} IS_EMPTY_ENVIRONMENT: ${{ vars.IS_EMPTY_ENVIRONMENT }} GATSBY_AVAILABLE_LANGUAGES: ${{ vars.GATSBY_AVAILABLE_LANGUAGES }} + SHARD_INDEX: ${{ matrix.shardIndex }} + SHARD_TOTAL: ${{ matrix.shardTotal }} + TEST_FOLDER: playwright/e2e/ - uses: actions/upload-artifact@v4 if: ${{ !cancelled() }} diff --git a/site/gatsby-site/cypress/e2e/integration/apps/classifications.cy.js b/site/gatsby-site/cypress/e2e/integration/apps/classifications.cy.js deleted file mode 100644 index 0684fab588..0000000000 --- a/site/gatsby-site/cypress/e2e/integration/apps/classifications.cy.js +++ /dev/null @@ -1,99 +0,0 @@ -import { conditionalIt } from '../../../support/utils'; -import taxa from '../../../fixtures/call/taxa.json'; -import classifications from '../../../fixtures/call/classifications.json'; - -describe('Classifications App', () => { - const url = '/apps/classifications'; - - it('Should successfully load', () => { - cy.visit(url); - }); - - it('Should successfully load the table', () => { - cy.conditionalIntercept( - '**/graphql', - (req) => req.body.operationName == 'FindTaxa', - 'FindTaxa', - taxa - ); - - cy.conditionalIntercept( - '**/graphql', - (req) => req.body.operationName == 'FindClassifications', - 'FindClassifications', - classifications - ); - - cy.visit(url); - - cy.wait(['@FindTaxa', '@FindClassifications']); - - cy.waitForStableDOM(); - - cy.get('[data-cy="row"]').should('have.length', 2); - }); - - conditionalIt( - !Cypress.env('isEmptyEnvironment'), - 'Successfully edit a CSET classification', - () => { - cy.login(Cypress.env('e2eUsername'), Cypress.env('e2ePassword')); - - cy.conditionalIntercept( - '**/graphql', - (req) => req.body.operationName == 'FindTaxa', - 'FindTaxa', - taxa - ); - - cy.conditionalIntercept( - '**/graphql', - (req) => req.body.operationName == 'FindClassifications', - 'FindClassifications', - classifications - ); - - cy.visit(url); - - cy.wait(['@FindTaxa', '@FindClassifications']); - - cy.get('select[data-cy="taxonomy"]').select('CSET'); - - cy.waitForStableDOM(); - - cy.get('a[href="/cite/2/?edit_taxonomy=CSET').click(); - } - ); - - conditionalIt(!Cypress.env('isEmptyEnvironment'), 'Should switch taxonomies', () => { - cy.login(Cypress.env('e2eUsername'), Cypress.env('e2ePassword')); - - cy.conditionalIntercept( - '**/graphql', - (req) => req.body.operationName == 'FindTaxa', - 'FindTaxa', - taxa - ); - - cy.conditionalIntercept( - '**/graphql', - (req) => req.body.operationName == 'FindClassifications', - 'FindClassifications', - classifications - ); - - cy.visit(url); - - cy.wait(['@FindTaxa', '@FindClassifications']); - - cy.get('select[data-cy="taxonomy"]').select('CSET'); - - cy.waitForStableDOM(); - - cy.get('select[data-cy="taxonomy"]').select('GMF'); - - cy.waitForStableDOM(); - - cy.get('[role="columnheader"]').contains('Known AI Goal').should('exist'); - }); -}); diff --git a/site/gatsby-site/cypress/e2e/integration/apps/csettool.cy.js b/site/gatsby-site/cypress/e2e/integration/apps/csettool.cy.js deleted file mode 100644 index 48571c86f9..0000000000 --- a/site/gatsby-site/cypress/e2e/integration/apps/csettool.cy.js +++ /dev/null @@ -1,497 +0,0 @@ -import { conditionalIt } from '../../../support/utils'; -import cssettool from '../../../fixtures/classifications/cssettool.json'; -import upsertCSETv1merge from '../../../fixtures/classifications/upsertCSETv1merge.json'; - -describe('CSET tool', () => { - const url = '/apps/csettool/52/'; - - function getRow(short_name, { index } = { index: 0 }) { - return cy.get(`[data-cy="column-${short_name}"]`).eq(index).parent().parent(); - } - - conditionalIt( - !Cypress.env('isEmptyEnvironment') && Cypress.env('e2eUsername') && Cypress.env('e2ePassword'), - 'Successfully loads CSET annotator classifications', - () => { - cy.login(Cypress.env('e2eUsername'), Cypress.env('e2ePassword')); - - cy.conditionalIntercept( - '**/graphql', - (req) => req.body.operationName == 'FindClassifications', - 'FindClassifications', - cssettool - ); - - cy.visit(url); - - cy.wait(['@FindClassifications']); - - cy.waitForStableDOM(); - - getRow('Incident Number').within(() => { - // same value should resolve automatically - cy.get('[data-cy="column-CSETv1_Annotator-1"]') - .should('have.text', '52') - .should('not.have.class', 'bg-red-100'); - cy.get('[data-cy="column-CSETv1_Annotator-2"]') - .should('have.text', '52') - .should('not.have.class', 'bg-red-100'); - cy.get('[data-cy="column-result"]') - .should('have.text', '52') - .should('have.class', 'bg-green-100'); - }); - - getRow('Annotator').within(() => { - // should be skipped - cy.get('[data-cy="column-CSETv1_Annotator-1"]') - .should('have.text', '"006"') - .should('have.class', 'bg-red-100'); - cy.get('[data-cy="column-CSETv1_Annotator-2"]') - .should('have.text', 'null') - .should('have.class', 'bg-red-100'); - cy.get('[data-cy="column-result"]').should('have.text', 'skipped'); - }); - - getRow('Physical Objects').within(() => { - // should ask for disambiguation - cy.get('[data-cy="column-CSETv1_Annotator-1"]') - .should('have.text', '"yes"') - .should('have.class', 'bg-red-100'); - - cy.get('[data-cy="column-CSETv1_Annotator-2"]') - .should('have.text', '"maybe"') - .should('have.class', 'bg-red-100'); - - cy.get('[data-cy="column-result"]').should('have.text', 'Please select a column'); - }); - - getRow('Harm Distribution Basis').within(() => { - // should merge arrays automatically - cy.get('[data-cy="column-CSETv1_Annotator-1"]') - .should('have.class', 'bg-red-100') - .find('span[data-cy="item"]') - .should(($items) => { - const items = ['"none"']; - - expect($items).to.have.length(items.length); - - $items.each((index, $item) => { - expect($item).to.have.text(items[index]); - }); - }); - - cy.get('[data-cy="column-CSETv1_Annotator-2"]') - .should('have.class', 'bg-red-100') - .find('span[data-cy="item"]') - .should(($items) => { - const items = ['"ideology"', '"financial means"', '"disability"']; - - expect($items).to.have.length(items.length); - - $items.each((index, $item) => { - expect($item).to.have.text(items[index]); - }); - }); - - cy.get('[data-cy="column-result"]') - .should('have.class', 'bg-green-100') - .find('span[data-cy="item"]') - .should(($items) => { - const items = ['"none"', '"ideology"', '"financial means"', '"disability"']; - - expect($items).to.have.length(items.length); - - $items.each((index, $item) => { - expect($item).to.have.text(items[index]); - }); - }); - }); - - getRow('notes').within(() => { - // should merge arrays automatically - cy.get('[data-cy="column-CSETv1_Annotator-1"]') - .should('have.text', 'This a note from the annotator 1') - .should('have.class', 'bg-red-100'); - cy.get('[data-cy="column-CSETv1_Annotator-2"]') - .should('have.text', 'This a note from the annotator 2') - .should('have.class', 'bg-red-100'); - cy.get('[data-cy="column-result"]') - .should( - 'have.text', - 'Annotator 1: \n\n This a note from the annotator 1\n\nAnnotator 2: \n\n This a note from the annotator 2' - ) - .should('have.class', 'bg-green-100'); - }); - - getRow('Notes (special interest intangible harm)').within(() => { - // should not mix annotator numbers - cy.get('[data-cy="column-CSETv1_Annotator-1"]') - .should('have.text', '') - .should('have.class', 'bg-red-100'); - cy.get('[data-cy="column-CSETv1_Annotator-2"]') - .should('have.text', 'This is a note from Annotator 2') - .should('have.class', 'bg-red-100'); - cy.get('[data-cy="column-result"]') - .should('have.text', 'Annotator 2: \n\n This is a note from Annotator 2') - .should('have.class', 'bg-green-100'); - }); - - getRow('Entities').within(() => { - // - - cy.get('[data-cy="column-CSETv1_Annotator-1"]').should('have.class', 'bg-red-100'); - - cy.get('[data-cy="column-CSETv1_Annotator-1"]') - .get('[data-cy="entity-Joshua Brown"]') - .should('be.visible'); - cy.get('[data-cy="column-CSETv1_Annotator-1"]') - .get('[data-cy="entity-Tesla Model S"]') - .should('be.visible'); - cy.get('[data-cy="column-CSETv1_Annotator-1"]') - .get('[data-cy="entity-Frank Baressi"]') - .should('be.visible'); - - // - - cy.get('[data-cy="column-CSETv1_Annotator-2"]').should('have.class', 'bg-red-100'); - - cy.get('[data-cy="column-CSETv1_Annotator-2"]') - .get('[data-cy="entity-Joshua Brown"]') - .should('be.visible'); - - // delete a duplicated entity - - cy.get('[data-cy="column-result"]').find('[data-cy*="entity-"]').should('have.length', 4); - - cy.get('[data-cy="column-CSETv1_Annotator-2"]') - .find('[data-cy="entity-Joshua Brown"]') - .parent() - .find('button') - .click(); - - cy.get('[data-cy="column-result"]').find('[data-cy*="entity-"]').should('have.length', 3); - }); - - getRow('Estimated Harm Quantities').within(() => { - // should ask for disambiguation for different boolean values - cy.get('[data-cy="column-CSETv1_Annotator-1"]') - .should('have.text', 'true') - .should('have.class', 'bg-red-100'); - cy.get('[data-cy="column-CSETv1_Annotator-2"]') - .should('have.text', 'false') - .should('have.class', 'bg-red-100'); - cy.get('[data-cy="column-result"]') - .should('have.text', 'Please select a column') - .should('have.class', 'bg-red-100'); - }); - - cy.contains('Merge Classifications').should('be.disabled'); - - // disambiguate and submit - - getRow('Physical Objects').within(() => { - cy.get('[data-cy="column-CSETv1_Annotator-1"]').click(); - }); - - getRow('Entertainment Industry').within(() => { - cy.get('[data-cy="column-CSETv1_Annotator-1"]').click(); - }); - - getRow('Tangible Harm').within(() => { - cy.get('[data-cy="column-CSETv1_Annotator-1"]').click(); - }); - - getRow('AI System').within(() => { - cy.get('[data-cy="column-CSETv1_Annotator-1"]').click(); - }); - - getRow('User Test in Controlled Conditions').within(() => { - cy.get('[data-cy="column-CSETv1_Annotator-1"]').click(); - }); - - getRow('Estimated Harm Quantities').within(() => { - cy.get('[data-cy="column-CSETv1_Annotator-1"]').click(); - }); - - getRow('Quality Control').within(() => { - cy.get('[data-cy="column-CSETv1_Annotator-2"]').click(); - }); - - getRow( - 'There is a potentially identifiable specific entity that experienced the harm' - ).within(() => { - cy.get('[data-cy="column-CSETv1_Annotator-1"]').click(); - }); - - getRow('Harmed Class of Entities').within(() => { - cy.get('[data-cy="column-CSETv1_Annotator-1"]').click(); - }); - - getRow('Estimated Date').within(() => { - cy.get('[data-cy="column-CSETv1_Annotator-1"]').click(); - }); - - cy.conditionalIntercept( - '**/graphql', - (req) => req.body.operationName == 'UpsertClassification', - 'UpsertClassification', - upsertCSETv1merge - ); - - cy.contains('Merge Classifications').click(); - - cy.wait('@UpsertClassification').then((xhr) => { - expect(xhr.request.body.variables.query).to.deep.eq({ - incidents: { incident_id: 52 }, - namespace: 'CSETv1', - }); - - expect(xhr.request.body.variables.data).to.deep.eq({ - incidents: { link: [52] }, - reports: { link: [] }, - namespace: 'CSETv1', - notes: - 'Annotator 1: \n\n This a note from the annotator 1\n\nAnnotator 2: \n\n This a note from the annotator 2', - attributes: [ - { - short_name: 'Incident Number', - value_json: '52', - }, - { - short_name: 'Quality Control', - value_json: 'true', - }, - { - short_name: 'Physical Objects', - value_json: '"yes"', - }, - { - short_name: 'Entertainment Industry', - value_json: '"no"', - }, - { - short_name: 'Report, Test, or Study of data', - value_json: '"no"', - }, - { - short_name: 'Deployed', - value_json: '"yes"', - }, - { - short_name: 'Producer Test in Controlled Conditions', - value_json: '"no"', - }, - { - short_name: 'Producer Test in Operational Conditions', - value_json: '"no"', - }, - { - short_name: 'User Test in Controlled Conditions', - value_json: '"no"', - }, - { - short_name: 'User Test in Operational Conditions', - value_json: '"no"', - }, - { - short_name: 'Harm Domain', - value_json: '"yes"', - }, - { - short_name: 'Tangible Harm', - value_json: '"tangible harm definitively occurred"', - }, - { - short_name: 'AI System', - value_json: '"yes"', - }, - { - short_name: 'Clear link to technology', - value_json: '"yes"', - }, - { - short_name: - 'There is a potentially identifiable specific entity that experienced the harm', - value_json: 'true', - }, - { - short_name: 'AI Harm Level', - value_json: '"AI tangible harm near-miss"', - }, - { - short_name: 'AI Tangible Harm Level Notes', - value_json: - '"Annotator 1: \\n\\n The Tesla\'s autopilot failed to notice the trailer of the trucker and deploy breaks. This could have minimized damage during the crash. However, the crash was ultimately at fault of the Tesla driver."', - }, - { - short_name: 'Impact on Critical Services', - value_json: '"no"', - }, - { - short_name: 'Rights Violation', - value_json: '"no"', - }, - { - short_name: 'Involving Minor', - value_json: '"no"', - }, - { - short_name: 'Detrimental Content', - value_json: '"no"', - }, - { - short_name: 'Protected Characteristic', - value_json: '"no"', - }, - { - short_name: 'Harm Distribution Basis', - value_json: '["none","ideology","financial means","disability"]', - }, - { - short_name: 'Notes (special interest intangible harm)', - value_json: '"Annotator 2: \\n\\n This is a note from Annotator 2"', - }, - { - short_name: 'Special Interest Intangible Harm', - value_json: '"no"', - }, - { - short_name: 'Clear link to Technology', - value_json: '"no"', - }, - { - short_name: 'Harmed Class of Entities', - value_json: 'true', - }, - { - short_name: 'Annotator’s AI special interest intangible harm assessment', - value_json: '"no"', - }, - { - short_name: 'Notes (AI special interest intangible harm)', - value_json: '""', - }, - { - short_name: 'Date of Incident Year', - value_json: '"2016"', - }, - { - short_name: 'Date of Incident Month', - value_json: '"05"', - }, - { - short_name: 'Date of Incident Day', - value_json: '"07"', - }, - { - short_name: 'Estimated Date', - value_json: 'true', - }, - { - short_name: 'Multiple AI Interaction', - value_json: '"no"', - }, - { - short_name: 'Embedded', - value_json: '"yes"', - }, - { - short_name: 'Location City', - value_json: '"Williston"', - }, - { - short_name: 'Location State/Province (two letters)', - value_json: '"FL"', - }, - { - short_name: 'Location Country (two letters)', - value_json: '"US"', - }, - { - short_name: 'Location Region', - value_json: '"North America"', - }, - { - short_name: 'Infrastructure Sectors', - value_json: '[]', - }, - { - short_name: 'Operating Conditions', - value_json: '""', - }, - { - short_name: 'Notes (Environmental and Temporal Characteristics)', - value_json: '""', - }, - { - short_name: 'Entities', - value_json: - '[{"attributes":[{"short_name":"Entity","value_json":"\\"Joshua Brown\\""},{"short_name":"Named Entity","value_json":"true"},{"short_name":"Entity type","value_json":"\\"individual\\""},{"short_name":"Entity Relationship to the AI","value_json":"[\\"user\\"]"},{"short_name":"Harm Category Experienced","value_json":"\\"AI tangible harm event\\""},{"short_name":"Harm Type Experienced","value_json":"\\"physical health/safety\\""},{"short_name":"Notes (Characterizing Entities and the Harm)","value_json":"\\"Tesla Autopilot failed to detected truck and deploy its breaks. This could have minimized damaged during the crash. Ultimately, the Tesla was responsible for the crash\\""}]},{"attributes":[{"short_name":"Entity","value_json":"\\"Tesla Model S\\""},{"short_name":"Named Entity","value_json":"true"},{"short_name":"Entity type","value_json":"\\"product\\""},{"short_name":"Entity Relationship to the AI","value_json":"[\\"product containing AI\\"]"},{"short_name":"Harm Category Experienced","value_json":"\\"AI tangible harm event\\""},{"short_name":"Harm Type Experienced","value_json":"\\"physical property\\""},{"short_name":"Notes (Characterizing Entities and the Harm)","value_json":"\\"Autopilot failed to deploy breaks which could have minimized damage in the crash\\""}]},{"attributes":[{"short_name":"Entity","value_json":"\\"Frank Baressi\\""},{"short_name":"Named Entity","value_json":"true"},{"short_name":"Entity type","value_json":"\\"individual\\""},{"short_name":"Entity Relationship to the AI","value_json":"[\\"user\\"]"},{"short_name":"Harm Category Experienced","value_json":"\\"AI tangible harm event\\""},{"short_name":"Harm Type Experienced","value_json":"\\"physical property\\""},{"short_name":"Notes (Characterizing Entities and the Harm)","value_json":"\\"Truck driver hit by Tesla. Damage to truck could have been minimized by the deployment of breaks\\""}]}]', - }, - { - short_name: 'Lives Lost', - value_json: '1', - }, - { - short_name: 'Injuries', - value_json: '-2', - }, - { - short_name: 'Estimated Harm Quantities', - value_json: 'true', - }, - { - short_name: 'Notes ( Tangible Harm Quantities Information)', - value_json: '""', - }, - { - short_name: 'AI System Description', - value_json: '"Tesla Autopilot. "', - }, - { - short_name: 'Data Inputs', - value_json: - '["video input","navigation","sensor data","tra","road data","traffic","GPS"]', - }, - { - short_name: 'Sector of Deployment', - value_json: '["transportation and storage"]', - }, - { - short_name: 'Public Sector Deployment', - value_json: '"no"', - }, - { - short_name: 'Autonomy Level', - value_json: '"Autonomy2"', - }, - { - short_name: 'Notes (Information about AI System)', - value_json: '""', - }, - { - short_name: 'Intentional Harm', - value_json: '"No. Not intentionally designed to perform harm"', - }, - { - short_name: 'Physical System Type', - value_json: '"Automobile"', - }, - { - short_name: 'AI Task', - value_json: '["navigation","transportation"]', - }, - { - short_name: 'AI tools and methods', - value_json: '["Autonomous Driving"]', - }, - { - short_name: 'Notes (AI Functionality and Techniques)', - value_json: '""', - }, - ], - }); - }); - } - ); -}); diff --git a/site/gatsby-site/package.json b/site/gatsby-site/package.json index d433600fd1..6fd4c6568f 100644 --- a/site/gatsby-site/package.json +++ b/site/gatsby-site/package.json @@ -130,7 +130,7 @@ "cypress:open": "cypress open", "cypress:run": "cypress run", "test:e2e": "start-server-and-test start http://localhost:8000 cypress:open", - "test:e2e:ci": "start-server-and-test start http://localhost:8000 cypress:run", + "test:e2e:ci": "DEBUG=pw:api,pw:webserver npx playwright test $TEST_FOLDER --shard=$SHARD_INDEX/$SHARD_TOTAL", "test:api": "jest --setupFiles dotenv/config --runInBand --forceExit", "test:api:ci": "jest --runInBand --forceExit", "codegen": "graphql-codegen --config codegen.ts", diff --git a/site/gatsby-site/playwright.config.ts b/site/gatsby-site/playwright.config.ts index 4e4a9cb158..17a3725913 100644 --- a/site/gatsby-site/playwright.config.ts +++ b/site/gatsby-site/playwright.config.ts @@ -13,19 +13,19 @@ export default defineConfig({ testDir: './playwright', globalTimeout: process.env.CI ? 60 * 60 * 1000 : undefined, expect: { - timeout: process.env.CI ? 30000 : undefined, + timeout: process.env.CI ? 60000 : 30000, }, - timeout: process.env.CI ? 60000 : undefined, + timeout: process.env.CI ? 180000 : 90000, /* Run tests in files in parallel */ fullyParallel: true, /* Fail the build on CI if you accidentally left test.only in the source code. */ forbidOnly: !!process.env.CI, /* Retry on CI only */ retries: process.env.CI ? 3 : 0, - /* Opt out of parallel tests on CI. */ - workers: process.env.CI ? 1 : undefined, + // TODO: We can handle only one worker because tests share the same database and many tests are resetting the database. + workers: process.env.CI ? 1 : 1, /* Reporter to use. See https://playwright.dev/docs/test-reporters */ - reporter: process.env.CI ? 'blob' : 'html', + reporter: process.env.CI ? [['blob'], ['line', { printSteps: true }]] : 'html', /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */ use: { /* Base URL to use in actions like `await page.goto('/')`. */ @@ -75,7 +75,7 @@ export default defineConfig({ /* Run your local dev server before starting the tests */ webServer: { - command: 'NODE_OPTIONS=--max-old-space-size=5600 npm run serve', + command: 'npx -y pm2 start npm --name "web-server" -- run serve && npx pm2 logs "web-server"', url: 'http://localhost:8000', reuseExistingServer: !process.env.CI, }, diff --git a/site/gatsby-site/playwright/e2e-full/admin.spec.ts b/site/gatsby-site/playwright/e2e-full/admin.spec.ts index 40b3c53b51..35ef3a1c55 100644 --- a/site/gatsby-site/playwright/e2e-full/admin.spec.ts +++ b/site/gatsby-site/playwright/e2e-full/admin.spec.ts @@ -5,6 +5,10 @@ import { init } from '../memory-mongo'; test.describe('Admin', () => { const baseUrl = '/admin'; + test.beforeEach(async () => { + await init(); + }); + test('Should show not enough permissions message', async ({ page }) => { await page.goto(baseUrl); await expect(page.getByText("Not enough permissions")).toBeVisible({ timeout: 30000 }); @@ -14,12 +18,9 @@ test.describe('Admin', () => { 'Should display a list of users, their roles and allow edition', async ({ page, login, skipOnEmptyEnvironment }) => { - const userId = await login(process.env.E2E_ADMIN_USERNAME, process.env.E2E_ADMIN_PASSWORD); - + const userId = await login(process.env.E2E_ADMIN_USERNAME, process.env.E2E_ADMIN_PASSWORD, { customData: { first_name: 'John', last_name: 'Doe', roles: ['admin'] } }); const users = [{ userId, first_name: 'John', last_name: 'Doe', roles: ['admin'] }]; - await init({ customData: { users } }, { drop: true }); - await page.goto(baseUrl); for (const user of users.slice(0, 5)) { @@ -51,9 +52,7 @@ test.describe('Admin', () => { 'Should display New Incident button', async ({ page, login, skipOnEmptyEnvironment }) => { - const userId = await login(process.env.E2E_ADMIN_USERNAME, process.env.E2E_ADMIN_PASSWORD); - - await init({ customData: { users: [{ userId, first_name: 'John', last_name: 'Doe', roles: ['admin'] }] } }, { drop: true }); + await login(process.env.E2E_ADMIN_USERNAME, process.env.E2E_ADMIN_PASSWORD, { customData: { first_name: 'John', last_name: 'Doe', roles: ['admin'] } }); await page.goto(baseUrl); @@ -67,10 +66,7 @@ test.describe('Admin', () => { 'Should filter results', async ({ page, login, skipOnEmptyEnvironment }) => { - const userId = await login(process.env.E2E_ADMIN_USERNAME, process.env.E2E_ADMIN_PASSWORD); - - await init({ customData: { users: [{ userId, first_name: 'John', last_name: 'Doe', roles: ['admin'] }] } }, { drop: true }); - + await login(process.env.E2E_ADMIN_USERNAME, process.env.E2E_ADMIN_PASSWORD, { customData: { first_name: 'John', last_name: 'Doe', roles: ['admin'] } }); await page.goto(baseUrl); diff --git a/site/gatsby-site/playwright/e2e-full/apps/classifications.spec.ts b/site/gatsby-site/playwright/e2e-full/apps/classifications.spec.ts new file mode 100644 index 0000000000..9f06614d4f --- /dev/null +++ b/site/gatsby-site/playwright/e2e-full/apps/classifications.spec.ts @@ -0,0 +1,54 @@ +import { expect } from '@playwright/test'; +import { test } from '../../utils'; + +const url = '/apps/classifications/'; + + +test.describe('Classifications App', () => { + test('Should successfully load', async ({ page }) => { + await page.goto(url); + await expect(page).toHaveURL(url); + }); + + test('Should successfully load the table', async ({ page }) => { + await page.goto(url); + + await page.waitForSelector('[data-cy="row"]'); + await expect(page.locator('[data-cy="row"]')).toHaveCount(1); + }); + + test('Successfully edit a CSET classification', async ({ page, login }) => { + + test.slow(); + + await login(process.env.E2E_ADMIN_USERNAME, process.env.E2E_ADMIN_PASSWORD, { customData: { roles: ['admin'] } }); + + await page.goto(url); + + await page.selectOption('select[data-cy="taxonomy"]', 'CSETv1'); + + const newTabPromise = page.waitForEvent('popup'); + + await page.locator('a[href="/cite/3/?edit_taxonomy=CSETv1"]').click() + + const newTab = await newTabPromise; + + await newTab.waitForLoadState(); + + await expect(newTab.locator('[data-cy="taxonomy-form"]')).toBeVisible(); + }); + + test('Should switch taxonomies', async ({ page }) => { + + await page.goto(url); + await page.waitForSelector('select[data-cy="taxonomy"]'); + + await page.selectOption('select[data-cy="taxonomy"]', 'CSETv1'); + await page.waitForSelector('select[data-cy="taxonomy"]'); + + await page.selectOption('select[data-cy="taxonomy"]', 'GMF'); + await page.waitForSelector('[role="columnheader"]'); + + await expect(page.locator('[role="columnheader"]').getByText('Known AI Goal', { exact: true })).toBeVisible(); + }); +}); \ No newline at end of file diff --git a/site/gatsby-site/playwright/e2e-full/apps/csettool.spec.ts b/site/gatsby-site/playwright/e2e-full/apps/csettool.spec.ts new file mode 100644 index 0000000000..62e68e4798 --- /dev/null +++ b/site/gatsby-site/playwright/e2e-full/apps/csettool.spec.ts @@ -0,0 +1,93 @@ +import { expect } from '@playwright/test'; +import { query, test } from '../../utils'; +import gql from 'graphql-tag'; +import { init } from '../../memory-mongo'; + +test.describe('CSET tool', () => { + const url = '/apps/csettool/2/'; + + async function getRow(page, short_name: string, index = 0) { + return page.locator(`[data-cy="column-${short_name}"]`).nth(index).locator('..').locator('..'); + } + + test('Successfully loads CSET annotator classifications', async ({ page, login }) => { + + await init(); + + await login(process.env.E2E_ADMIN_USERNAME, process.env.E2E_ADMIN_PASSWORD, { customData: { roles: ['admin'] } }); + + await page.goto(url); + await page.waitForSelector('[data-cy="column-Incident Number"]'); + + const incidentRow = await getRow(page, 'Incident Number'); + await expect(incidentRow.locator('[data-cy="column-CSETv1_Annotator-1"]')).toHaveText('2'); + await expect(incidentRow.locator('[data-cy="column-CSETv1_Annotator-1"]')).not.toHaveClass('bg-green-100'); + await expect(incidentRow.locator('[data-cy="column-CSETv1_Annotator-2"]')).toHaveText('2'); + await expect(incidentRow.locator('[data-cy="column-CSETv1_Annotator-2"]')).not.toHaveClass('bg-green-100'); + await expect(incidentRow.locator('[data-cy="column-result"]')).toHaveText('2'); + await expect(incidentRow.locator('[data-cy="column-result"]')).toHaveClass(/bg\-green\-100/); + + const annotatorRow = await getRow(page, 'Annotator'); + await expect(annotatorRow.locator('[data-cy="column-CSETv1_Annotator-1"]')).toHaveText('"005"'); + await expect(annotatorRow.locator('[data-cy="column-CSETv1_Annotator-1"]')).toHaveClass(/bg\-red\-100/); + await expect(annotatorRow.locator('[data-cy="column-CSETv1_Annotator-2"]')).toHaveText('"001"'); + await expect(annotatorRow.locator('[data-cy="column-CSETv1_Annotator-2"]')).toHaveClass(/bg\-red\-100/); + await expect(annotatorRow.locator('[data-cy="column-result"]')).toHaveText('skipped'); + + const physicalObjectsRow = await getRow(page, 'Physical Objects'); + await expect(physicalObjectsRow.locator('[data-cy="column-CSETv1_Annotator-1"]')).toHaveText('"no"'); + await expect(physicalObjectsRow.locator('[data-cy="column-CSETv1_Annotator-1"]')).toHaveClass(/bg\-red\-100/); + await expect(physicalObjectsRow.locator('[data-cy="column-CSETv1_Annotator-2"]')).toHaveText('"maybe"'); + await expect(physicalObjectsRow.locator('[data-cy="column-CSETv1_Annotator-2"]')).toHaveClass(/bg\-red\-100/); + await expect(physicalObjectsRow.locator('[data-cy="column-result"]')).toHaveText('Please select a column'); + + // Disambiguate and submit + await (await getRow(page, 'Physical Objects')).locator('[data-cy="column-CSETv1_Annotator-1"]').click(); + await (await getRow(page, 'AI System Description')).locator('[data-cy="column-CSETv1_Annotator-1"]').click(); + + + await page.getByText('Merge Classifications').click(); + + await expect(page.getByText('Classification updated ')).toBeVisible(); + + + const { data: { classifications } } = await query({ + query: gql` + query ($filter: ClassificationFilterType) { + classifications(filter: $filter) { + namespace + incidents { + incident_id + } + reports { + report_number + } + attributes { + short_name + value_json + } + } + }`, + variables: { + "filter": { + "namespace": { + "EQ": "CSETv1" + }, + "incidents": { + "EQ": 2 + } + } + } + }) + + expect(classifications).toMatchObject([ + { + namespace: 'CSETv1', + incidents: [ + { incident_id: 2 } + ], + reports: [], + } + ]); + }); +}); \ No newline at end of file diff --git a/site/gatsby-site/playwright/e2e-full/apps/incidents.spec.ts b/site/gatsby-site/playwright/e2e-full/apps/incidents.spec.ts index cc7d789ab3..c8f75ba756 100644 --- a/site/gatsby-site/playwright/e2e-full/apps/incidents.spec.ts +++ b/site/gatsby-site/playwright/e2e-full/apps/incidents.spec.ts @@ -21,10 +21,10 @@ test.describe('Incidents App', () => { await expect(page.locator('[data-cy="row"]')).toHaveCount(0); }); - test('Successfully filter and edit incident 112', async ({ page, login }) => { + // TODO: this test should be moved to a smoke tests suite since it depends on Algolia production data + test.skip('Successfully filter and edit incident 112', async ({ page, login }) => { - const userId = await login(process.env.E2E_ADMIN_USERNAME, process.env.E2E_ADMIN_PASSWORD); - await init({ customData: { users: [{ userId, first_name: 'John', last_name: 'Doe', roles: ['incident_editor'] }] } }, { drop: true }); + await login(process.env.E2E_ADMIN_USERNAME, process.env.E2E_ADMIN_PASSWORD, { customData: { first_name: 'John', last_name: 'Doe', roles: ['admin'] } }); await conditionalIntercept( page, diff --git a/site/gatsby-site/playwright/e2e-full/apps/variants.spec.ts b/site/gatsby-site/playwright/e2e-full/apps/variants.spec.ts index e148145803..628f50a751 100644 --- a/site/gatsby-site/playwright/e2e-full/apps/variants.spec.ts +++ b/site/gatsby-site/playwright/e2e-full/apps/variants.spec.ts @@ -42,8 +42,7 @@ test.describe('Variants App', () => { test('Should Delete a Variant - Incident Editor user', async ({ page, login }) => { - const userId = await login(process.env.E2E_ADMIN_USERNAME, process.env.E2E_ADMIN_PASSWORD); - await init({ customData: { users: [{ userId, first_name: 'John', last_name: 'Doe', roles: ['incident_editor'] }] } }, { drop: true }); + await login(process.env.E2E_ADMIN_USERNAME, process.env.E2E_ADMIN_PASSWORD, { customData: { roles: ['incident_editor'], first_name: 'John', last_name: 'Doe' } }); await page.goto(url); @@ -60,9 +59,9 @@ test.describe('Variants App', () => { test('Should Approve a Variant - Incident Editor user', async ({ page, login }) => { - const userId = await login(process.env.E2E_ADMIN_USERNAME, process.env.E2E_ADMIN_PASSWORD); - await init({ customData: { users: [{ userId, first_name: 'John', last_name: 'Doe', roles: ['incident_editor'] }] } }, { drop: true }); + await init(); + await login(process.env.E2E_ADMIN_USERNAME, process.env.E2E_ADMIN_PASSWORD, { customData: { roles: ['incident_editor'], first_name: 'John', last_name: 'Doe' } }); await page.goto(url); @@ -76,8 +75,9 @@ test.describe('Variants App', () => { test('Should Reject a Variant - Incident Editor user', async ({ page, login }) => { - const userId = await login(process.env.E2E_ADMIN_USERNAME, process.env.E2E_ADMIN_PASSWORD); - await init({ customData: { users: [{ userId, first_name: 'John', last_name: 'Doe', roles: ['incident_editor'] }] } }, { drop: true }); + await init(); + + await login(process.env.E2E_ADMIN_USERNAME, process.env.E2E_ADMIN_PASSWORD, { customData: { roles: ['incident_editor'], first_name: 'John', last_name: 'Doe' } }); await page.goto(url); @@ -91,8 +91,9 @@ test.describe('Variants App', () => { test('Should Edit a Variant - Incident Editor user', async ({ page, login }) => { - const userId = await login(process.env.E2E_ADMIN_USERNAME, process.env.E2E_ADMIN_PASSWORD); - await init({ customData: { users: [{ userId, first_name: 'John', last_name: 'Doe', roles: ['incident_editor'] }] } }, { drop: true }); + await init(); + + await login(process.env.E2E_ADMIN_USERNAME, process.env.E2E_ADMIN_PASSWORD, { customData: { roles: ['incident_editor'], first_name: 'John', last_name: 'Doe' } }); const newDatePublished = '2000-01-01'; const newText = 'New text example with more than 80 characters. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.'; diff --git a/site/gatsby-site/playwright/e2e-full/cite.spec.ts b/site/gatsby-site/playwright/e2e-full/cite.spec.ts index 455d8e1e17..63e217b59b 100644 --- a/site/gatsby-site/playwright/e2e-full/cite.spec.ts +++ b/site/gatsby-site/playwright/e2e-full/cite.spec.ts @@ -420,9 +420,9 @@ test.describe('Cite pages', () => { test('Should flag an incident as not related (authenticated)', async ({ page, login }) => { - const userId = await login(config.E2E_ADMIN_USERNAME, config.E2E_ADMIN_PASSWORD); + await init(); - await init({ customData: { users: [{ userId, first_name: 'Test', last_name: 'User', roles: ['admin'] }] } }); + await login(config.E2E_ADMIN_USERNAME, config.E2E_ADMIN_PASSWORD, { customData: { first_name: 'Test', last_name: 'User', roles: ['admin'] } }); await conditionalIntercept( page, @@ -486,7 +486,7 @@ test.describe('Cite pages', () => { await expect(page.locator('head meta[name="twitter:site"]')).toHaveAttribute('content', '@IncidentsDB'); await expect(page.locator('head meta[name="twitter:creator"]')).toHaveAttribute('content', '@IncidentsDB'); - await expect(page.locator('head meta[property="og:url"]')).toHaveAttribute('content', `https://incidentdatabase.ai${url}/`); + await expect(page.locator('head meta[property="og:url"]')).toHaveAttribute('content', new RegExp(`^https://incidentdatabase.ai${url}/?$`)); await expect(page.locator('head meta[property="og:type"]')).toHaveAttribute('content', 'website'); await expect(page.locator('head meta[property="og:title"]')).toHaveAttribute('content', title); await expect(page.locator('head meta[property="og:description"]')).toHaveAttribute('content', description); @@ -569,14 +569,9 @@ test.describe('Cite pages', () => { test.slow(); - const userId = await login(process.env.E2E_ADMIN_USERNAME, process.env.E2E_ADMIN_PASSWORD); - await init({ - customData: { - users: [ - { userId, first_name: 'John', last_name: 'Doe', roles: ['admin'] }, - ] - } - }, { drop: false }); + await init(); + + await login(process.env.E2E_ADMIN_USERNAME, process.env.E2E_ADMIN_PASSWORD, { customData: { first_name: 'John', last_name: 'Doe', roles: ['admin'] } }); await conditionalIntercept( page, @@ -608,7 +603,7 @@ test.describe('Cite pages', () => { await page.locator('[data-cy="related-byId"] [data-cy="result"]:nth-child(1)').getByText('No', { exact: true }).click(); - await page.locator('button[type="submit"]').click(); + await page.getByText('Save').click(); await waitForRequest('logIncidentHistory'); diff --git a/site/gatsby-site/playwright/e2e-full/citeEdit.spec.ts b/site/gatsby-site/playwright/e2e-full/citeEdit.spec.ts index 50568f2912..989e4fb361 100644 --- a/site/gatsby-site/playwright/e2e-full/citeEdit.spec.ts +++ b/site/gatsby-site/playwright/e2e-full/citeEdit.spec.ts @@ -9,6 +9,11 @@ import gql from 'graphql-tag'; test.describe('Edit report', () => { const url = '/cite/edit?report_number=3&incident_id=3'; + test.beforeEach(async () => { + + await init(); + }); + test('Successfully loads', async ({ page }) => { await page.goto(url); }); @@ -17,8 +22,7 @@ test.describe('Edit report', () => { test.slow(); - const userId = await login(config.E2E_ADMIN_USERNAME, config.E2E_ADMIN_PASSWORD); - await init({ customData: { users: [{ userId, first_name: 'Test', last_name: 'User', roles: ['admin'] }] }, }, { drop: true }); + await login(config.E2E_ADMIN_USERNAME, config.E2E_ADMIN_PASSWORD, { customData: { roles: ['admin'] } }); // TODO: delete once we implement the new report history // it should be done inside the report mutation resolver @@ -147,8 +151,7 @@ test.describe('Edit report', () => { test.slow(); - const userId = await login(config.E2E_ADMIN_USERNAME, config.E2E_ADMIN_PASSWORD); - await init({ customData: { users: [{ userId, first_name: 'Test', last_name: 'User', roles: ['admin'] }] } }, { drop: true }); + await login(config.E2E_ADMIN_USERNAME, config.E2E_ADMIN_PASSWORD, { customData: { roles: ['admin'] } }); await conditionalIntercept( page, @@ -231,8 +234,7 @@ test.describe('Edit report', () => { test('Should delete incident report', async ({ page, login }) => { - const userId = await login(config.E2E_ADMIN_USERNAME, config.E2E_ADMIN_PASSWORD); - await init({ customData: { users: [{ userId, first_name: 'Test', last_name: 'User', roles: ['admin'] }] } }, { drop: true }); + await login(config.E2E_ADMIN_USERNAME, config.E2E_ADMIN_PASSWORD, { customData: { roles: ['admin'] } }); await page.goto(url); @@ -323,8 +325,7 @@ test.describe('Edit report', () => { test.slow(); - const userId = await login(config.E2E_ADMIN_USERNAME, config.E2E_ADMIN_PASSWORD); - await init({ customData: { users: [{ userId, first_name: 'Test', last_name: 'User', roles: ['admin'] }] } }, { drop: true }); + await login(config.E2E_ADMIN_USERNAME, config.E2E_ADMIN_PASSWORD, { customData: { roles: ['admin'] } }); await conditionalIntercept( page, @@ -371,9 +372,7 @@ test.describe('Edit report', () => { test('Should display the report image', async ({ page, login }) => { - const userId = await login(config.E2E_ADMIN_USERNAME, config.E2E_ADMIN_PASSWORD); - await init({ customData: { users: [{ userId, first_name: 'Test', last_name: 'User', roles: ['admin'] }] } }, { drop: true }); - + await login(config.E2E_ADMIN_USERNAME, config.E2E_ADMIN_PASSWORD, { customData: { roles: ['admin'] } }); await page.goto(url); await page.locator('[data-cy="image-preview-figure"] img').waitFor(); diff --git a/site/gatsby-site/playwright/e2e-full/classificationsEditor.spec.ts b/site/gatsby-site/playwright/e2e-full/classificationsEditor.spec.ts new file mode 100644 index 0000000000..986b3c589e --- /dev/null +++ b/site/gatsby-site/playwright/e2e-full/classificationsEditor.spec.ts @@ -0,0 +1,274 @@ +import { test, waitForRequest, query } from '../utils'; +import { gql } from '@apollo/client'; +import { expect, Page } from '@playwright/test'; +import { init } from '../memory-mongo'; + +test.describe('Classifications Editor', () => { + + const incidentId = 3; + const reportNumber = 5; + const incidentURL = `/cite/${incidentId}`; + const reportURL = `/reports/${reportNumber}`; + + async function editAndSubmitForm(page: Page) { + await page.locator('[data-cy="classifications-editor"] [data-cy="taxonomy-CSETv1"] >> text=Edit').click(); + await page.locator('[data-cy="classifications-editor"] [data-cy="taxonomy-CSETv1"] [data-cy="Notes"] textarea').fill('Test notes'); + await page.locator('[data-cy="classifications-editor"] [data-cy="taxonomy-CSETv1"] >> text=Submit').click(); + await expect(page.locator('[data-cy="classifications-editor"] [data-cy="taxonomy-CSETv1"] >> text=Submit')).toBeDisabled(); + } + + async function setField(page, { short_name, display_type, permitted_values }) { + let value = permitted_values?.length > 0 ? permitted_values[0] : 'Test'; + await page.locator(`[data-cy="${short_name}"]`).first().scrollIntoViewIfNeeded(); + + const fieldLocator = page.locator(`[data-cy="${short_name}"]`).first(); + await fieldLocator.scrollIntoViewIfNeeded(); + + switch (display_type) { + case 'enum': + permitted_values.length <= 5 + ? await fieldLocator.locator(`[value="${value}"]`).check() + : await fieldLocator.locator('select').selectOption(value); + break; + case 'bool': + await fieldLocator.locator(`[id="${short_name}-yes"]`).click(); + value = true; + break; + case 'string': + await fieldLocator.locator('input').fill(value); + break; + case 'date': + await fieldLocator.locator('input').fill('2023-01-01'); + value = '2023-01-01'; + break; + case 'location': + await fieldLocator.locator('input').fill(`${value}`); + await page.keyboard.press('Enter'); + break; + case 'list': + await fieldLocator.locator('input[type="text"]').fill(`${value}`); + await page.keyboard.press('Enter'); + value = [value]; + break; + case 'multi': + await fieldLocator.locator(`input[value="${value}"]`).click(); + value = [value]; + break; + case 'long_string': + await fieldLocator.locator('textarea').fill(value); + break; + case 'int': + await fieldLocator.locator('input').fill('1'); + value = 1; + break; + case 'object-list': + break; + default: + throw new Error(`Unknown display type: ${display_type} for ${short_name}`); + } + + return value; + } + + test('Shouldn\'t show the classifications editor for unauthenticated users', async ({ page, skipOnEmptyEnvironment }) => { + await page.goto(incidentURL); + await expect(page.locator('[data-cy="classifications-editor"]')).not.toBeVisible(); + }); + + test('Should show classifications editor on incident page and save edited values', async ({ page, login, skipOnEmptyEnvironment }) => { + + await init(); + + await login(process.env.E2E_ADMIN_USERNAME, process.env.E2E_ADMIN_PASSWORD, { customData: { roles: ['admin'] } }); + + await page.goto(incidentURL); + + await expect(page.locator('[data-cy="classifications-editor"]')).toBeVisible(); + await expect(page.locator('[data-cy="classifications-editor"] [data-cy="taxonomy-CSETv1"]')).toBeVisible(); + + await editAndSubmitForm(page); + + await expect(page.locator('[data-cy="classifications-editor"]').getByText('Submit')).not.toBeVisible(); + + const { data: { classifications } } = await query({ + query: gql` + query FindClassifications($filter: ClassificationFilterType!) { + classifications(filter: $filter) { + notes + attributes { + short_name + value_json + } + } + } + `, variables: { filter: { incidents: { EQ: 3 }, namespace: { EQ: "CSETv1" } } } + }); + + expect(classifications).toHaveLength(1); + expect(classifications[0]).toMatchObject({ + notes: 'Test notes' + }); + }); + + test('Should show classifications editor on report page and save edited values', async ({ page, login, skipOnEmptyEnvironment }) => { + + await login(process.env.E2E_ADMIN_USERNAME, process.env.E2E_ADMIN_PASSWORD, { customData: { first_name: 'John', last_name: 'Doe', roles: ['admin'] } }); + + await page.goto(reportURL); + await waitForRequest('FindClassifications'); + await expect(page.locator('[data-cy="classifications-editor"]')).toBeVisible(); + await expect(page.locator('[data-cy="classifications-editor"] [data-cy="taxonomy-CSETv1"]')).toBeVisible(); + + await editAndSubmitForm(page); + + await expect(page.locator('[data-cy="classifications-editor"]').getByText('Submit')).not.toBeVisible(); + + const { data: { classifications } } = await query({ + query: gql` + query FindClassifications($filter: ClassificationFilterType!) { + classifications(filter: $filter) { + notes + attributes { + short_name + value_json + } + } + } + `, variables: { filter: { reports: { EQ: 5 }, namespace: { EQ: "CSETv1" } } } + }); + + expect(classifications).toHaveLength(1); + expect(classifications[0]).toMatchObject({ + notes: 'Test notes' + }); + }); + + test('Should show classifications editor on report page and add a new classification', async ({ page, login, skipOnEmptyEnvironment }) => { + + await init(); + + await login(process.env.E2E_ADMIN_USERNAME, process.env.E2E_ADMIN_PASSWORD, { customData: { first_name: 'John', last_name: 'Doe', roles: ['admin'] } }); + + await page.goto(reportURL); + await waitForRequest('FindClassifications'); + await expect(page.locator('[data-cy="classifications-editor"]')).toBeVisible(); + await page.locator('text=Select a taxonomy').click(); + await page.locator('text=GMF').click(); + await page.locator('text=Add').first().click(); + await expect(page.locator('[data-cy="classifications-editor"] [data-cy="taxonomy-GMF"]')).toBeVisible(); + + await page.locator('[data-cy="classifications-editor"] [data-cy="taxonomy-GMF"] [data-cy="Notes"] textarea').fill('Test notes'); + await page.locator('[data-cy="classifications-editor"] [data-cy="taxonomy-GMF"] >> text=Submit').click(); + + await expect(page.locator('[data-cy="classifications-editor"]').getByText('Submit')).not.toBeVisible(); + + const { data: { classifications } } = await query({ + query: gql` + query FindClassifications($filter: ClassificationFilterType!) { + classifications(filter: $filter) { + namespace + notes + attributes { + short_name + value_json + } + } + } + `, variables: { filter: { reports: { EQ: 5 }, namespace: { EQ: "GMF" } } } + }); + + expect(classifications).toHaveLength(1); + expect(classifications[0]).toMatchObject({ + notes: 'Test notes', + namespace: 'GMF' + }) + }); + + const namespaces = ['GMF', 'CSETv1']; + + for (const namespace of namespaces) { + + test(`Should properly display and store ${namespace} classification values`, async ({ page, login, skipOnEmptyEnvironment }) => { + + await init(); + + await login(process.env.E2E_ADMIN_USERNAME, process.env.E2E_ADMIN_PASSWORD, { customData: { first_name: 'John', last_name: 'Doe', roles: ['admin'] } }); + + await page.goto('/cite/1'); + + const { data: { taxas } } = await query({ + query: gql` + query TaxasQuery($namespace: String) { + taxas(filter: { namespace: {EQ: $namespace} }) { + namespace + field_list { + permitted_values + display_type + short_name + mongo_type + } + } + } + `, + variables: { namespace } + }); + + for (const taxa of taxas) { + await page.locator('text=Select a taxonomy').click(); + await page.locator(`[data-testid="flowbite-tooltip"] >> text=${namespace}`).first().click(); + await page.locator('text=Add').first().click(); + + const selectedValues = {}; + + for (const field of taxa.field_list) { + const value = await setField(page, { + short_name: field.short_name, + display_type: field.display_type, + permitted_values: field.permitted_values + }); + selectedValues[field.short_name] = value; + } + + await page.locator(`[data-cy="classifications-editor"]`).getByText(`Submit`).click(); + + await expect(page.locator(`[data-cy="classifications-editor"]`).getByText(`Submit`)).not.toBeVisible(); + + const { data: { classifications } } = await query({ + query: gql` + query TaxasQuery($namespace: String) { + classifications(filter: { namespace: { EQ: $namespace }, incidents: { EQ: 1 } }) { + namespace + attributes { + short_name + value_json + } + } + } + `, + variables: { namespace } + }); + + const [classification] = classifications; + + for (const [name, value] of Object.entries(selectedValues)) { + + classification.attributes.find(({ short_name }) => short_name === name).value_json === JSON.stringify(value); + } + } + }); + } + + test('Should synchronize duplicate fields', async ({ page, login, skipOnEmptyEnvironment }) => { + await login(process.env.E2E_ADMIN_USERNAME, process.env.E2E_ADMIN_PASSWORD, { customData: { first_name: 'John', last_name: 'Doe', roles: ['admin'] } }); + + await page.goto(incidentURL); + await waitForRequest('FindClassifications'); + + await page.locator('[data-cy="taxonomy-CSETv1"] >> text=Edit').click(); + await page.locator('[data-cy="taxonomy-CSETv1"] [data-cy="AI System"] >> text=yes').first().click(); + await page.locator('[data-cy="taxonomy-CSETv1"] [data-cy="AI System"] [value="yes"]').first().check(); + await expect(page.locator('[data-cy="taxonomy-CSETv1"] [data-cy="AI System"] [value="yes"]').last()).toBeChecked(); + await page.locator('[data-cy="taxonomy-CSETv1"] [data-cy="AI System"] [value="yes"]').last().click(); + await expect(page.locator('[data-cy="taxonomy-CSETv1"] [data-cy="AI System"] [value="yes"]').first()).not.toBeChecked(); + }); +}); diff --git a/site/gatsby-site/playwright/e2e-full/incidents/edit.spec.ts b/site/gatsby-site/playwright/e2e-full/incidents/edit.spec.ts index a2152ec615..bc5fec2161 100644 --- a/site/gatsby-site/playwright/e2e-full/incidents/edit.spec.ts +++ b/site/gatsby-site/playwright/e2e-full/incidents/edit.spec.ts @@ -1,5 +1,5 @@ import { expect } from '@playwright/test'; -import { conditionalIntercept, waitForRequest, test } from '../../utils'; +import { conditionalIntercept, waitForRequest, test, fillAutoComplete } from '../../utils'; import { init } from '../../memory-mongo'; test.describe('Incidents', () => { @@ -7,15 +7,15 @@ test.describe('Incidents', () => { test('Should successfully edit incident fields', async ({ page, login }) => { - const userId = await login(process.env.E2E_ADMIN_USERNAME, process.env.E2E_ADMIN_PASSWORD); await init({ customData: { users: [ - { userId, first_name: 'John', last_name: 'Doe', roles: ['admin'] }, { userId: 'pablo', first_name: 'Pablo', last_name: 'Costa', roles: ['admin'] }, ] } - }, { drop: false }); + }); + + await login(process.env.E2E_ADMIN_USERNAME, process.env.E2E_ADMIN_PASSWORD, { customData: { roles: ['admin'], first_name: 'John', last_name: 'Doe' } }); await page.goto(url); @@ -35,8 +35,7 @@ test.describe('Incidents', () => { await expect(await page.locator('[title="Sean McGregor"]')).toBeVisible(); - await page.locator('#input-editors').fill('Pablo'); - await page.locator('[aria-label="Pablo Costa"]').click(); + await fillAutoComplete(page, '#input-editors', 'Pab', 'Pablo Costa'); await conditionalIntercept(page, '**/graphql', (req) => req.postDataJSON().operationName == 'logIncidentHistory', { data: { diff --git a/site/gatsby-site/playwright/e2e-full/incidents/new.spec.ts b/site/gatsby-site/playwright/e2e-full/incidents/new.spec.ts index 3c5d316181..59f66e0ea9 100644 --- a/site/gatsby-site/playwright/e2e-full/incidents/new.spec.ts +++ b/site/gatsby-site/playwright/e2e-full/incidents/new.spec.ts @@ -1,5 +1,4 @@ -import { test, conditionalIntercept, waitForRequest, query } from '../../utils'; -import { expect } from '@playwright/test'; +import { test, conditionalIntercept, waitForRequest, query, fillAutoComplete } from '../../utils'; import { init } from '../../memory-mongo'; test.describe('New Incident page', () => { @@ -11,17 +10,11 @@ test.describe('New Incident page', () => { test('Should successfully create a new incident', async ({ page, login }) => { - test.slow(); + await init(); - const userId = await login(process.env.E2E_ADMIN_USERNAME, process.env.E2E_ADMIN_PASSWORD); - await init({ - customData: { - users: [ - { userId, first_name: 'John', last_name: 'Doe', roles: ['admin'] }, - ] - } - }, { drop: false }); + test.slow(); + await login(process.env.E2E_ADMIN_USERNAME, process.env.E2E_ADMIN_PASSWORD, { customData: { roles: ['admin'], first_name: 'John', last_name: 'Doe' } }); await page.goto(url); @@ -42,12 +35,7 @@ test.describe('New Incident page', () => { await page.locator('[data-cy="alleged-harmed-or-nearly-harmed-parties-input"] input').first().fill('children'); await page.keyboard.press('Enter'); - await expect(async () => { - await page.locator('#input-editors').clear(); - await page.waitForTimeout(1000); - await page.locator('#input-editors').pressSequentially('Joh', { delay: 500 }); - await page.getByText('John Doe').click({ timeout: 1000 }); - }).toPass(); + await fillAutoComplete(page, '#input-editors', 'Joh', 'John Doe'); await conditionalIntercept(page, '**/graphql', @@ -62,21 +50,16 @@ test.describe('New Incident page', () => { await waitForRequest('logIncidentHistory'); - await page.getByText(`You have successfully create Incident ${4}. View incident`).waitFor(); + await page.getByText(`You have successfully create Incident 4. View incident`).waitFor(); }); test('Should clone an incident', async ({ page, login }) => { + await init(); + test.slow(); - const userId = await login(process.env.E2E_ADMIN_USERNAME, process.env.E2E_ADMIN_PASSWORD); - await init({ - customData: { - users: [ - { userId, first_name: 'John', last_name: 'Doe', roles: ['admin'] }, - ] - } - }, { drop: false }); + await login(process.env.E2E_ADMIN_USERNAME, process.env.E2E_ADMIN_PASSWORD, { customData: { roles: ['admin'], first_name: 'John', last_name: 'Doe' } }); const newIncidentId = 4; diff --git a/site/gatsby-site/playwright/e2e-full/submit.spec.ts b/site/gatsby-site/playwright/e2e-full/submit.spec.ts index a0d50b620b..ed62314a48 100644 --- a/site/gatsby-site/playwright/e2e-full/submit.spec.ts +++ b/site/gatsby-site/playwright/e2e-full/submit.spec.ts @@ -1,5 +1,5 @@ import parseNews from '../fixtures/api/parseNews.json'; -import { conditionalIntercept, waitForRequest, setEditorText, test, trackRequest, query } from '../utils'; +import { conditionalIntercept, waitForRequest, setEditorText, test, trackRequest, query, fillAutoComplete } from '../utils'; import { expect } from '@playwright/test'; import config from '../config'; import { init } from '../memory-mongo'; @@ -147,9 +147,9 @@ test.describe('The Submit form', () => { test('As editor, should submit a new incident report, adding an incident title and editors.', async ({ page, login, skipOnEmptyEnvironment }) => { - const userId = await login(config.E2E_ADMIN_USERNAME, config.E2E_ADMIN_PASSWORD); + await init(); - await init({ customData: { users: [{ userId, first_name: 'Test', last_name: 'User', roles: ['admin'] }] } }, { drop: true }); + const userId = await login(config.E2E_ADMIN_USERNAME, config.E2E_ADMIN_PASSWORD, { customData: { first_name: 'Cesar', last_name: 'Ito', roles: ['admin'] } }); await conditionalIntercept( page, @@ -201,9 +201,7 @@ test.describe('The Submit form', () => { await page.locator('[name="description"]').fill('Description'); - await page.locator('#input-incident_editors').fill('Test'); - - await page.locator('[aria-label="Test User"]').click(); + await fillAutoComplete(page, "#input-incident_editors", 'Ces', 'Cesar Ito'); await page.locator('[name="tags"]').fill('New Tag'); await page.keyboard.press('Enter'); @@ -309,7 +307,7 @@ test.describe('The Submit form', () => { await expect(page.locator(':text("Report successfully added to review queue")')).toBeVisible(); - await login(config.E2E_ADMIN_USERNAME, config.E2E_ADMIN_PASSWORD); + await login(config.E2E_ADMIN_USERNAME, config.E2E_ADMIN_PASSWORD, { customData: { first_name: 'Test', last_name: 'User', roles: ['admin'] } }); await page.goto('/apps/submitted'); @@ -390,10 +388,7 @@ test.describe('The Submit form', () => { test('Should submit a submission and link it to the current user id', async ({ page, login, skipOnEmptyEnvironment }) => { - const userId = await login(config.E2E_ADMIN_USERNAME, config.E2E_ADMIN_PASSWORD); - - await init({ customData: { users: [{ userId, first_name: 'Test', last_name: 'User', roles: ['admin'] }] } }, { drop: true }); - + const userId = await login(config.E2E_ADMIN_USERNAME, config.E2E_ADMIN_PASSWORD, { customData: { first_name: 'Test', last_name: 'User', roles: ['admin'] } }); const values = { url: 'https://incidentdatabase.ai', diff --git a/site/gatsby-site/playwright/e2e-full/utils.spec.ts b/site/gatsby-site/playwright/e2e-full/utils.spec.ts new file mode 100644 index 0000000000..ba8a5a8004 --- /dev/null +++ b/site/gatsby-site/playwright/e2e-full/utils.spec.ts @@ -0,0 +1,17 @@ +import { expect } from '@playwright/test'; +import { test } from '../utils'; +import config from '../config'; + +test.describe('Test playwright utils', () => { + + test('Login fixture should mock user and roles', async ({ page, login }) => { + + await login(config.E2E_ADMIN_USERNAME, config.E2E_ADMIN_PASSWORD, { customData: { roles: ['sarasa'], first_name: 'Fula', last_name: 'Nito' } }); + + await page.goto('/account'); + + await expect(page.getByText('Fula')).toBeVisible(); + await expect(page.getByText('Nito')).toBeVisible(); + await expect(page.getByText('sarasa')).toBeVisible(); + }); +}); \ No newline at end of file diff --git a/site/gatsby-site/playwright/e2e/integration/classificationsEditor.spec.ts b/site/gatsby-site/playwright/e2e/integration/classificationsEditor.spec.ts deleted file mode 100644 index 8d271307cf..0000000000 --- a/site/gatsby-site/playwright/e2e/integration/classificationsEditor.spec.ts +++ /dev/null @@ -1,308 +0,0 @@ -import { test, conditionalIntercept, waitForRequest, setEditorText, query } from '../../utils'; -import classificationsMock from '../../fixtures/classifications/editor.json'; -import classificationsUpsertMock from '../../fixtures/classifications/editorUpsert.json'; -import editorCSETV1Mock from '../../fixtures/classifications/editorCSETV1.json'; -import { gql } from '@apollo/client'; -import { expect } from '@playwright/test'; - -test.describe('Classifications Editor', () => { - - const incidentId = 2; - const reportNumber = 2658; - const incidentURL = `/cite/${incidentId}`; - const reportURL = `/reports/${reportNumber}`; - - async function editAndSubmitForm(page) { - await page.locator('[data-cy="classifications-editor"] [data-cy="taxonomy-CSETv0"] >> text=Edit').click(); - await page.locator('[data-cy="classifications-editor"] [data-cy="taxonomy-CSETv0"] [data-cy="Notes"] textarea').scrollIntoViewIfNeeded(); - await page.locator('[data-cy="classifications-editor"] [data-cy="taxonomy-CSETv0"] [data-cy="Notes"] textarea').fill('Test notes'); - await page.locator('[data-cy="classifications-editor"] [data-cy="taxonomy-CSETv0"] [data-cy="Full Description"] input').scrollIntoViewIfNeeded(); - await page.locator('[data-cy="classifications-editor"] [data-cy="taxonomy-CSETv0"] [data-cy="Full Description"] input').fill('Test description'); - await page.locator('[data-cy="classifications-editor"] [data-cy="taxonomy-CSETv0"] >> text=Submit').click(); - await expect(page.locator('[data-cy="classifications-editor"] [data-cy="taxonomy-CSETv0"] >> text=Submit')).toBeDisabled(); - } - - async function setField(page, { short_name, display_type, permitted_values }) { - let value = permitted_values?.length > 0 ? permitted_values[0] : 'Test'; - await page.locator(`[data-cy="${short_name}"]`).first().scrollIntoViewIfNeeded(); - - const fieldLocator = page.locator(`[data-cy="${short_name}"]`).first(); - await fieldLocator.scrollIntoViewIfNeeded(); - - switch (display_type) { - case 'enum': - permitted_values.length <= 5 - ? await fieldLocator.locator(`[value="${value}"]`).check() - : await fieldLocator.locator('select').selectOption(value); - break; - case 'bool': - await fieldLocator.locator(`[id="${short_name}-yes"]`).click(); - value = true; - break; - case 'string': - await fieldLocator.locator('input').fill(value); - break; - case 'date': - await fieldLocator.locator('input').fill('2023-01-01'); - value = '2023-01-01'; - break; - case 'location': - await fieldLocator.locator('input').fill(`${value}`); - await page.keyboard.press('Enter'); - break; - case 'list': - await fieldLocator.locator('input[type="text"]').fill(`${value}`); - await page.keyboard.press('Enter'); - value = [value]; - break; - case 'multi': - await fieldLocator.locator(`input[value="${value}"]`).click(); - value = [value]; - break; - case 'long_string': - await fieldLocator.locator('textarea').fill(value); - break; - case 'int': - await fieldLocator.locator('input').fill('1'); - value = 1; - break; - case 'object-list': - break; - default: - throw new Error(`Unknown display type: ${display_type} for ${short_name}`); - } - - return value; - } - - test('Shouldn\'t show the classifications editor for unauthenticated users', async ({ page, skipOnEmptyEnvironment }) => { - await page.goto(incidentURL); - await expect(page.locator('[data-cy="classifications-editor"]')).not.toBeVisible(); - }); - - test('Should show classifications editor on incident page and save edited values', async ({ page, login, skipOnEmptyEnvironment }) => { - await login(process.env.E2E_ADMIN_USERNAME, process.env.E2E_ADMIN_PASSWORD); - - await conditionalIntercept( - page, - '**/graphql', - (req) => req.postDataJSON().operationName == 'FindClassifications', - classificationsMock, - 'FindClassifications' - ); - - await conditionalIntercept( - page, - '**/graphql', - (req) => req.postDataJSON().operationName == 'UpsertClassification', - classificationsUpsertMock, - 'UpsertClassification' - ); - - await page.goto(incidentURL); - await waitForRequest('FindClassifications'); - await expect(page.locator('[data-cy="classifications-editor"]')).toBeVisible(); - await expect(page.locator('[data-cy="classifications-editor"] [data-cy="taxonomy-CSETv0"]')).toBeVisible(); - - await editAndSubmitForm(page); - - const upsertClassificationRequest = await waitForRequest('UpsertClassification'); - const variables = upsertClassificationRequest.postDataJSON().variables; - expect(variables.query.reports).toBeUndefined(); - expect(variables.query.incidents).toEqual({ incident_id: incidentId }); - expect(variables.query.namespace).toBe('CSETv0'); - expect(variables.data.incidents).toEqual({ link: [incidentId] }); - expect(variables.data.reports).toEqual({ link: [reportNumber, 2659] }); - expect(variables.data.notes).toBe('Test notes'); - expect(variables.data.attributes.find((a) => a.short_name == 'Full Description').value_json).toBe('"Test description"'); - }); - - test('Should show classifications editor on report page and save edited values', async ({ page, login, skipOnEmptyEnvironment }) => { - await login(process.env.E2E_ADMIN_USERNAME, process.env.E2E_ADMIN_PASSWORD); - - await conditionalIntercept( - page, - '**/graphql', - (req) => req.postDataJSON().operationName == 'FindClassifications', - classificationsMock, - 'FindClassifications' - ); - - await conditionalIntercept( - page, - '**/graphql', - (req) => req.postDataJSON().operationName == 'UpsertClassification', - classificationsUpsertMock, - 'UpsertClassification' - ); - - await page.goto(reportURL); - await waitForRequest('FindClassifications'); - await expect(page.locator('[data-cy="classifications-editor"]')).toBeVisible(); - await expect(page.locator('[data-cy="classifications-editor"] [data-cy="taxonomy-CSETv0"]')).toBeVisible(); - - await editAndSubmitForm(page); - - const upsertClassificationRequest = await waitForRequest('UpsertClassification'); - const variables = upsertClassificationRequest.postDataJSON().variables; - expect(variables.query.incidents).toBeUndefined(); - expect(variables.query.reports).toEqual({ report_number: reportNumber }); - expect(variables.query.namespace).toBe('CSETv0'); - expect(variables.data.incidents).toEqual({ link: [] }); - expect(variables.data.reports).toEqual({ link: [reportNumber, 2659] }); - expect(variables.data.notes).toBe('Test notes'); - expect(variables.data.attributes.find((a) => a.short_name == 'Full Description').value_json).toBe('"Test description"'); - }); - - test('Should show classifications editor on report page and add a new classification', async ({ page, login, skipOnEmptyEnvironment }) => { - await login(process.env.E2E_ADMIN_USERNAME, process.env.E2E_ADMIN_PASSWORD); - - await conditionalIntercept( - page, - '**/graphql', - (req) => req.postDataJSON().operationName == 'FindClassifications', - { data: { classifications: [] } }, - 'FindClassifications' - ); - - await conditionalIntercept( - page, - '**/graphql', - (req) => req.postDataJSON().operationName == 'UpsertClassification', - classificationsUpsertMock, - 'UpsertClassification' - ); - - await page.goto(reportURL); - await waitForRequest('FindClassifications'); - await expect(page.locator('[data-cy="classifications-editor"]')).toBeVisible(); - await page.locator('text=Select a taxonomy').click(); - await page.locator('text=GMF').click(); - await page.locator('text=Add').first().click(); - await expect(page.locator('[data-cy="classifications-editor"] [data-cy="taxonomy-GMF"]')).toBeVisible(); - - await page.locator('[data-cy="classifications-editor"] [data-cy="taxonomy-GMF"] [data-cy="Notes"] textarea').scrollIntoViewIfNeeded(); - await page.locator('[data-cy="classifications-editor"] [data-cy="taxonomy-GMF"] [data-cy="Notes"] textarea').fill('Test notes'); - await page.locator('[data-cy="classifications-editor"] [data-cy="taxonomy-GMF"] >> text=Submit').click(); - - const upsertClassificationRequest = await waitForRequest('UpsertClassification'); - const variables = upsertClassificationRequest.postDataJSON().variables; - expect(variables.query.incidents).toBeUndefined(); - expect(variables.query.reports).toEqual({ report_number: reportNumber }); - expect(variables.query.namespace).toBe('GMF'); - expect(variables.data.incidents).toEqual({ link: [] }); - expect(variables.data.reports).toEqual({ link: [reportNumber] }); - expect(variables.data.notes).toBe('Test notes'); - }); - - const namespaces = ['CSETv0', 'GMF', 'CSETv1']; - for (const namespace of namespaces) { - test(`Should properly display and store ${namespace} classification values`, async ({ page, login, skipOnEmptyEnvironment }) => { - await login(process.env.E2E_ADMIN_USERNAME, process.env.E2E_ADMIN_PASSWORD); - - await conditionalIntercept( - page, - '**/graphql', - (req) => req.postDataJSON().operationName == 'FindClassifications', - { data: { classifications: [] } }, - 'FindClassifications' - ); - - await page.goto(incidentURL); - await waitForRequest('FindClassifications'); - - const { data: { taxas } } = await query({ - query: gql` - query TaxasQuery($namespace: String) { - taxas(query: { namespace: $namespace }) { - namespace - field_list { - permitted_values - display_type - short_name - mongo_type - } - } - } - `, - variables: { namespace } - }); - - for (const taxa of taxas) { - await page.locator('text=Select a taxonomy').scrollIntoViewIfNeeded(); - await page.locator('text=Select a taxonomy').click(); - await page.locator(`[data-testid="flowbite-tooltip"] >> text=${namespace}`).first().click(); - await page.locator('text=Add').first().click(); - await conditionalIntercept( - page, - '**/graphql', - (req) => req.postDataJSON().operationName == 'UpsertClassification' && req.postDataJSON()?.variables?.query.namespace == namespace, - classificationsUpsertMock, - `Upsert-${namespace}`, - ); - - // await page.locator(`[data-cy="classifications-editor"] [data-cy="taxonomy-${namespace}"] text=Edit`).click(); - const selectedValues = {}; - - for (const field of taxa.field_list) { - const value = await setField(page, { - short_name: field.short_name, - display_type: field.display_type, - permitted_values: field.permitted_values - }); - selectedValues[field.short_name] = value; - } - - await page.locator(`[data-cy="classifications-editor"] [data-cy="taxonomy-${namespace}"] >> text=Submit`).click(); - - const upsertRequest = await waitForRequest(`Upsert-${namespace}`); - const variables = upsertRequest.postDataJSON().variables; - expect(variables.query.namespace).toBe(namespace); - - for (const field of taxa.field_list) { - const skippedFields = [ - 'Known AI Technology Snippets', - 'Known AI Technical Failure Snippets', - 'Entities', - 'Known AI Goal Snippets', - 'Potential AI Goal Snippets', - 'Potential AI Technology Snippets', - 'Potential AI Technical Failure Snippets' - ]; - - if (!skippedFields.includes(field.short_name)) { - expect(variables.data.attributes.find((a) => a.short_name == field.short_name)).toEqual({ - short_name: field.short_name, - value_json: JSON.stringify(selectedValues[field.short_name]) - }); - } - } - - } - }); - } - - test('Should synchronize duplicate fields', async ({ page, login, skipOnEmptyEnvironment }) => { - await login(process.env.E2E_ADMIN_USERNAME, process.env.E2E_ADMIN_PASSWORD); - - await conditionalIntercept( - page, - '**/graphql', - (req) => req.postDataJSON().operationName == 'FindClassifications', - editorCSETV1Mock, - 'FindClassifications' - ); - - await page.goto(incidentURL); - await waitForRequest('FindClassifications'); - - await page.locator('[data-cy="taxonomy-CSETv1"]').first().scrollIntoViewIfNeeded(); - await page.locator('[data-cy="taxonomy-CSETv1"] >> text=Edit').click(); - await page.locator('[data-cy="taxonomy-CSETv1"] [data-cy="AI System"]').first().scrollIntoViewIfNeeded(); - await page.locator('[data-cy="taxonomy-CSETv1"] [data-cy="AI System"] >> text=yes').first().click(); - await page.locator('[data-cy="taxonomy-CSETv1"] [data-cy="AI System"] [value="yes"]').first().check(); - await expect(page.locator('[data-cy="taxonomy-CSETv1"] [data-cy="AI System"] [value="yes"]').last()).toBeChecked(); - await page.locator('[data-cy="taxonomy-CSETv1"] [data-cy="AI System"] [value="yes"]').last().click(); - await expect(page.locator('[data-cy="taxonomy-CSETv1"] [data-cy="AI System"] [value="yes"]').first()).not.toBeChecked(); - }); -}); diff --git a/site/gatsby-site/playwright/memory-mongo.ts b/site/gatsby-site/playwright/memory-mongo.ts index 5cf7394812..18ca87a3b4 100644 --- a/site/gatsby-site/playwright/memory-mongo.ts +++ b/site/gatsby-site/playwright/memory-mongo.ts @@ -7,6 +7,8 @@ import reports from './seeds/aiidprod/reports'; import submissions from './seeds/aiidprod/submissions'; import entities from './seeds/aiidprod/entities'; import reports_es from './seeds/translations/reports_es'; +import classifications from './seeds/aiidprod/classifications'; +import taxa from './seeds/aiidprod/taxa'; import users from './seeds/customData/users'; @@ -19,6 +21,8 @@ export const init = async (extra?: Record[], drop?: boolean }) => { @@ -70,6 +74,24 @@ export const seedFixture = async (seeds: Record Promise) => { + + assert(process.env.MONGODB_CONNECTION_STRING?.includes('localhost') || process.env.MONGODB_CONNECTION_STRING?.includes('127.0.0.1'), 'Seeding is only allowed on localhost'); + + const client = new MongoClient(process.env.MONGODB_CONNECTION_STRING!); + + await client.connect(); + + try { + + await fn(client); + } + finally { + + await client.close(); + } +} + // command line support diff --git a/site/gatsby-site/playwright/seeds/aiidprod/classifications.ts b/site/gatsby-site/playwright/seeds/aiidprod/classifications.ts new file mode 100644 index 0000000000..957b23fbef --- /dev/null +++ b/site/gatsby-site/playwright/seeds/aiidprod/classifications.ts @@ -0,0 +1,1038 @@ +import { ObjectId } from 'bson'; +import { Classification } from '../../../server/generated/graphql' + +type DBClassification = Omit + & { incidents: number[] } + & { reports: number[] } + + +const items: DBClassification[] = [ + { + _id: new ObjectId("63f3d58c26ab981f33b3f9c7"), + publish: true, + reports: [], + attributes: [ + { + short_name: "Harm Distribution Basis", + value_json: "[]" + }, + { + short_name: "Sector of Deployment", + value_json: "[]" + }, + { + short_name: "Physical Objects", + value_json: "\"no\"" + }, + { + short_name: "Entertainment Industry", + value_json: "\"no\"" + }, + { + short_name: "Report, Test, or Study of data", + value_json: "\"yes\"" + }, + { + value_json: "\"no\"", + short_name: "Deployed" + }, + { + short_name: "Producer Test in Controlled Conditions", + value_json: "\"no\"" + }, + { + short_name: "Producer Test in Operational Conditions", + value_json: "\"no\"" + }, + { + short_name: "User Test in Controlled Conditions", + value_json: "\"\"" + }, + { + value_json: "\"no\"", + short_name: "User Test in Operational Conditions" + }, + { + short_name: "Harm Domain", + value_json: "\"no\"" + }, + { + short_name: "Tangible Harm", + value_json: "\"yes\"" + }, + { + short_name: "AI System", + value_json: "\"yes\"" + }, + { + short_name: "Clear link to technology", + value_json: "\"no\"" + }, + { + short_name: "There is a potentially identifiable specific entity that experienced the harm", + value_json: "false" + }, + { + short_name: "AI Harm Level", + value_json: "\"none\"" + }, + { + short_name: "Impact on Critical Services", + value_json: "\"no\"" + }, + { + short_name: "Rights Violation", + value_json: "\"yes\"" + }, + { + short_name: "Involving Minor", + value_json: "\"no\"" + }, + { + short_name: "Detrimental Content", + value_json: "\"\"" + }, + { + short_name: "Protected Characteristic", + value_json: "\"\"" + }, + { + short_name: "Clear link to Technology", + value_json: "\"\"" + }, + { + short_name: "Harmed Class of Entities", + value_json: "false" + }, + { + short_name: "Annotator’s AI special interest intangible harm assessment", + value_json: "\"\"" + }, + { + short_name: "Public Sector Deployment", + value_json: "\"\"" + }, + { + value_json: "\"\"", + short_name: "Autonomy Level" + }, + { + value_json: "\"\"", + short_name: "Intentional Harm" + }, + { + short_name: "AI tools and methods", + value_json: "\"\"" + }, + { + short_name: "Peer Reviewer", + value_json: "\"\"" + }, + { + short_name: "Quality Control", + value_json: "false" + }, + { + short_name: "Annotation Status", + value_json: "\"--\"" + }, + { + short_name: "Incident Number", + value_json: "0" + }, + { + short_name: "Annotator", + value_json: "\"\"" + }, + { + short_name: "AI Tangible Harm Level Notes", + value_json: "\"\"" + }, + { + short_name: "Notes (special interest intangible harm)", + value_json: "\"\"" + }, + { + short_name: "Special Interest Intangible Harm", + value_json: "\"\"" + }, + { + short_name: "Notes (AI special interest intangible harm)", + value_json: "\"\"" + }, + { + short_name: "Date of Incident Year", + value_json: "\"\"" + }, + { + short_name: "Date of Incident Month", + value_json: "\"\"" + }, + { + short_name: "Date of Incident Day", + value_json: "\"\"" + }, + { + value_json: "false", + short_name: "Estimated Date" + }, + { + short_name: "Multiple AI Interaction", + value_json: "\"\"" + }, + { + value_json: "\"\"", + short_name: "Embedded" + }, + { + short_name: "Location City", + value_json: "\"\"" + }, + { + short_name: "Location State/Province (two letters)", + value_json: "\"\"" + }, + { + value_json: "\"\"", + short_name: "Location Country (two letters)" + }, + { + short_name: "Location Region", + value_json: "\"Eastern and South-Eastern Asia\"" + }, + { + short_name: "Infrastructure Sectors", + value_json: "[]" + }, + { + short_name: "Operating Conditions", + value_json: "\"\"" + }, + { + short_name: "Notes (Environmental and Temporal Characteristics)", + value_json: "\"\"" + }, + { + short_name: "Entities", + value_json: "[]" + }, + { + short_name: "Lives Lost", + value_json: "0" + }, + { + short_name: "Injuries", + value_json: "4" + }, + { + value_json: "false", + short_name: "Estimated Harm Quantities" + }, + { + short_name: "Notes ( Tangible Harm Quantities Information)", + value_json: "\"\"" + }, + { + short_name: "AI System Description", + value_json: "\"\"" + }, + { + short_name: "Data Inputs", + value_json: "[]" + }, + { + short_name: "Notes (Information about AI System)", + value_json: "\"\"" + }, + { + short_name: "Physical System Type", + value_json: "\"\"" + }, + { + short_name: "AI Task", + value_json: "\"\"" + }, + { + short_name: "Notes (AI Functionality and Techniques)", + value_json: "\"\"" + } + ], + incidents: [3], + namespace: "CSETv1", + notes: "This is a test classification for Incident 3", + + // TODO: not in graphql schema but present on db + // created_at: new Date( "1722269831340") + }, + + { + _id: new ObjectId("63f3d58c26ab981f33b3f9c8"), + publish: true, + reports: [5], + attributes: [ + { + short_name: "Harm Distribution Basis", + value_json: "[]" + }, + { + short_name: "Sector of Deployment", + value_json: "[]" + }, + { + short_name: "Physical Objects", + value_json: "\"\"" + }, + { + short_name: "Entertainment Industry", + value_json: "\"\"" + }, + { + short_name: "Report, Test, or Study of data", + value_json: "\"\"" + }, + { + value_json: "\"\"", + short_name: "Deployed" + }, + { + short_name: "Producer Test in Controlled Conditions", + value_json: "\"\"" + }, + { + short_name: "Producer Test in Operational Conditions", + value_json: "\"\"" + }, + { + short_name: "User Test in Controlled Conditions", + value_json: "\"\"" + }, + { + value_json: "\"\"", + short_name: "User Test in Operational Conditions" + }, + { + short_name: "Harm Domain", + value_json: "\"\"" + }, + { + short_name: "Tangible Harm", + value_json: "\"\"" + }, + { + short_name: "AI System", + value_json: "\"\"" + }, + { + short_name: "Clear link to technology", + value_json: "\"\"" + }, + { + short_name: "There is a potentially identifiable specific entity that experienced the harm", + value_json: "false" + }, + { + short_name: "AI Harm Level", + value_json: "\"\"" + }, + { + short_name: "Impact on Critical Services", + value_json: "\"\"" + }, + { + short_name: "Rights Violation", + value_json: "\"\"" + }, + { + short_name: "Involving Minor", + value_json: "\"\"" + }, + { + short_name: "Detrimental Content", + value_json: "\"\"" + }, + { + short_name: "Protected Characteristic", + value_json: "\"\"" + }, + { + short_name: "Clear link to Technology", + value_json: "\"\"" + }, + { + short_name: "Harmed Class of Entities", + value_json: "false" + }, + { + short_name: "Annotator’s AI special interest intangible harm assessment", + value_json: "\"\"" + }, + { + short_name: "Public Sector Deployment", + value_json: "\"\"" + }, + { + value_json: "\"\"", + short_name: "Autonomy Level" + }, + { + value_json: "\"\"", + short_name: "Intentional Harm" + }, + { + short_name: "AI tools and methods", + value_json: "\"\"" + }, + { + short_name: "Peer Reviewer", + value_json: "\"\"" + }, + { + short_name: "Quality Control", + value_json: "false" + }, + { + short_name: "Annotation Status", + value_json: "\"--\"" + }, + { + short_name: "Incident Number", + value_json: "0" + }, + { + short_name: "Annotator", + value_json: "\"\"" + }, + { + short_name: "AI Tangible Harm Level Notes", + value_json: "\"\"" + }, + { + short_name: "Notes (special interest intangible harm)", + value_json: "\"\"" + }, + { + short_name: "Special Interest Intangible Harm", + value_json: "\"\"" + }, + { + short_name: "Notes (AI special interest intangible harm)", + value_json: "\"\"" + }, + { + short_name: "Date of Incident Year", + value_json: "\"\"" + }, + { + short_name: "Date of Incident Month", + value_json: "\"\"" + }, + { + short_name: "Date of Incident Day", + value_json: "\"\"" + }, + { + value_json: "false", + short_name: "Estimated Date" + }, + { + short_name: "Multiple AI Interaction", + value_json: "\"\"" + }, + { + value_json: "\"\"", + short_name: "Embedded" + }, + { + short_name: "Location City", + value_json: "\"\"" + }, + { + short_name: "Location State/Province (two letters)", + value_json: "\"\"" + }, + { + value_json: "\"\"", + short_name: "Location Country (two letters)" + }, + { + short_name: "Location Region", + value_json: "\"Eastern and South-Eastern Asia\"" + }, + { + short_name: "Infrastructure Sectors", + value_json: "[]" + }, + { + short_name: "Operating Conditions", + value_json: "\"\"" + }, + { + short_name: "Notes (Environmental and Temporal Characteristics)", + value_json: "\"\"" + }, + { + short_name: "Entities", + value_json: "[]" + }, + { + short_name: "Lives Lost", + value_json: "0" + }, + { + short_name: "Injuries", + value_json: "4" + }, + { + value_json: "false", + short_name: "Estimated Harm Quantities" + }, + { + short_name: "Notes ( Tangible Harm Quantities Information)", + value_json: "\"\"" + }, + { + short_name: "AI System Description", + value_json: "\"\"" + }, + { + short_name: "Data Inputs", + value_json: "[]" + }, + { + short_name: "Notes (Information about AI System)", + value_json: "\"\"" + }, + { + short_name: "Physical System Type", + value_json: "\"\"" + }, + { + short_name: "AI Task", + value_json: "\"\"" + }, + { + short_name: "Notes (AI Functionality and Techniques)", + value_json: "\"\"" + } + ], + incidents: [], + namespace: "CSETv1", + notes: "This is a test classification for Report 5", + + // TODO: not in graphql schema but present on db + // created_at: new Date( "1722269831340") + }, + + { + "_id": new ObjectId("64bf3898da1ddcde072ec094"), + "notes": "", + "publish": true, + "reports": [], + "attributes": [ + { + "short_name": "Harm Distribution Basis", + "value_json": "[\"race\",\"sex\"]" + }, + { + "short_name": "Sector of Deployment", + "value_json": "[\"information and communication\"]" + }, + { + "short_name": "Physical Objects", + "value_json": "\"no\"" + }, + { + "short_name": "Entertainment Industry", + "value_json": "\"no\"" + }, + { + "short_name": "Report, Test, or Study of data", + "value_json": "\"no\"" + }, + { + "short_name": "Deployed", + "value_json": "\"yes\"" + }, + { + "short_name": "Producer Test in Controlled Conditions", + "value_json": "\"no\"" + }, + { + "short_name": "Producer Test in Operational Conditions", + "value_json": "\"no\"" + }, + { + "short_name": "User Test in Controlled Conditions", + "value_json": "\"no\"" + }, + { + "short_name": "User Test in Operational Conditions", + "value_json": "\"no\"" + }, + { + "short_name": "Harm Domain", + "value_json": "\"yes\"" + }, + { + "short_name": "Tangible Harm", + "value_json": "\"no tangible harm, near-miss, or issue\"" + }, + { + "value_json": "\"yes\"", + "short_name": "AI System" + }, + { + "short_name": "Clear link to technology", + "value_json": "\"yes\"" + }, + { + "short_name": "There is a potentially identifiable specific entity that experienced the harm", + "value_json": "false" + }, + { + "short_name": "AI Harm Level", + "value_json": "\"none\"" + }, + { + "short_name": "Impact on Critical Services", + "value_json": "\"no\"" + }, + { + "value_json": "\"no\"", + "short_name": "Rights Violation" + }, + { + "short_name": "Involving Minor", + "value_json": "\"no\"" + }, + { + "value_json": "\"no\"", + "short_name": "Detrimental Content" + }, + { + "value_json": "\"yes\"", + "short_name": "Protected Characteristic" + }, + { + "short_name": "Clear link to Technology", + "value_json": "\"yes\"" + }, + { + "short_name": "Harmed Class of Entities", + "value_json": "true" + }, + { + "short_name": "Annotator’s AI special interest intangible harm assessment", + "value_json": "\"yes\"" + }, + { + "short_name": "Public Sector Deployment", + "value_json": "\"no\"" + }, + { + "short_name": "Autonomy Level", + "value_json": "\"Autonomy1\"" + }, + { + "short_name": "Intentional Harm", + "value_json": "\"No. Not intentionally designed to perform harm\"" + }, + { + "value_json": "[]", + "short_name": "AI tools and methods" + }, + { + "short_name": "Peer Reviewer", + "value_json": "\"002\"" + }, + { + "short_name": "Quality Control", + "value_json": "false" + }, + { + "value_json": "\"4. Peer review complete\"", + "short_name": "Annotation Status" + }, + { + "value_json": "2", + "short_name": "Incident Number" + }, + { + "short_name": "Annotator", + "value_json": "\"005\"" + }, + { + "short_name": "AI Tangible Harm Level Notes", + "value_json": "\"\"" + }, + { + "short_name": "Notes (special interest intangible harm)", + "value_json": "\"\"" + }, + { + "short_name": "Special Interest Intangible Harm", + "value_json": "\"yes\"" + }, + { + "short_name": "Notes (AI special interest intangible harm)", + "value_json": "\"\"" + }, + { + "short_name": "Date of Incident Year", + "value_json": "\"2012\"" + }, + { + "short_name": "Date of Incident Month", + "value_json": "\"04\"" + }, + { + "short_name": "Date of Incident Day", + "value_json": "\"\"" + }, + { + "value_json": "true", + "short_name": "Estimated Date" + }, + { + "short_name": "Multiple AI Interaction", + "value_json": "\"no\"" + }, + { + "short_name": "Embedded", + "value_json": "\"no\"" + }, + { + "short_name": "Location City", + "value_json": "\"\"" + }, + { + "short_name": "Location State/Province (two letters)", + "value_json": "\"\"" + }, + { + "short_name": "Location Country (two letters)", + "value_json": "\"\"" + }, + { + "short_name": "Location Region", + "value_json": "\"Global\"" + }, + { + "value_json": "[]", + "short_name": "Infrastructure Sectors" + }, + { + "short_name": "Operating Conditions", + "value_json": "\"\"" + }, + { + "short_name": "Notes (Environmental and Temporal Characteristics)", + "value_json": "\"\"" + }, + { + "short_name": "Entities", + "value_json": "[{\"attributes\":[{\"short_name\":\"Entity\",\"value_json\":\"\\\"Kabir Alli\\\"\"},{\"short_name\":\"Named Entity\",\"value_json\":\"true\"},{\"short_name\":\"Entity type\",\"value_json\":\"\\\"individual\\\"\"},{\"short_name\":\"Entity Relationship to the AI\",\"value_json\":\"[\\\"user\\\"]\"},{\"short_name\":\"Harm Category Experienced\",\"value_json\":\"\\\"AI special interest intangible harm\\\"\"},{\"short_name\":\"Harm Type Experienced\",\"value_json\":\"\\\"disproportionate treatment based upon a protected characteristic\\\"\"}]},{\"attributes\":[{\"short_name\":\"Entity\",\"value_json\":\"\\\"Black teenagers\\\"\"},{\"short_name\":\"Named Entity\",\"value_json\":\"false\"},{\"short_name\":\"Entity type\",\"value_json\":\"\\\"group of individuals\\\"\"},{\"short_name\":\"Entity Relationship to the AI\",\"value_json\":\"[\\\"affected non-users\\\"]\"},{\"short_name\":\"Harm Category Experienced\",\"value_json\":\"\\\"AI special interest intangible harm\\\"\"},{\"short_name\":\"Harm Type Experienced\",\"value_json\":\"\\\"disproportionate treatment based upon a protected characteristic\\\"\"}]},{\"attributes\":[{\"short_name\":\"Entity\",\"value_json\":\"\\\"Google\\\"\"},{\"short_name\":\"Named Entity\",\"value_json\":\"true\"},{\"short_name\":\"Entity type\",\"value_json\":\"\\\"for-profit organization\\\"\"},{\"short_name\":\"Entity Relationship to the AI\",\"value_json\":\"[\\\"developer\\\",\\\"deployer\\\"]\"},{\"short_name\":\"Harm Category Experienced\",\"value_json\":\"\\\"not applicable\\\"\"},{\"short_name\":\"Harm Type Experienced\",\"value_json\":\"\\\"not applicable\\\"\"}]},{\"attributes\":[{\"short_name\":\"Entity\",\"value_json\":\"\\\"Google Search\\\"\"},{\"short_name\":\"Named Entity\",\"value_json\":\"true\"},{\"short_name\":\"Entity type\",\"value_json\":\"\\\"product\\\"\"},{\"short_name\":\"Entity Relationship to the AI\",\"value_json\":\"[\\\"product containing AI\\\"]\"},{\"short_name\":\"Harm Category Experienced\",\"value_json\":\"\\\"not applicable\\\"\"},{\"short_name\":\"Harm Type Experienced\",\"value_json\":\"\\\"not applicable\\\"\"}]},{\"attributes\":[{\"short_name\":\"Entity\",\"value_json\":\"\\\"Women of color\\\"\"},{\"short_name\":\"Named Entity\",\"value_json\":\"false\"},{\"short_name\":\"Entity type\",\"value_json\":\"\\\"group of individuals\\\"\"},{\"short_name\":\"Entity Relationship to the AI\",\"value_json\":\"[\\\"user\\\",\\\"affected non-user\\\"]\"},{\"short_name\":\"Harm Category Experienced\",\"value_json\":\"\\\"AI special interest intangible harm\\\"\"},{\"short_name\":\"Harm Type Experienced\",\"value_json\":\"\\\"disproportionate treatment based upon a protected characteristic\\\"\"}]}]" + }, + { + "short_name": "Lives Lost", + "value_json": "0" + }, + { + "value_json": "0", + "short_name": "Injuries" + }, + { + "short_name": "Estimated Harm Quantities", + "value_json": "false" + }, + { + "short_name": "Notes ( Tangible Harm Quantities Information)", + "value_json": "\"\"" + }, + { + "short_name": "AI System Description", + "value_json": "\"Search engine and content ranking system. \"" + }, + { + "short_name": "Data Inputs", + "value_json": "[\"images\",\"image alt tags\",\"text\"]" + }, + { + "short_name": "Notes (Information about AI System)", + "value_json": "\"\"" + }, + { + "short_name": "Physical System Type", + "value_json": "\"\"" + }, + { + "short_name": "AI Task", + "value_json": "[\"search engine optimization\"]" + }, + { + "value_json": "\"\"", + "short_name": "Notes (AI Functionality and Techniques)" + } + ], + "incidents": [2], + "namespace": "CSETv1_Annotator-1", + }, + + { + "_id": new ObjectId("6503a32f8361de7a3732d36f"), + "attributes": [ + { + "short_name": "Harm Distribution Basis", + "value_json": "[\"sex\",\"race\"]" + }, + { + "short_name": "Sector of Deployment", + "value_json": "[\"information and communication\"]" + }, + { + "value_json": "\"maybe\"", + "short_name": "Physical Objects" + }, + { + "short_name": "Entertainment Industry", + "value_json": "\"no\"" + }, + { + "value_json": "\"no\"", + "short_name": "Report, Test, or Study of data" + }, + { + "short_name": "Deployed", + "value_json": "\"yes\"" + }, + { + "value_json": "\"no\"", + "short_name": "Producer Test in Controlled Conditions" + }, + { + "short_name": "Producer Test in Operational Conditions", + "value_json": "\"no\"" + }, + { + "short_name": "User Test in Controlled Conditions", + "value_json": "\"no\"" + }, + { + "short_name": "User Test in Operational Conditions", + "value_json": "\"no\"" + }, + { + "short_name": "Harm Domain", + "value_json": "\"yes\"" + }, + { + "short_name": "Tangible Harm", + "value_json": "\"no tangible harm, near-miss, or issue\"" + }, + { + "short_name": "AI System", + "value_json": "\"yes\"" + }, + { + "short_name": "Clear link to technology", + "value_json": "\"yes\"" + }, + { + "short_name": "There is a potentially identifiable specific entity that experienced the harm", + "value_json": "false" + }, + { + "short_name": "AI Harm Level", + "value_json": "\"none\"" + }, + { + "short_name": "Impact on Critical Services", + "value_json": "\"no\"" + }, + { + "short_name": "Rights Violation", + "value_json": "\"no\"" + }, + { + "short_name": "Involving Minor", + "value_json": "\"no\"" + }, + { + "short_name": "Detrimental Content", + "value_json": "\"no\"" + }, + { + "short_name": "Protected Characteristic", + "value_json": "\"yes\"" + }, + { + "value_json": "\"yes\"", + "short_name": "Clear link to Technology" + }, + { + "short_name": "Harmed Class of Entities", + "value_json": "true" + }, + { + "short_name": "Annotator’s AI special interest intangible harm assessment", + "value_json": "\"yes\"" + }, + { + "short_name": "Public Sector Deployment", + "value_json": "\"no\"" + }, + { + "value_json": "\"Autonomy1\"", + "short_name": "Autonomy Level" + }, + { + "short_name": "Intentional Harm", + "value_json": "\"No. Not intentionally designed to perform harm\"" + }, + { + "short_name": "AI tools and methods", + "value_json": "\"\"" + }, + { + "short_name": "Peer Reviewer", + "value_json": "\"002\"" + }, + { + "short_name": "Quality Control", + "value_json": "false" + }, + { + "value_json": "\"4. Peer review complete\"", + "short_name": "Annotation Status" + }, + { + "short_name": "Incident Number", + "value_json": "2" + }, + { + "short_name": "Annotator", + "value_json": "\"001\"" + }, + { + "value_json": "\"\"", + "short_name": "AI Tangible Harm Level Notes" + }, + { + "short_name": "Notes (special interest intangible harm)", + "value_json": "\"Propagation of systemic racial bias by Google search \"" + }, + { + "value_json": "\"yes\"", + "short_name": "Special Interest Intangible Harm" + }, + { + "value_json": "\"\"", + "short_name": "Notes (AI special interest intangible harm)" + }, + { + "short_name": "Date of Incident Year", + "value_json": "\"2012\"" + }, + { + "short_name": "Date of Incident Month", + "value_json": "\"\"" + }, + { + "short_name": "Date of Incident Day", + "value_json": "\"\"" + }, + { + "short_name": "Estimated Date", + "value_json": "true" + }, + { + "value_json": "\"no\"", + "short_name": "Multiple AI Interaction" + }, + { + "short_name": "Embedded", + "value_json": "\"no\"" + }, + { + "short_name": "Location City", + "value_json": "\"\"" + }, + { + "short_name": "Location State/Province (two letters)", + "value_json": "\"\"" + }, + { + "short_name": "Location Country (two letters)", + "value_json": "\"\"" + }, + { + "short_name": "Location Region", + "value_json": "\"Global\"" + }, + { + "short_name": "Infrastructure Sectors", + "value_json": "[]" + }, + { + "short_name": "Operating Conditions", + "value_json": "\"\"" + }, + { + "value_json": "\"\"", + "short_name": "Notes (Environmental and Temporal Characteristics)" + }, + { + "short_name": "Entities", + "value_json": "[{\"attributes\":[{\"short_name\":\"Entity\",\"value_json\":\"\\\"Google\\\"\"},{\"short_name\":\"Named Entity\",\"value_json\":\"true\"},{\"short_name\":\"Entity type\",\"value_json\":\"\\\"for-profit organization\\\"\"},{\"short_name\":\"Entity Relationship to the AI\",\"value_json\":\"[\\\"developer\\\",\\\"deployer\\\"]\"},{\"short_name\":\"Harm Category Experienced\",\"value_json\":\"\\\"not applicable\\\"\"},{\"short_name\":\"Harm Type Experienced\",\"value_json\":\"\\\"not applicable\\\"\"}]},{\"attributes\":[{\"short_name\":\"Entity\",\"value_json\":\"\\\"Google Search\\\"\"},{\"short_name\":\"Named Entity\",\"value_json\":\"true\"},{\"short_name\":\"Entity type\",\"value_json\":\"\\\"product\\\"\"},{\"short_name\":\"Entity Relationship to the AI\",\"value_json\":\"[\\\"AI\\\"]\"},{\"short_name\":\"Harm Category Experienced\",\"value_json\":\"\\\"not applicable\\\"\"},{\"short_name\":\"Harm Type Experienced\",\"value_json\":\"\\\"not applicable\\\"\"}]},{\"attributes\":[{\"short_name\":\"Entity\",\"value_json\":\"\\\"Black people\\\"\"},{\"short_name\":\"Named Entity\",\"value_json\":\"false\"},{\"short_name\":\"Entity type\",\"value_json\":\"\\\"group of individuals\\\"\"},{\"short_name\":\"Entity Relationship to the AI\",\"value_json\":\"[\\\"user\\\",\\\"affected non-user\\\"]\"},{\"short_name\":\"Harm Category Experienced\",\"value_json\":\"\\\"AI special interest intangible harm\\\"\"},{\"short_name\":\"Harm Type Experienced\",\"value_json\":\"\\\"disproportionate treatment based upon a protected characteristic\\\"\"}]},{\"attributes\":[{\"short_name\":\"Entity\",\"value_json\":\"\\\"Women of color\\\"\"},{\"short_name\":\"Named Entity\",\"value_json\":\"false\"},{\"short_name\":\"Entity type\",\"value_json\":\"\\\"group of individuals\\\"\"},{\"short_name\":\"Entity Relationship to the AI\",\"value_json\":\"[\\\"user\\\",\\\"affected non-user\\\"]\"},{\"short_name\":\"Harm Category Experienced\",\"value_json\":\"\\\"AI special interest intangible harm\\\"\"},{\"short_name\":\"Harm Type Experienced\",\"value_json\":\"\\\"disproportionate treatment based upon a protected characteristic\\\"\"}]}]" + }, + { + "value_json": "0", + "short_name": "Lives Lost" + }, + { + "short_name": "Injuries", + "value_json": "0" + }, + { + "short_name": "Estimated Harm Quantities", + "value_json": "false" + }, + { + "short_name": "Notes ( Tangible Harm Quantities Information)", + "value_json": "\"\"" + }, + { + "short_name": "AI System Description", + "value_json": "\"Search engine\"" + }, + { + "value_json": "[\"Text\"]", + "short_name": "Data Inputs" + }, + { + "value_json": "\"\"", + "short_name": "Notes (Information about AI System)" + }, + { + "short_name": "Physical System Type", + "value_json": "\"\"" + }, + { + "value_json": "[\"search engine\"]", + "short_name": "AI Task" + }, + { + "short_name": "Notes (AI Functionality and Techniques)", + "value_json": "\"\"" + } + ], + "incidents": [2], + "namespace": "CSETv1_Annotator-2", + "notes": "", + "publish": true, + "reports": [], + } + +] + +export default items; \ No newline at end of file diff --git a/site/gatsby-site/playwright/seeds/aiidprod/taxa.ts b/site/gatsby-site/playwright/seeds/aiidprod/taxa.ts new file mode 100644 index 0000000000..20a412087b --- /dev/null +++ b/site/gatsby-site/playwright/seeds/aiidprod/taxa.ts @@ -0,0 +1,4978 @@ +import { ObjectId } from 'bson' +import { Taxa } from '../../../server/generated/graphql' + +type DBTaxa = Taxa; + + +const items: DBTaxa[] = [ + { + _id: new ObjectId("64c27aef6bd9217f29557f14"), + namespace: "CSETv1", + weight: 70, + complete_entities: true, + description: "# What is the CSET Taxonomy?\n\nThe CSET AI Harm Taxonomy for AIID is the second edition of the \nCSET incident taxonomy.", + dummy_fields: [ + { + field_number: "1", + short_name: "Metadata" + }, + { + field_number: "2", + short_name: "Incident Domain" + }, + { + field_number: "3", + short_name: "AI Tangible Harm Assessment" + }, + { + field_number: "4", + short_name: "Special Interest Intangible Harm" + }, + { + field_number: "5", + short_name: "AI Special Interest Intangible Harm Assessment" + }, + { + field_number: "6", + short_name: "Environmental and Temporal Characteristics" + }, + { + field_number: "7", + short_name: "Characterizing Entities and the Harm" + }, + { + field_number: "8", + short_name: "Tangible Harm Quantities " + }, + { + field_number: "9", + short_name: "Information about AI System" + }, + { + field_number: "10", + short_name: "AI Functionality and Techniques" + } + ], + field_list: [ + { + field_number: "1.1", + short_name: "Incident Number", + long_name: "The number of the incident in the AI Incident Database.", + short_description: "The number of the incident in the AI Incident Database.", + long_description: "The number of the incident in the AI Incident Database.", + display_type: "int", + mongo_type: "int", + default: "", + weight: 5, + instant_facet: false, + required: false, + public: true, + hide_search: true + }, + { + field_number: "1.2", + short_name: "Annotator", + long_name: "Person responsible for the annotations", + short_description: "This is the researcher that is responsible for applying the classifications of the CSET taxonomy.", + long_description: "An ID designating the individual who classified this incident according to the CSET taxonomy.", + display_type: "string", + mongo_type: "string", + default: "", + placeholder: "Select name here", + permitted_values: [], + weight: 5, + instant_facet: false, + required: false, + public: false, + hide_search: true + }, + { + field_number: "1.3", + short_name: "Annotation Status", + long_name: "Where in the annotation process is this incident?", + short_description: "What is the quality assurance status of the CSET classifications for this incident?", + long_description: "What is the quality assurance status of the CSET classifications for this incident?", + display_type: "enum", + mongo_type: "string", + default: "", + placeholder: "Select process status here", + permitted_values: [ + "1. Annotation in progress", + "2. Initial annotation complete", + "3. In peer review", + "4. Peer review complete", + "5. In quality control", + "6. Complete and final" + ], + weight: 10, + instant_facet: false, + required: false, + public: false, + hide_search: true, + notes: "When you start a row, switch this field from blank to “Annotation in progress” so other annotators know not to work on the same row (and can be sure it wasn’t skipped or left unfinished accidentally).\n\nOnce a row is marked “Initial annotation complete,” we will assume that any remaining blanks were left deliberately - that is, you looked, but couldn’t find enough information to fill out the blank fields. For this reason, please don’t mark a row “Initial annotation complete” until you’ve truly finished filling it out.\n\nWhen peer review begins, the assigned reviewer should switch the status to “In peer review.” When the review is complete and all comments have been resolved, either the peer reviewer or the original annotator should switch the status to “Peer review complete.”\n\nOptions 5 and 6 should only ever be selected by the project lead" + }, + { + field_number: "1.4", + short_name: "Peer Reviewer", + long_name: "Person responsible for reviewing annotations", + short_description: "This is the researcher that is responsible for ensuring the quality of the classifications applied to this incident.", + long_description: "The CSET taxonomy assigns individual researchers to each incident as the primary parties responsible for classifying the incident according to the taxonomy. This is the person responsible for assuring the integrity of annotator's classifications.", + display_type: "string", + mongo_type: "string", + default: "", + placeholder: "Select name here", + permitted_values: [], + weight: 20, + instant_facet: false, + required: false, + public: false, + hide_search: true, + notes: "The project lead will assign a peer reviewer to each incident." + }, + { + field_number: "1.5", + short_name: "Quality Control", + long_name: "Was this incident randomly selected for additional quality control?", + short_description: "Has someone flagged a potential issue with this incident's classifications? Annotators should leave this field blank.", + long_description: "The peer review process sometimes uncovers issues with the classifications that have been applied by the annotator. This field serves as a flag when there is a need for additional thought and input on the classifications applied", + display_type: "bool", + mongo_type: "bool", + default: "false", + placeholder: "", + permitted_values: [], + weight: 15, + instant_facet: false, + required: false, + public: false, + hide_search: true + }, + { + field_number: "2.1", + short_name: "Physical Objects", + long_name: "Did the incident occur in a domain with physical objects ?", + short_description: "Did the incident occur in a domain with physical objects ?", + long_description: "“Yes” if the AI system(s) is embedded in hardware that can interact with, affect, and change the physical objects (cars, robots, medical facilities, etc.). Mark “No” if the system cannot. This includes systems that inform, detect, predict, or recommend.", + display_type: "enum", + mongo_type: "string", + default: "maybe", + permitted_values: [ + "yes", + "no", + "maybe" + ], + placeholder: "", + weight: 40, + instant_facet: false, + required: false, + notes: "Context matters. AI systems embedded in hardware that can physically interact are more likely to cause death, injury, or damage." + }, + { + field_number: "2.2", + short_name: "Entertainment Industry", + long_name: "Did the AI incident occur in the entertainment industry?", + short_description: "Did the AI incident occur in the entertainment industry?", + long_description: "“Yes” if the sector in which the AI was used is associated with entertainment. “No” if it was used in a different, clearly identifiable sector. “Maybe” if the sector of use could not be determined.", + display_type: "enum", + mongo_type: "string", + default: "maybe", + permitted_values: [ + "yes", + "no", + "maybe" + ], + placeholder: "", + weight: 40, + instant_facet: false, + required: false, + notes: "Context matters. AI systems used for entertainment are less likely to result in harm. For example a deepfake used in a movie is less likely to cause harm than a deepfake used for political misinformation." + }, + { + field_number: "2.3", + short_name: "Report, Test, or Study of data", + long_name: "Was the incident about a report, test, or study of data instead of the AI itself?", + short_description: "Was the incident about a report, test, or study of data instead of the AI itself?", + long_description: "“Yes” if the incident is about a report, test, or study of the data and does not discuss an instance of injury, damage, or loss. “Maybe” if it is unclear. Otherwise mark “No.”", + display_type: "enum", + mongo_type: "string", + default: "maybe", + permitted_values: [ + "yes", + "no", + "maybe" + ], + placeholder: "", + weight: 40, + instant_facet: false, + required: false, + notes: "Sometimes there are reports about issues with the data that could be used to develop AI systems. Since there are mitigation approaches, data issues do not automatically mean that the associated AI will have issues that lead to harm. A projection or hypothesis of the harm resulting from data issues is not sufficient. There must be harm that can be clearly linked to an AI." + }, + { + field_number: "2.4", + short_name: "Deployed", + long_name: "Was the reported system (even if AI involvement is unknown) deployed or sold to users?", + short_description: "Was the reported system (even if AI involvement is unknown) deployed or sold to users?", + long_description: "“Yes” if the involved system was deployed or sold to users. “No” if it was not. “Maybe” if there is not enough information or if the use is unclear.", + display_type: "enum", + mongo_type: "string", + default: "maybe", + permitted_values: [ + "yes", + "no", + "maybe" + ], + placeholder: "", + weight: 40, + instant_facet: false, + required: false, + notes: "Systems that are not deployed or sold to users tend to still be in the development stage and hence are less likely to cause harm. However, harm can still be possible. " + }, + { + field_number: "2.5", + short_name: "Producer Test in Controlled Conditions", + long_name: "Was this a test or demonstration of an AI system done by developers, producers or researchers (versus users) in controlled conditions?", + short_description: "Was this a test or demonstration of an AI system done by developers, producers or researchers (versus users) in controlled conditions?", + long_description: "“Yes” if it was a test/demonstration performed by developers, producers or journalists in controlled conditions. “No” if it was not a test/demonstration. “No” if the test/demonstration was done by a user. “No” if the test/demonstration was in operational or uncontrolled conditions. “Maybe” otherwise.", + display_type: "enum", + mongo_type: "string", + default: "maybe", + permitted_values: [ + "yes", + "no", + "maybe" + ], + placeholder: "", + weight: 40, + instant_facet: false, + required: false, + notes: "AI system tests or demonstrations by developers, producers, or researchers in controlled environments are less likely to expose people, organizations, property, institutions, or the natural environment to harm. Controlled environments may include situations such as an isolated compute system, a regulatory sandbox, or an autonomous vehicle testing range. " + }, + { + field_number: "2.6", + short_name: "Producer Test in Operational Conditions", + long_name: "Was this a test or demonstration of an AI system done by developers, producers or researchers (versus users) in operational conditions?", + short_description: "Was this a test or demonstration of an AI system done by developers, producers or researchers (versus users) in operational conditions?", + long_description: "“Yes” if it was a test/demonstration performed by developers, producers or journalists in controlled conditions. “No” if it was not a test/demonstration. “No” if the test/demonstration was done by a user. “No” if the test/demonstration was in controlled or non-operational conditions. “Maybe” otherwise.", + display_type: "enum", + mongo_type: "string", + default: "maybe", + permitted_values: [ + "yes", + "no", + "maybe" + ], + placeholder: "", + weight: 40, + instant_facet: false, + required: false, + notes: "While almost every AI system undergoes testing or demonstration in a controlled environment, some also undergo testing or demonstration in an operational environment. Testing in operational environments still occurs before the system is deployed or sold to end-users. However, relative to controlled environments, operational environments try to closely represent real-world conditions and end-users that affect use of the AI system. Therefore, testing in an operational environment typically poses a heightened risk of harm to people, organizations, property, institutions, or the environment." + }, + { + field_number: "2.7", + short_name: "User Test in Controlled Conditions", + long_name: "Was this a test or demonstration done by users in controlled conditions?", + short_description: "Was this a test or demonstration done by users in controlled conditions?", + long_description: "“Yes” if it was a test/demonstration performed by users in controlled conditions. “No” if it was not a test/demonstration. “No” if the test/demonstration was done by developers, producers or researchers. “No” if the test/demonstration was in controlled or non-controlled conditions.“Maybe” otherwise.", + display_type: "enum", + mongo_type: "string", + default: "maybe", + permitted_values: [ + "yes", + "no", + "maybe" + ], + placeholder: "", + weight: 40, + instant_facet: false, + required: false, + notes: "The involvement of a user (versus a developer, producer, or researcher) increases the likelihood that harm can occur even if the AI system is being tested. Relative to controlled environments, controlled environments try to closely represent real-world conditions and end-users that affect use of the AI system. Therefore, testing in an controlled environment typically poses a heightened risk of harm to people, organizations, property, institutions, or the environment." + }, + { + field_number: "2.8", + short_name: "User Test in Operational Conditions", + long_name: "Was this a test or demonstration done by users in operational conditions?", + short_description: "Was this a test or demonstration done by users in operational conditions?", + long_description: "“Yes” if it was a test/demonstration performed by users in operational conditions. “No” if it was not a test/demonstration. “No” if the test/demonstration was done by developers, producers or researchers. “No” if the test/demonstration was in controlled or non-operational conditions.“Maybe” otherwise.", + display_type: "enum", + mongo_type: "string", + default: "maybe", + permitted_values: [ + "yes", + "no", + "maybe" + ], + placeholder: "", + weight: 40, + instant_facet: false, + required: false, + notes: "Sometimes, prior to deployment, the user will perform a test or demonstration of the AI system. The involvement of a user (versus a developer, producer, or researcher) increases the likelihood that harm can occur even if the AI system is being tested in controlled environments." + }, + { + field_number: "2.9", + short_name: "Harm Domain", + long_name: "Incident occurred in a domain where we could expect harm to occur?", + short_description: "Incident occurred in a domain where we could likely expect harm to occur?", + long_description: "Using the answers to the 8 domain questions, assess if the incident occurred in a domain where harm could be expected to occur. If you are unclear, input “maybe.”", + display_type: "enum", + mongo_type: "string", + default: "maybe", + permitted_values: [ + "yes", + "no", + "maybe" + ], + placeholder: "", + weight: 40, + instant_facet: false, + required: false, + hide_search: true, + notes: "Reflecting upon the previously answered questions, decide if the reported incident or instance occurred in a domain in which harm could possibly occur. This is not a decision on whether or not harm did occur. Just a reflection on the operating conditions or context of the system." + }, + { + field_number: "3.1", + short_name: "Tangible Harm", + long_name: "Did tangible harm (loss, damage or injury ) occur? ", + short_description: "Did tangible harm (loss, damage or injury ) occur? ", + long_description: "An assessment of whether tangible harm, imminent tangible harm, or non-imminent tangible harm occurred. This assessment does not consider the context of the tangible harm, if an AI was involved, or if there is an identifiable, specific, and harmed entity. It is also not assessing if an intangible harm occurred. It is only asking if tangible harm occurred and what its imminency was.", + display_type: "enum", + mongo_type: "string", + default: "unclear", + placeholder: "", + permitted_values: [ + "tangible harm definitively occurred", + "imminent risk of tangible harm (near miss) did occur", + "non-imminent risk of tangible harm (an issue) occurred", + "no tangible harm, near-miss, or issue", + "unclear" + ], + weight: 40, + instant_facet: false, + required: false, + public: true + }, + { + field_number: "3.2", + short_name: "AI System", + long_name: "Does the incident involve an AI system?", + short_description: "Does the incident involve an AI system?", + long_description: "An assessment of whether or not an AI system was involved. It is sometimes difficult to judge between an AI and an automated system or expert rules system. In these cases select “maybe”", + display_type: "enum", + mongo_type: "string", + default: "maybe", + permitted_values: [ + "yes", + "no", + "maybe" + ], + placeholder: "", + weight: 40, + instant_facet: false, + required: false, + public: true, + notes: "Note, over time more information about the incident may become available, allowing a ‘maybe’ to be changed to a ‘yes’ or ‘no.’" + }, + { + field_number: "3.3", + short_name: "Clear link to technology", + long_name: "Can the technology be directly and clearly linked to the adverse outcome of the incident", + short_description: "Can the technology be directly and clearly linked to the adverse outcome of the incident", + long_description: "An assessment of the technology's involvement in the chain of harm. \"Yes\" indicates that the technology was involved in harm, its behavior can be directly linked to the harm, and the harm may not have occurred if the technology acted differently. \"No\", indicates that the technology's behavior cannot be linked to the harm outcome. \"Maybe\" indicates that the link is unclear.", + hide_search: true, + display_type: "enum", + mongo_type: "string", + default: "maybe", + permitted_values: [ + "yes", + "no", + "maybe" + ], + placeholder: "", + weight: 40, + instant_facet: false, + required: false, + public: true + }, + { + field_number: "3.4", + short_name: "There is a potentially identifiable specific entity that experienced the harm", + long_name: "There is a potentially identifiable specific entity that experienced the harm", + short_description: "A potentially identifiable specific entity that experienced the harm can be characterized or identified.", + long_description: "“Yes” if it is theoretically possible to both specify and identify the entity. Having that information is not required. The information just needs to exist and be potentially discoverable. “No” if there are not any potentially identifiable specific entities or if the harmed entities are a class or subgroup that can only be characterized. ", + display_type: "bool", + mongo_type: "bool", + permitted_values: [], + placeholder: "", + default: "", + weight: 40, + instant_facet: false, + required: false, + public: true, + hide_search: true, + notes: "A potentially identifiable specific entity is an entity that can be described in detail such that the name (Mr. Joe Smith, Acme Inc, etc.) or a unique identifier (e.g. 100 Main Street, Anywhere USA) of the entity could be found. We may not know the name or identifier of the entity from the reports, but it does exist and could be found. For example, the general public is not a potentially identifiable specific entity. However, incarcerated people in the Springfield penitentiary would be specific entities because it would be possible to get a list of all the prisoners in the facility." + }, + { + field_number: "3.5", + short_name: "AI Harm Level", + long_name: "Annotator's AI tangible harm level assessment", + short_description: "An assessment of the AI tangible harm level, which takes into account the CSET definitions of AI tangible harm levels, along with the inputs for annotation fields about the AI, harm, chain of harm, and entity. ", + long_description: "An assessment of the AI tangible harm level, which takes into account the CSET definitions of AI tangible harm levels, along with the inputs for annotation fields about the AI, harm, chain of harm, and entity.", + display_type: "enum", + mongo_type: "string", + default: "unclear", + placeholder: "", + permitted_values: [ + "AI tangible harm event", + "AI tangible harm near-miss", + "AI tangible harm issue", + "none", + "unclear" + ], + weight: 40, + instant_facet: false, + required: false, + public: true, + notes: "Special interest intangible harm is determined in a different field. The determination of a special interest intangible harm is not dependant upon the AI tangible harm level" + }, + { + field_number: "3.6", + short_name: "AI Tangible Harm Level Notes", + long_name: "AI Tangible Harm Level Notes", + short_description: "Notes about the AI tangible harm level assessment", + long_description: "If for 3.5 you select unclear or leave it blank, please provide a brief description of why.\n\n You can also add notes if you want to provide justification for a level", + display_type: "long_string", + mongo_type: "string", + default: "", + placeholder: "Notes about the AI tangible harm level assessment", + permitted_values: [], + weight: 5, + instant_facet: false, + required: false, + public: true, + hide_search: true + }, + { + field_number: "4.1", + short_name: "Impact on Critical Services", + long_name: "Did this impact people's access to critical or public services (health care, social services, voting, transportation, etc)?", + short_description: "Indicates if people’s access to critical public services was impacted.", + long_description: "Did this impact people's access to critical or public services (health care, social services, voting, transportation, etc)?", + display_type: "enum", + mongo_type: "string", + default: "maybe", + permitted_values: [ + "yes", + "no", + "maybe" + ], + placeholder: "", + weight: 40, + instant_facet: false, + required: false, + public: true, + notes: "Public services include healthcare, social services, voting, public transportation, education, and consumer protection.\n\nNote, if ‘yes’ is selected then there was likely a violation of civil liberties and there was a special interest intangible harm." + }, + { + field_number: "4.2", + short_name: "Rights Violation", + long_name: "Was this a violation of human rights, civil liberties, civil rights, or democratic norms?", + short_description: "Indicate if a violation of human rights, civil rights, civil liberties, or democratic norms occurred.", + long_description: "Indicate if a violation of human rights, civil rights, civil liberties, or democratic norms occurred.", + display_type: "enum", + mongo_type: "string", + default: "maybe", + permitted_values: [ + "yes", + "no", + "maybe" + ], + placeholder: "", + weight: 40, + instant_facet: false, + required: false, + public: true, + notes: "It can often be difficult for the typical annotator to differentiate between violations of civil liberties, civil rights, human rights, and democratic norms. For this reason CSET grouped them together.\n\nHuman rights are rights inherent to all human beings, regardless of race, sex, nationality, ethnicity, language, religion, or any other status. They include the right to life and liberty, freedom from slavery and torture, freedom of opinion and expression, and the right to work and education. Civil rights are legal provisions that originate from notions of equality and can be enforced by law. Civil liberties are personal freedoms that are referenced in the Bill of Rights. Democratic norms are traditions, customs, and best practices that support democracy. An example of a democratic norm is accepting election results and facilitating a peaceful transfer of political power." + }, + { + field_number: "4.3", + short_name: "Involving Minor", + long_name: "Was a minor involved in the incident (disproportionally treated or specifically targeted/affected)", + short_description: "Was a minor involved in the incident (disproportionally treated or specifically targeted/affected)", + long_description: "Indicate if a minor was disproportionately targeted or affected", + display_type: "enum", + mongo_type: "string", + default: "maybe", + permitted_values: [ + "yes", + "no", + "maybe" + ], + placeholder: "", + weight: 40, + instant_facet: false, + required: false, + public: true, + notes: "Generally, governments have an interest in establishing heightened protections for minors. These protections are often associated with media content or privacy. For example, if an AI system illegally tracked a minor’s activity online, then answer “yes” to this question. There are instances where an AI system causes indiscriminate harm to a group of people, and it is plausible that some of those people are minors. However, in this case the entire group of people, adults and children alike, shared the distribution of harm equally and therefore the answer to this question would be “no.”" + }, + { + field_number: "4.4", + short_name: "Detrimental Content", + long_name: "Was detrimental content (misinformation, hate speech) involved?", + short_description: "Was detrimental content (misinformation, hate speech) involved?", + long_description: "Detrimental content can include deepfakes, identity misrepresentation, insults, threats of violence, eating disorder or self harm promotion, extremist content, misinformation, sexual abuse material, and scam emails. Detrimental content in itself is often not harmful, however, it can lead to or instigate injury, damage, or loss.", + display_type: "enum", + mongo_type: "string", + default: "maybe", + permitted_values: [ + "yes", + "no", + "maybe" + ], + placeholder: "", + weight: 40, + instant_facet: false, + required: false, + public: true + }, + { + field_number: "4.5", + short_name: "Protected Characteristic", + long_name: "Was a group of people or an individual treated differently based upon a protected characteristic?", + short_description: "Was a group of people treated differently based upon a protected characteristic (e.g. race, ethnicity, creed, immigrant status, color, religion, sex, national origin, age, disability, genetic information)?", + long_description: "Protected characteristics include religion, commercial facilities, geography, age, sex, sexual orientation or gender identity, familial status (e.g., having or not having children) or pregnancy, disability, veteran status, genetic information, financial means, race or creed, Ideology, nation of origin, citizenship, and immigrant status.\n\nAt the federal level in the US, age is a protected characteristic for people over the age of 40. Minors are not considered a protected class. For this reason the CSET annotation taxonomy has a separate field to note if a minor was involved.\n\nOnly mark yes if there is clear evidence discrimination occurred. If there are conflicting accounts, mark unsure. Do not mark that discrimination occurred based on expectation alone.", + display_type: "enum", + mongo_type: "string", + default: "maybe", + permitted_values: [ + "yes", + "no", + "maybe" + ], + placeholder: "", + weight: 40, + instant_facet: false, + required: false, + public: true + }, + { + field_number: "4.6", + short_name: "Harm Distribution Basis", + long_name: "If harms were potentially unevenly distributed among people, on what basis?", + short_description: "Indicates how the harms were potentially distributed.", + long_description: "Multiple can occur.\n\nGenetic information refers to information about a person’s genetic tests or the genetic tests of their relatives. Genetic information can predict the manifestation of a disease or disorder.", + display_type: "multi", + mongo_type: "string", + default: "unclear", + placeholder: "", + permitted_values: [ + "none", + "age", + "disability", + "familial status (e.g., having or not having children) or pregnancy", + "financial means", + "genetic information", + "geography", + "ideology", + "nation of origin, citizenship, immigrant status", + "race", + "religion", + "sex", + "sexual orientation or gender identity", + "veteran status", + "unclear", + "other" + ], + weight: 60, + instant_facet: false, + required: false, + public: true + }, + { + field_number: "4.7", + short_name: "Notes (special interest intangible harm)", + long_name: "Input any notes that may help explain your answers.", + short_description: "Input any notes that may help explain your answers.", + long_description: "Input any notes that may help explain your answers.", + display_type: "long_string", + mongo_type: "string", + default: "", + placeholder: "Notes", + permitted_values: [], + weight: 5, + instant_facet: false, + required: false, + public: true, + hide_search: true + }, + { + field_number: "5.1", + short_name: "Special Interest Intangible Harm", + long_name: "Was there a special interest intangible harm or risk of harm?", + short_description: "An assessment of whether a special interest intangible harm occurred. This assessment does not consider the context of the intangible harm, if an AI was involved, or if there is characterizable class or subgroup of harmed entities. It is also not assessing if an intangible harm occurred. It is only asking if a special interest intangible harm occurred.", + long_description: "An assessment of whether a special interest intangible harm occurred. This assessment does not consider the context of the intangible harm, if an AI was involved, or if there is characterizable class or subgroup of harmed entities. It is also not assessing if an intangible harm occurred. It is only asking if a special interest intangible harm occurred.", + display_type: "enum", + mongo_type: "string", + default: "maybe", + permitted_values: [ + "yes", + "no", + "maybe" + ], + placeholder: "", + weight: 5, + instant_facet: false, + required: false, + public: true + }, + { + field_number: "5.2", + short_name: "AI System", + long_name: "Does the incident involve an AI system?", + short_description: "Does the incident involve an AI system?", + long_description: "An assessment of whether or not an AI system was involved. It is sometimes difficult to judge between an AI and an automated system or expert rules system. In these cases select “maybe”", + display_type: "enum", + mongo_type: "string", + default: "maybe", + permitted_values: [ + "yes", + "no", + "maybe" + ], + placeholder: "", + weight: 40, + instant_facet: false, + required: false, + public: true, + hide_search: true, + notes: "Note, over time more information about the incident may become available, allowing a ‘maybe’ to be changed to a ‘yes’ or ‘no.’" + }, + { + field_number: "5.3", + short_name: "Clear link to Technology", + long_name: "Can the technology be directly and clearly linked to the adverse outcome of the incident?", + short_description: "Can the technology be directly and clearly linked to the adverse outcome of the incident?", + long_description: "An assessment of the technology's involvement in the chain of harm. \"Yes\" indicates that the technology was involved in harm, its behavior can be directly linked to the harm, and the harm may not have occurred if the technology acted differently. \"No\", indicates that the technology's behavior cannot be linked to the harm outcome. \"Maybe\" indicates that the link is unclear.", + display_type: "enum", + mongo_type: "string", + default: "maybe", + permitted_values: [ + "yes", + "no", + "maybe" + ], + placeholder: "", + weight: 40, + instant_facet: false, + required: false, + public: true + }, + { + field_number: "5.4", + short_name: "Harmed Class of Entities", + long_name: "There is a characterizable class or subgroup of entities that experienced the harm", + short_description: "“Yes” if the harmed entity or entities can be characterized. “No” if there are not any characterizable entities.", + long_description: "A characterizable class or subgroup are descriptions of different populations of people. Often they are characteristics by which people qualify for special protection by a law, policy, or similar authority.\n\n Sometimes, groups may be characterized by their exposure to the incident via geographical proximity (e.g., ‘visitors to the park’) or participation in an activity (e.g.,‘Twitter users’).", + display_type: "bool", + mongo_type: "bool", + permitted_values: [], + placeholder: "", + weight: 40, + instant_facet: false, + required: false, + public: true, + hide_search: true, + notes: "" + }, + { + field_number: "5.5", + short_name: "Annotator’s AI special interest intangible harm assessment", + long_name: "The annotator’s assessment of if an AI special interest intangible harm occurred.", + short_description: "The annotator’s assessment of if an AI special interest intangible harm occurred.", + long_description: "AI tangible harm is determined in a different field. The determination of a special interest intangible harm is not dependant upon the AI tangible harm level.", + display_type: "enum", + mongo_type: "string", + default: "maybe", + permitted_values: [ + "yes", + "no", + "maybe" + ], + placeholder: "", + weight: 40, + instant_facet: false, + required: false, + public: true, + notes: "" + }, + { + field_number: "5.6", + short_name: "Notes (AI special interest intangible harm)", + long_name: "If for 5.5 you select unclear or leave it blank, please provide a brief description of why.\n\nYou can also add notes if you want to provide justification for a level.", + short_description: "If for 5.5 you select unclear or leave it blank, please provide a brief description of why.\n\nYou can also add notes if you want to provide justification for a level.", + long_description: "If for 5.5 you select unclear or leave it blank, please provide a brief description of why.\n\nYou can also add notes if you want to provide justification for a level.", + display_type: "long_string", + mongo_type: "string", + default: "", + placeholder: "Notes", + permitted_values: [], + weight: 5, + instant_facet: false, + required: false, + public: true, + hide_search: true + }, + { + field_number: "6.1", + short_name: "Date of Incident Year", + long_name: "The year in which the incident first occurred.", + short_description: "The year in which the incident occurred. If there are multiple harms or occurrences of the incident, list the earliest. If a precise date is unavailable, but the available sources provide a basis for estimating the year, estimate. Otherwise, leave blank.\n\nEnter in the format of YYYY", + long_description: "The year in which the incident occurred. If there are multiple harms or occurrences of the incident, list the earliest. If a precise date is unavailable, but the available sources provide a basis for estimating the year, estimate. Otherwise, leave blank.", + display_type: "string", + mongo_type: "string", + default: "", + placeholder: "YYYY", + permitted_values: [], + weight: 5, + instant_facet: false, + required: false, + public: true + }, + { + field_number: "6.2", + short_name: "Date of Incident Month", + long_name: "The month in which the incident first occurred.", + short_description: "The month in which the incident occurred. If there are multiple harms or occurrences of the incident, list the earliest. If a precise date is unavailable, but the available sources provide a basis for estimating the month, estimate. Otherwise, leave blank.\n\nEnter in the format of MM", + long_description: "The month in which the incident occurred. If there are multiple harms or occurrences of the incident, list the earliest. If a precise date is unavailable, but the available sources provide a basis for estimating the month, estimate. Otherwise, leave blank.", + display_type: "string", + mongo_type: "string", + default: "", + placeholder: "MM", + permitted_values: [], + weight: 5, + instant_facet: false, + required: false, + public: true, + hide_search: true + }, + { + field_number: "6.3", + short_name: "Date of Incident Day", + long_name: "The day on which the first incident occurred.", + short_description: "The day on which the incident occurred. If a precise date is unavailable, leave blank.\n\nEnter in the format of DD", + long_description: "The day on which the incident occurred. If a precise date is unavailable, leave blank.\n\nEnter in the format of DD", + display_type: "string", + mongo_type: "string", + default: "", + placeholder: "DD", + permitted_values: [], + weight: 5, + instant_facet: false, + required: false, + public: true, + hide_search: true + }, + { + field_number: "6.4", + short_name: "Estimated Date", + long_name: "Is the date estimated?", + short_description: "“Yes” if the data was estimated. “No” otherwise.", + long_description: "“Yes” if the data was estimated. “No” otherwise.", + display_type: "bool", + mongo_type: "bool", + permitted_values: [], + placeholder: "", + weight: 5, + instant_facet: false, + required: false, + public: true, + hide_search: true + }, + { + field_number: "6.5", + short_name: "Multiple AI Interaction", + long_name: "Was the AI interacting with another AI?", + short_description: "“Yes” if two or more independently operating AI systems were involved. “No” otherwise.", + long_description: "This happens very rarely but is possible. Examples include two chatbots having a conversation with each other, or two autonomous vehicles in a crash.", + display_type: "enum", + mongo_type: "string", + default: "maybe", + permitted_values: [ + "yes", + "no", + "maybe" + ], + placeholder: "", + weight: 5, + instant_facet: false, + required: false, + public: true + }, + { + field_number: "6.6", + short_name: "Embedded", + long_name: "Is the AI embedded in a physical system or have a physical presence?", + short_description: "“Yes” if the AI is embedded in a physical system. “No” if it is not. “Maybe” if it is unclear.", + long_description: "This question is slightly different from the one in field 2.1.1. That question asks about there being interaction with physical objects–an ability to manipulate or change. A system can be embedded in a physical object and able to interact with the physical environment, e.g. a vacuum robot. A system can be embedded in a physical object and not interact with a physical environment, e.g. a camera system that only records images when the AI detects that dogs are present. AI systems that are accessed through API, web-browser, etc by using a mobile device or computer are not considered to be embedded in hardware systems. They are accessed through hardware.", + display_type: "enum", + mongo_type: "string", + default: "maybe", + permitted_values: [ + "yes", + "no", + "maybe" + ], + placeholder: "", + weight: 5, + instant_facet: false, + required: false, + public: true + }, + { + field_number: "6.7", + short_name: "Location City", + long_name: "If the incident occurred at a specific known location, note the city.", + short_description: "If the incident occurred at a specific known location, note the city.", + long_description: "If the incident occurred at a specific known location, note the city. If there are multiple relevant locations, enter multiple city/state/country values.", + display_type: "string", + mongo_type: "string", + default: "", + placeholder: "City", + permitted_values: [], + weight: 5, + instant_facet: false, + required: false, + public: true, + hide_search: true + }, + { + field_number: "6.8", + short_name: "Location State/Province (two letters)", + long_name: "If the incident occurred at a specific known location, note the state/province.", + short_description: "If the incident occurred at a specific known location, note the state/province.", + long_description: "If the incident occurred at a specific known location, note the state/province. If there are multiple relevant locations, enter multiple city/state/country values.", + display_type: "string", + mongo_type: "string", + default: "", + placeholder: "State or Province", + permitted_values: [], + weight: 5, + instant_facet: false, + required: false, + public: true, + hide_search: true + }, + { + field_number: "6.9", + short_name: "Location Country (two letters)", + long_name: "If the incident occurred at a specific known location, note the country. ", + short_description: "If the incident occurred at a specific known location, note the country. Follow ISO 3166 for the 2-letter country codes.", + long_description: "Follow ISO 3166 for the 2-letter country codes.\n\nIf there are multiple relevant locations, enter multiple city/state/country values.", + display_type: "string", + mongo_type: "string", + default: "", + placeholder: "Country", + permitted_values: [], + weight: 5, + instant_facet: false, + required: false, + public: true + }, + { + field_number: "6.10", + short_name: "Location Region", + long_name: "Location Region", + short_description: "Select the region of the world where the incident occurred. If it occurred in multiple, leave blank.", + long_description: "Use this reference to map countries to regions: https://www.dhs.gov/geographic-regions", + display_type: "enum", + mongo_type: "string", + default: "unclear", + placeholder: "", + permitted_values: [ + "Global", + "Africa", + "Asia", + "Caribbean", + "Central America", + "Europe", + "North America", + "Oceania", + "South America", + "unclear" + ], + weight: 5, + instant_facet: false, + required: false, + public: true + }, + { + field_number: "6.11", + short_name: "Infrastructure Sectors", + long_name: "Which critical infrastructure sectors were affected, if any?", + short_description: "Which critical infrastructure sectors were affected, if any?", + long_description: "Which critical infrastructure sectors were affected, if any?", + display_type: "multi", + mongo_type: "string", + default: "unclear", + placeholder: "", + permitted_values: [ + "chemical", + "commercial facilities", + "communications", + "critical manufacturing", + "dams", + "defense-industrial base", + "emergency services", + "energy", + "financial services", + "food and agriculture", + "government facilities", + "healthcare and public health", + "information technology", + "nuclear ", + "transportation", + "water and wastewater", + "Other", + "unclear" + ], + weight: 5, + instant_facet: false, + required: false, + public: true + }, + { + field_number: "6.12", + short_name: "Operating Conditions", + long_name: "A record of any abnormal or atypical operational conditions that occurred.", + short_description: "A record of any abnormal or atypical operational conditions that occurred.", + long_description: "A record of any abnormal or atypical operational conditions that occurred. This field is most often blank.", + display_type: "list", + mongo_type: "array", + default: "", + placeholder: "e.g. raining; night; low visibility", + permitted_values: [], + weight: 5, + instant_facet: false, + required: false, + public: true + }, + { + field_number: "6.13", + short_name: "Notes (Environmental and Temporal Characteristics)", + long_name: "Notes (Environmental and Temporal Characteristics)", + short_description: "Input any notes that may help explain your answers.", + long_description: "Input any notes that may help explain your answers.", + display_type: "long_string", + mongo_type: "string", + default: "", + placeholder: "Notes", + permitted_values: [], + weight: 5, + instant_facet: false, + required: false, + public: true, + hide_search: true + }, + { + field_number: "7", + short_name: "Entities", + long_name: "Characterizing Entities and the Harm", + short_description: "Characterizing Entities and the Harm", + long_description: "Characterizing Entities and the Harm", + display_type: "object-list", + mongo_type: "array", + hide_search: true, + subfields: [ + { + field_number: "7.1", + short_name: "Entity", + long_name: "A short 1 to 2 word description of the entity.", + short_description: "A short 1 to 2 word description of the entity. When possible use a proper name for the entity, making it a Named Entity.", + long_description: "A short 1 to 2 word description of the entity. When possible use a proper name for the entity, making it a Named Entity.\n\nAnnotate information for each entity involved in the report. Try to capture every entity directly linked to the harm. Think about the entity that experienced the harm, all of the entities between them and the AI, and then all of the entities involved in producing and deploying the AI.\n\nEmployees representing a company in a media or public relations capacity should not be included as an entity.", + display_type: "string", + mongo_type: "string", + complete_from: { + entities: true + }, + default: "", + placeholder: "", + permitted_values: [], + weight: 5, + instant_facet: false, + required: false, + public: true + }, + { + field_number: "7.2", + short_name: "Named Entity", + long_name: "Named Entity Indicator", + short_description: "Indicates if the entity is a Named Entity.", + long_description: "Indicates if the entity is a Named Entity. “Yes” if the entity is a named entity. “No” otherwise.", + display_type: "bool", + mongo_type: "bool", + permitted_values: [], + placeholder: "", + weight: 5, + instant_facet: false, + required: false, + public: true + }, + { + field_number: "7.3", + short_name: "Entity type", + long_name: "Indicates the type of entity", + short_description: "Indicates the type of entity", + long_description: "Indicates the type of entity. If multiple selections could characterize the entity, select the primary function of the entity.", + display_type: "enum", + mongo_type: "string", + default: "unclear", + placeholder: "Entity Type", + permitted_values: [ + "individual", + "group of individuals", + "for-profit organization", + "non-profit organization", + "government entity", + "privately owned space", + "public space", + "infrastructure", + "social or political system", + "product", + "other", + "unclear" + ], + weight: 5, + instant_facet: false, + required: false, + public: true, + hide_search: true + }, + { + field_number: "7.4", + short_name: "Entity Relationship to the AI", + long_name: "Entity Relationship to the AI", + short_description: "Indicates the entity’s relationship to the AI.", + long_description: "Indicates the entity’s relationship to the AI. Note, the smallest possible chain of harm has just two elements; an AI and an entity experiencing harm, near-miss, or issue.", + display_type: "list", + mongo_type: "array", + default: "", + placeholder: "", + permitted_values: [ + "developer", + "deployer", + "government oversight", + "user", + "AI", + "geographic area of use", + "researcher", + "product containing AI", + "watchdog" + ], + weight: 5, + instant_facet: false, + required: false, + public: true + }, + { + field_number: "7.5", + short_name: "Harm Category Experienced", + long_name: "Was an AI special interest intangible harm, tangible harm event, near-miss, or issue experienced by this entity", + short_description: "Was an AI special interest intangible harm, tangible harm event, near-miss, or issue experienced by this entity", + long_description: "Was an AI special interest intangible harm, tangible harm event, near-miss, or issue experienced by this entity. For each recorded entity, indicate the harm category that they experienced. Because recorded entities have a variety of roles in the AI incident, not every recorded entity will experience harm.", + display_type: "enum", + mongo_type: "string", + default: "unclear", + placeholder: "", + permitted_values: [ + "AI special interest intangible harm", + "AI tangible harm event", + "AI tangible harm near-miss", + "AI tangible harm issue", + "Other harm not meeting CSET definitions", + "not applicable", + "unclear" + ], + weight: 5, + instant_facet: false, + required: false, + public: true + }, + { + field_number: "7.6", + short_name: "Harm Type Experienced", + long_name: "Type of harm experienced by entity ", + short_description: "Indicates the type of harm experienced by the harmed entity", + long_description: "Indicates the type of harm experienced by the harmed entity. Only entities experiencing harm should have an assigned type. If the entity did not experience the harm, ‘not applicable’ should be selected.", + display_type: "enum", + mongo_type: "string", + default: "not applicable", + placeholder: "", + permitted_values: [ + "physical health/safety", + "financial loss", + "physical property", + "intangible property", + "infrastructure", + "natural environment", + "social or political systems", + "violation of human rights, civil liberties, civil rights, or democratic norms", + "detrimental content", + "disproportionate treatment based upon a protected characteristic", + "other tangible harm", + "other intangible harm", + "not applicable" + ], + weight: 5, + instant_facet: false, + required: false, + public: true + }, + { + field_number: "7.7", + short_name: "Notes (Characterizing Entities and the Harm)", + long_name: "Notes (Characterizing Entities and the Harm)", + short_description: "Input any notes that may help explain your answers.", + long_description: "Input any notes that may help explain your answers.", + display_type: "long_string", + mongo_type: "string", + default: "", + placeholder: "Notes", + permitted_values: [], + weight: 5, + instant_facet: false, + required: false, + public: true, + hide_search: true + } + ], + default: "[]", + placeholder: "", + permitted_values: [], + weight: 5, + instant_facet: false, + required: false, + public: true + }, + { + field_number: "8.1", + short_name: "Lives Lost", + long_name: "How many human lives were lost?", + short_description: "Indicates the number of deaths reported", + long_description: "This field cannot be greater than zero if the harm is anything besides ‘Physical health/safety.’ ", + display_type: "int", + mongo_type: "int", + default: "", + placeholder: "", + permitted_values: [], + weight: 5, + instant_facet: false, + required: false, + public: true + }, + { + field_number: "8.2", + short_name: "Injuries", + long_name: "How many humans were injured?", + short_description: "Indicate the number of injuries reported.", + long_description: "This field cannot be greater than zero if the harm is anything besides 'Physical health/safety'.\n\nAll reported injuries should count, regardless of their severity level. If a person lost their limb and another person scraped their elbow, both cases would be considered injuries. Do not include the number of deaths in this count.", + display_type: "int", + mongo_type: "int", + default: "", + placeholder: "", + permitted_values: [], + weight: 5, + instant_facet: false, + required: false, + public: true + }, + { + field_number: "8.3", + short_name: "Estimated Harm Quantities", + long_name: "Are any quantities estimated?", + short_description: "Indicates if the amount was estimated.", + long_description: "Indicates if the amount was estimated.", + display_type: "bool", + mongo_type: "bool", + permitted_values: [], + placeholder: "", + weight: 5, + instant_facet: false, + required: false, + hide_search: true, + public: true + }, + { + field_number: "8.4", + short_name: "Notes ( Tangible Harm Quantities Information)", + long_name: "Notes ( Tangible Harm Quantities Information)", + short_description: "Input any notes that may help explain your answers.", + long_description: "Input any notes that may help explain your answers.", + display_type: "long_string", + mongo_type: "string", + default: "", + placeholder: "Notes", + permitted_values: [], + weight: 5, + instant_facet: false, + required: false, + public: true, + hide_search: true + }, + { + field_number: "9.1", + short_name: "AI System Description", + long_name: "Description of the AI system involved", + short_description: "A description of the AI system (when possible)", + long_description: "Describe the AI system in as much detail as the reports will allow.\n\nA high level description of the AI system is sufficient, but if more technical details about the AI system are available, include them in the description as well.", + display_type: "long_string", + mongo_type: "string", + default: "", + placeholder: "Description of the AI system involved", + permitted_values: [], + weight: 5, + instant_facet: false, + required: false, + public: true, + hide_search: true + }, + { + field_number: "9.2", + short_name: "Data Inputs", + long_name: "Description of data inputs to the AI system", + short_description: "A list of the types of data inputs for the AI system.", + long_description: "This is a freeform field that can have any value. There could be multiple entries for this field.\n\nCommon ones include\n\n- still images\n- video\n- text\n- speech\n- Personally Identifiable Information\n- structured data\n- other\n- unclear\n\nStill images are static images. Video images consist of moving images. Text and speech data are considered an important category of unstructured data. They consist of written and spoken words that are not in a tabular format. Personally identifiable information is data that can uniquely identify an individual and may contain sensitive information. Structured data is often in a tabular, machine readable format and can typically be used by an AI system without much preprocessing.\n\nAvoid using ‘unstructured data’ data in this field. Instead specify the type of unstructured data; text, images, audio files, etc. It is ok to use ‘structured data’ in this field.\n\nRecord what the media report explicitly states. If the report does not explicitly state an input modality but it is likely that a particular kind of input contributed to the harm or near harm, record that input. If you are still unsure, do not record anything.", + display_type: "list", + mongo_type: "array", + default: "", + placeholder: "", + permitted_values: [], + weight: 5, + instant_facet: false, + required: false, + public: true + }, + { + field_number: "9.3", + short_name: "Sector of Deployment", + long_name: "Indicates the sector in which the AI system is deployed", + short_description: "Indicate the sector in which the AI system is deployed", + long_description: "Indicate the sector in which the AI system is deployed\n\nThere could be multiple entries for this field.", + display_type: "multi", + mongo_type: "array", + default: "", + placeholder: "", + permitted_values: [ + "agriculture, forestry and fishing", + "mining and quarrying", + "manufacturing", + "electricity, gas, steam and air conditioning supply", + "water supply", + "construction", + "wholesale and retail trade", + "transportation and storage", + "accommodation and food service activities", + "information and communication", + "financial and insurance activities", + "real estate activities", + "professional, scientific and technical activities", + "administrative and support service activities", + "public administration", + "defense", + "law enforcement", + "Education", + "human health and social work activities", + "Arts, entertainment and recreation", + "other service activities", + "activities of households as employers", + "activities of extraterritorial organizations and bodies", + "other", + "unclear" + ], + weight: 60, + instant_facet: false, + required: false, + public: true + }, + { + field_number: "9.4", + short_name: "Public Sector Deployment", + long_name: "Indicates whether the AI system is deployed in the public sector", + short_description: "Indicate whether the AI system is deployed in the public sector", + long_description: "Indicate whether the AI system is deployed in the public sector. The public sector is the part of the economy that is controlled and operated by the government.", + display_type: "enum", + mongo_type: "string", + default: "maybe", + permitted_values: [ + "yes", + "no", + "maybe" + ], + placeholder: "", + weight: 40, + instant_facet: false, + required: false, + public: true + }, + { + field_number: "9.5", + short_name: "Autonomy Level", + long_name: "Autonomy Level", + short_description: "Autonomy1: The system operates independently without simultaneous human oversight, interaction, or intervention.\n\nAutonomy2: The system operates independently but with human oversight, where a human can observe and override the system’s decisions in real time.\n\nAutonomy3: The system does not independently make decisions but instead provides information to a human who actively chooses to proceed with the AI’s information.", + long_description: "Autonomy1: The system operates independently without simultaneous human oversight, interaction, or intervention.\n\nAutonomy2: The system operates independently but with human oversight, where a human can observe and override the system’s decisions in real time.\n\nAutonomy3: The system does not independently make decisions but instead provides information to a human who actively chooses to proceed with the AI’s information.", + display_type: "enum", + mongo_type: "string", + default: "", + placeholder: "", + permitted_values: [ + "Autonomy1", + "Autonomy2", + "Autonomy3", + "unclear" + ], + weight: 40, + instant_facet: false, + required: false, + public: true + }, + { + field_number: "9.8", + short_name: "Notes (Information about AI System)", + long_name: "Notes (Information about AI System)", + short_description: "Input any notes that may help explain your answers.", + long_description: "Input any notes that may help explain your answers.", + display_type: "long_string", + mongo_type: "string", + default: "", + placeholder: "Notes", + permitted_values: [], + weight: 5, + instant_facet: false, + required: false, + public: true, + hide_search: true + }, + { + field_number: "10.1", + short_name: "Intentional Harm", + long_name: "Was the AI intentionally developed or deployed to perform the harm?", + short_description: "Was the AI intentionally developed or deployed to perform the harm?\n\nIf yes, did the AI’s behavior result in unintended or intended harm?", + long_description: "Indicates if the system was designed to do harm. If it was designed to perform harm, the field will indicate if the AI system did or did not create unintended harm–i.e. was the reported harm the harm that AI was expected to perform or a different unexpected harm? ", + display_type: "enum", + mongo_type: "string", + default: "unclear", + placeholder: "", + permitted_values: [ + "Yes. Intentionally designed to perform harm and did create intended harm", + "Yes. Intentionally designed to perform harm but created an unintended harm (a different harm may have occurred)", + "No. Not intentionally designed to perform harm", + "unclear" + ], + weight: 40, + instant_facet: false, + required: false, + public: true, + notes: "Tracking and analyzing harm from AI systems designed to do harm is valuable and worthwhile. However, analysts may want to separately analyze harm from AI systems that were or were not designed to produce the observed harm." + }, + { + field_number: "10.2", + short_name: "Physical System Type", + long_name: "Into what type of physical system was the AI integrated, if any?", + short_description: "Describe the type of physical system that the AI was integrated into.", + long_description: "Describe the type of physical system that the AI was integrated into. ", + display_type: "string", + mongo_type: "string", + default: "", + placeholder: "Physical System Type (e.g. trash sorting robot)", + permitted_values: [], + weight: 5, + instant_facet: false, + required: false, + public: true + }, + { + field_number: "10.3", + short_name: "AI Task", + long_name: "AI task or core application area", + short_description: "Describe the AI’s application.", + long_description: "Describe the AI’s application.\n\nIt is likely that the annotator will not have enough information to complete this field. If this occurs, enter unclear.\n\nThis is a freeform field. Some possible entries are\n\n- unclear\n- human language technologies\n- computer vision\n- robotics\n- automation and/or optimization\n- other\n\nThe application area of an AI is the high level task that the AI is intended to perform. It does not describe the technical methods by which the AI performs the task. Considering what an AI’s technical methods enable it to do is another way of arriving at what an AI’s application is. \n\nIt is possible for multiple application areas to be involved. When possible pick the principle or domain area, but it is ok to select multiple areas.", + display_type: "list", + mongo_type: "array", + default: "", + placeholder: "", + permitted_values: [], + weight: 5, + instant_facet: false, + required: false, + public: true + }, + { + field_number: "10.4", + short_name: "AI tools and methods", + long_name: "AI tools and methods", + short_description: "Describe the tools and methods that enable the AI’s application.", + long_description: "Describe the tools and methods that enable the AI’s application.\n\nIt is likely that the annotator will not have enough information to complete this field. If this occurs, enter unclear\n\nThis is a freeform field. Some possible entries are\n\n- unclear\n- reinforcement learning\n- neural networks\n- decision trees\n- bias mitigation\n- optimization\n- classifier\n- NLP/text analytics\n- continuous learning\n- unsupervised learning\n- supervised learning\n- clustering\n- prediction\n- rules\n- random forest\n\nAI tools and methods are the technical building blocks that enable the AI’s application.", + display_type: "list", + mongo_type: "array", + default: "unclear", + placeholder: "", + permitted_values: [], + weight: 40, + instant_facet: false, + required: false, + public: true + }, + { + field_number: "10.5", + short_name: "Notes (AI Functionality and Techniques)", + long_name: "Notes (AI Functionality and Techniques)", + short_description: "Input any notes that may help explain your answers.", + long_description: "Input any notes that may help explain your answers.", + display_type: "long_string", + mongo_type: "string", + default: "", + placeholder: "Notes", + permitted_values: [], + weight: 5, + instant_facet: false, + required: false, + public: true, + hide_search: true + } + ], + + // TODO: created_at is missing from Atlas schema + // created_at: new Date("1722269934190") + }, + + { + _id: new ObjectId("643f1a5558f256c7dbc4dc67"), + namespace: "GMF", + weight: 70, + description: "## What is the GMF Taxonomy?\n\nThe Goals, Methods, and Failures (GMF) taxonomy is a failure \ncause analysis taxonomy for AI systems in the real world, interrelating the goals of the system \ndeployment, the system's methods, and likely technical causal factors for the observed failure events.\nThe taxonomy structure encourages considering what is known or observed versus what is potential or likely, guiding how to apply and interpret expert technical knowledge about AI failures. Further, the proposed annotation workflow features grounding labels to data and external evidence, enhancing verifiability, collaborative annotation and crowdsourcing. These design decisions render GMF a valuable tool for annotating noisy, low-information documents like public AI incident reports.\n\nDetails on the taxonomy description, proposed annotation process\nand future work are available in our [SafeAI 2023 workshop paper](https://ceur-ws.org/Vol-3381/17.pdf), while\na short description is provided below. \n\n### GMF Motivation and Structure\nThe GMF taxonomy was developed to address the following use cases and questions:\n\n- **Linking harms to system goals**: How can developers and deployers of AI systems discover technical failure causes\nof **harm related to the system task**, that the AI is deployed to perform in the real world?\n\n- **Connecting technical approaches to failure causes**: How can AI developers and auditors discover **technical causal factors of harm**\nthat may be linked to implementation methods, model architectures and techniques\nemployed in their system, such that they may be corrected or avoided?\n\n- **Harness interdisciplinary technical expertise**: How can we **leverage the body of expert technical knowledge** from the Machine\n Learning, AI Safety, Engineering, etc. community, to produce useful, high-quality annotations on **publicly available AI incident reports**, which may lack details and technical information?\n\n- **Data-driven, grounded labelling**: How can we generate annotations **grounded to real-world data** for high-level accuracy,\nverifiability and increased potential for further research and development?\n\n\nThe taxonomy is designed to address these questions via a structure of three interrelated\n ontologies, each describing the AI system involved in a publicly available incident report under a different lens.\n These ontologies include system views focused on:\n\n- **AI System Goals**, which characterize high-level goals, objectives and tasks of AI system deployments in the real world (e.g. `Face Recognition`)\n- **AI Methods and Technologies**, which describe AI implementation methodologies (e.g. `Transformer`)\n- **AI failure causes**, containing technical reasons for systemic failure that results in the observed harm (e.g. `Concept Drift`)\n\nGiven that AI incident reports in the news media often lack technical details, GMF annotations are paired with:\n\n- **Confidence modifiers** (`known` and `potential`), corresponding to the degree of certainty of the annotator for applying a given label to an incident\n- **Text samples** from the incident report relevant to the assigned label, which ground each the classification to supporting text data\n- **Free comments**, where the annotator may provide their rationale, evidence, sources and any information deemed relevant for assigning the label\n\n\n## How do I annotate incidents with GMF?\n\nThe structure of GMF, paired with the AIID interfaces for [incident discovery](https://incidentdatabase.ai/apps/discover) and annotation editing [^1], exposes the user to multiple sources of useful data for efficient and informed incident annotation. \n\nFor example, the user can retrieve similar incidents annotated by the community with respect to\nexisting classifications, e.g. regarding the goal of the AI system. Retrieved incidents expose past annotations and auxiliary metadata, such as exemplar text snippets, annotator rationale and related sourced materials of potential relevance. \n\nThese supplemental data may counteract the lack of AI system implementation details in incident\n reports regarding methods, technologies and technical failure causes, allowing the application of\n fitting labels for the incident at hand.\n\n[^1]: Found in the page for each incident, e.g. [AIID incident #72](https://incidentdatabase.ai/cite/72/)\n\nA visualization of this flow of information for decision making given uncertainty, is illustrated in the proposed GMF annotation process diagram below:\n\n![](/images/gmf/structure_simplified.png)\n\nAdditionally, an indicative application of this annotation process for the real-world [AIID incident #72](https://incidentdatabase.ai/cite/72/) is illustrated below.\n\n![](/images/gmf/annotation.png)\n\n\n## How do I explore the taxonomy?\n\nAll taxonomies can be used to filter incident reports within the \n[Discover Application](https://incidentdatabase.ai/apps/discover). The taxonomy filters work similarly to how \nyou filter products on an e-commerce website. Use the search \nfield at the bottom of the “Classifications” tab to find the \ntaxonomy field you would like to filter with, then click the \ndesired value to apply the filter.\n\n## About the Responsible AI Collaborative\n\nThe AI Incident Database is a collaborative project of many \npeople and organizations. Details on the people and organizations \ncontributing to this particular taxonomy will appear here, while \nyou can learn more about the Collab itself on the incident \ndatabase [home](https://incidentdatabase.ai/) and \n[about](https://incidentdatabase.ai/about/) pages.\n\nThe maintainer(s) of this taxonomy include:\n* [Nikiforos Pittaras](https://www.linkedin.com/in/nikiforos-pittaras/)\n\nContributor(s) to the taxonomy include:\n* [Sean McGregor](https://www.linkedin.com/in/seanbmcgregor/)\n", + dummy_fields: [ + { + field_number: "1", + short_name: "Goals" + }, + { + field_number: "2", + short_name: "Methods" + }, + { + field_number: "3", + short_name: "Failures" + } + ], + field_list: [ + { + field_number: "1.1.1", + short_name: "Known AI Goal", + long_name: "Known AI Goal", + short_description: "An AI Goal which is almost certainly pursued by the AI system referenced in the incident.", + long_description: "An AI Goal which is almost certainly pursued by the AI system referenced in the incident.", + display_type: "list", + mongo_type: "array", + complete_from: { + all: [ + "Known AI Goal", + "Potential AI Goal" + ] + }, + default: "", + placeholder: "", + permitted_values: [], + weight: 50, + instant_facet: true, + required: false, + public: true + }, + { + field_number: "1.1.2", + short_name: "Known AI Goal Snippets", + long_name: "Known AI Goal Snippets", + short_description: "One or more snippets that justify the classification.", + long_description: "One or more snippets that justify the classification.", + default: "", + placeholder: "", + permitted_values: [], + weight: 40, + instant_facet: false, + required: false, + public: true, + display_type: "object-list", + mongo_type: "array", + subfields: [ + { + short_name: "Snippet Text", + long_name: "Snippet Text", + short_description: "Snippet Text", + long_description: "Snippet Text", + display_type: "long_string", + mongo_type: "string", + default: "", + placeholder: "", + permitted_values: [], + weight: 50, + instant_facet: false, + required: false, + public: true + }, + { + short_name: "Related Classifications", + long_name: "Related Classifications", + short_description: "The Known AI Goal Classification classifications from above which this snippet supports", + long_description: "The Known AI Goal Classification classifications from above which this snippet supports", + display_type: "list", + mongo_type: "array", + complete_from: { + current: [ + "Known AI Goal" + ] + }, + default: "", + placeholder: "", + permitted_values: [], + weight: 50, + instant_facet: false, + required: false, + public: true + }, + { + short_name: "Snippet Discussion", + long_name: "Snippet Discussion", + short_description: "Free text discussion on snippet usefulness, elaboration on information / terms included, etc.", + long_description: "Free text discussion on snippet usefulness, elaboration on information / terms included, etc.", + display_type: "long_string", + mongo_type: "string", + default: "", + placeholder: "", + permitted_values: [], + weight: 50, + instant_facet: false, + required: false, + public: true + } + ] + }, + { + field_number: "1.1.3", + short_name: "Known AI Goal Classification Discussion", + long_name: "Known AI Goal Classification Discussion", + short_description: "Free text with comments justifying the chosen classification (e.g. based on information on selected snippets and technical analysis), if needed.", + long_description: "Free text with comments justifying the chosen classification (e.g. based on information on selected snippets and technical analysis), if needed.", + display_type: "long_string", + mongo_type: "string", + default: "", + placeholder: "", + permitted_values: [], + weight: 40, + instant_facet: false, + required: false, + public: true + }, + { + field_number: "1.2.1", + short_name: "Potential AI Goal", + long_name: "Potential AI Goal", + short_description: "An AI Goal which is probably pursued by the AI system referenced in the incident.", + long_description: "An AI Goal which is probably pursued by the AI system referenced in the incident.", + display_type: "list", + mongo_type: "array", + complete_from: { + all: [ + "Known AI Goal", + "Potential AI Goal" + ] + }, + default: "", + placeholder: "", + permitted_values: [], + weight: 50, + instant_facet: true, + required: false, + public: true + }, + { + field_number: "1.2.2", + short_name: "Potential AI Goal Snippets", + long_name: "Potential AI Goal Snippets", + short_description: "One or more snippets that justify the classification.", + long_description: "One or more snippets that justify the classification.", + default: "", + placeholder: "", + permitted_values: [], + weight: 40, + instant_facet: false, + required: false, + public: true, + display_type: "object-list", + mongo_type: "array", + subfields: [ + { + short_name: "Snippet Text", + long_name: "Snippet Text", + short_description: "Snippet Text", + long_description: "Snippet Text", + display_type: "long_string", + mongo_type: "string", + default: "", + placeholder: "", + permitted_values: [], + weight: 50, + instant_facet: false, + required: false, + public: true + }, + { + short_name: "Related Classifications", + long_name: "Related Classifications", + short_description: "The Potential AI Goal classifications from above which this snippet supports", + long_description: "The Potential AI Goal classifications from above which this snippet supports", + display_type: "list", + mongo_type: "array", + complete_from: { + current: [ + "Potential AI Goal" + ] + }, + default: "", + placeholder: "", + permitted_values: [], + weight: 50, + instant_facet: false, + required: false, + public: true + }, + { + short_name: "Snippet Discussion", + long_name: "Snippet Discussion", + short_description: "Free text discussion on snippet usefulness, elaboration on information / terms included, etc.", + long_description: "Free text discussion on snippet usefulness, elaboration on information / terms included, etc.", + display_type: "long_string", + mongo_type: "string", + default: "", + placeholder: "", + permitted_values: [], + weight: 50, + instant_facet: false, + required: false, + public: true + } + ] + }, + { + field_number: "1.2.3", + short_name: "Potential AI Goal Classification Discussion", + long_name: "Potential AI Goal Classification Discussion", + short_description: "Free text with comments justifying the chosen classification (e.g. based on information on selected snippets and technical analysis), if needed.", + long_description: "Free text with comments justifying the chosen classification (e.g. based on information on selected snippets and technical analysis), if needed.", + display_type: "long_string", + mongo_type: "string", + default: "", + placeholder: "", + permitted_values: [], + weight: 40, + instant_facet: false, + required: false, + public: true + }, + { + field_number: "2.1.1", + short_name: "Known AI Technology", + long_name: "Known AI Technology", + short_description: "An AI Technology which is almost certainly a part of the implementation of the AI system referenced in the incident.", + long_description: "An AI Technology which is almost certainly a part of the implementation of the AI system referenced in the incident.", + display_type: "list", + mongo_type: "array", + complete_from: { + all: [ + "Known AI Technology", + "Potential AI Technology" + ] + }, + default: "", + placeholder: "", + permitted_values: [], + weight: 50, + instant_facet: true, + required: false, + public: true + }, + { + field_number: "2.1.2", + short_name: "Known AI Technology Snippets", + long_name: "Known AI Technology Snippets", + short_description: "One or more snippets that justify the classification.", + long_description: "One or more snippets that justify the classification.", + default: "", + placeholder: "", + permitted_values: [], + weight: 40, + instant_facet: false, + required: false, + public: true, + display_type: "object-list", + mongo_type: "array", + subfields: [ + { + short_name: "Snippet Text", + long_name: "Snippet Text", + short_description: "Snippet Text", + long_description: "Snippet Text", + display_type: "long_string", + mongo_type: "string", + default: "", + placeholder: "", + permitted_values: [], + weight: 50, + instant_facet: false, + required: false, + public: true + }, + { + short_name: "Related Classifications", + long_name: "Related Classifications", + short_description: "The Known AI Technology classifications from above which this snippet supports", + long_description: "The Known AI Technology classifications from above which this snippet supports", + display_type: "list", + mongo_type: "array", + complete_from: { + current: [ + "Known AI Technology" + ] + }, + default: "", + placeholder: "", + permitted_values: [], + weight: 50, + instant_facet: false, + required: false, + public: true + }, + { + short_name: "Snippet Discussion", + long_name: "Snippet Discussion", + short_description: "Free text discussion on snippet usefulness, elaboration on information / terms included, etc.", + long_description: "Free text discussion on snippet usefulness, elaboration on information / terms included, etc.", + display_type: "long_string", + mongo_type: "string", + default: "", + placeholder: "", + permitted_values: [], + weight: 50, + instant_facet: false, + required: false, + public: true + } + ] + }, + { + field_number: "2.1.3", + short_name: "Known AI Technology Classification Discussion", + long_name: "Known AI Technology Classification Discussion", + short_description: "Free text with comments justifying the chosen classification (e.g. based on information on selected snippets and technical analysis), if needed.", + long_description: "Free text with comments justifying the chosen classification (e.g. based on information on selected snippets and technical analysis), if needed.", + display_type: "long_string", + mongo_type: "string", + default: "", + placeholder: "", + permitted_values: [], + weight: 40, + instant_facet: false, + required: false, + public: true + }, + { + field_number: "2.2.1", + short_name: "Potential AI Technology", + long_name: "Potential AI Technology", + short_description: "An AI Method / Technology which probably is a part of the implementation of the AI system referenced in the incident.", + long_description: "An AI Method / Technology which probably is a part of the implementation of the AI system referenced in the incident.", + display_type: "list", + mongo_type: "array", + default: "", + placeholder: "", + permitted_values: [], + complete_from: { + all: [ + "Known AI Technology", + "Potential AI Technology" + ] + }, + weight: 50, + instant_facet: true, + required: false, + public: true + }, + { + field_number: "2.2.2", + short_name: "Potential AI Technology Snippets", + long_name: "Potential AI Technology Snippets", + short_description: "One or more snippets that justify the classification.", + long_description: "One or more snippets that justify the classification.", + default: "", + placeholder: "", + permitted_values: [], + weight: 40, + instant_facet: false, + required: false, + public: true, + display_type: "object-list", + mongo_type: "array", + subfields: [ + { + short_name: "Snippet Text", + long_name: "Snippet Text", + short_description: "Snippet Text", + long_description: "Snippet Text", + display_type: "long_string", + mongo_type: "string", + default: "", + placeholder: "", + permitted_values: [], + weight: 50, + instant_facet: false, + required: false, + public: true + }, + { + short_name: "Related Classifications", + long_name: "Related Classifications", + short_description: "The Potential AI Technology classifications from above which this snippet supports", + long_description: "The Potential AI Technology classifications from above which this snippet supports", + display_type: "list", + mongo_type: "array", + complete_from: { + current: [ + "Potential AI Technology" + ] + }, + default: "", + placeholder: "", + permitted_values: [], + weight: 50, + instant_facet: false, + required: false, + public: true + }, + { + short_name: "Snippet Discussion", + long_name: "Snippet Discussion", + short_description: "Free text discussion on snippet usefulness, elaboration on information / terms included, etc.", + long_description: "Free text discussion on snippet usefulness, elaboration on information / terms included, etc.", + display_type: "long_string", + mongo_type: "string", + default: "", + placeholder: "", + permitted_values: [], + weight: 50, + instant_facet: false, + required: false, + public: true + } + ] + }, + { + field_number: "2.2.3", + short_name: "Potential AI Technology Classification Discussion", + long_name: "Potential AI Technology Classification Discussion", + short_description: "Free text with comments justifying the chosen classification (e.g. based on information on selected snippets and technical analysis), if needed.", + long_description: "Free text with comments justifying the chosen classification (e.g. based on information on selected snippets and technical analysis), if needed.", + display_type: "long_string", + mongo_type: "string", + default: "", + placeholder: "", + permitted_values: [], + weight: 40, + instant_facet: false, + required: false, + public: true + }, + { + field_number: "3.1.1", + short_name: "Known AI Technical Failure", + long_name: "Known AI Technical Failure", + short_description: "An AI Technical Failure which almost certainly contributes to the AI system failure referenced in the incident.", + long_description: "An AI Technical Failure which almost certainly contributes to the AI system failure referenced in the incident.", + display_type: "list", + mongo_type: "array", + complete_from: { + all: [ + "Known AI Technical Failure", + "Potential AI Technical Failure" + ] + }, + default: "", + placeholder: "", + permitted_values: [], + weight: 50, + instant_facet: true, + required: false, + public: true + }, + { + field_number: "3.1.2", + short_name: "Known AI Technical Failure Snippets", + long_name: "Snippets", + short_description: "One or more snippets that justify the classification.", + long_description: "One or more snippets that justify the classification.", + default: "", + placeholder: "", + permitted_values: [], + weight: 40, + instant_facet: false, + required: false, + public: true, + display_type: "object-list", + mongo_type: "array", + subfields: [ + { + short_name: "Snippet Text", + long_name: "Snippet Text", + short_description: "Snippet Text", + long_description: "Snippet Text", + display_type: "long_string", + mongo_type: "string", + default: "", + placeholder: "", + permitted_values: [], + weight: 50, + instant_facet: false, + required: false, + public: true + }, + { + short_name: "Related Classifications", + long_name: "Related Classifications", + short_description: "The Known AI Technical Failure classifications from above which this snippet supports", + long_description: "The Known AI Technical Failure classifications from above which this snippet supports", + display_type: "list", + mongo_type: "array", + complete_from: { + current: [ + "Known AI Technical Failure" + ] + }, + default: "", + placeholder: "", + permitted_values: [], + weight: 50, + instant_facet: false, + required: false, + public: true + }, + { + short_name: "Snippet Discussion", + long_name: "Snippet Discussion", + short_description: "Free text discussion on snippet usefulness, elaboration on information / terms included, etc.", + long_description: "Free text discussion on snippet usefulness, elaboration on information / terms included, etc.", + display_type: "long_string", + mongo_type: "string", + default: "", + placeholder: "", + permitted_values: [], + weight: 50, + instant_facet: false, + required: false, + public: true + } + ] + }, + { + field_number: "3.1.3", + short_name: "Known AI Technical Failure Classification Discussion", + long_name: "Known AI Technical Failure Classification Discussion", + short_description: "Free text with comments justifying the chosen classification (e.g. based on information on selected snippets and technical analysis), if needed.", + long_description: "Free text with comments justifying the chosen classification (e.g. based on information on selected snippets and technical analysis), if needed.", + display_type: "long_string", + mongo_type: "string", + default: "", + placeholder: "", + permitted_values: [], + weight: 40, + instant_facet: false, + required: false, + public: true + }, + { + field_number: "3.2.1", + short_name: "Potential AI Technical Failure", + long_name: "Potential AI Technical Failure", + short_description: "An AI Technical Failure which probably contributes to the AI system failure referenced in the incident.", + long_description: "An AI Technical Failure which probably contributes to the AI system failure referenced in the incident.", + display_type: "list", + mongo_type: "array", + complete_from: { + all: [ + "Known AI Technical Failure", + "Potential AI Technical Failure" + ] + }, + default: "", + placeholder: "", + permitted_values: [], + weight: 50, + instant_facet: true, + required: false, + public: true + }, + { + field_number: "3.2.2", + short_name: "Potential AI Technical Failure Snippets", + long_name: "Potential AI Technical Failure Snippets", + short_description: "One or more snippets that justify the classification.", + long_description: "One or more snippets that justify the classification.", + default: "", + placeholder: "", + permitted_values: [], + weight: 40, + instant_facet: false, + required: false, + public: true, + display_type: "object-list", + mongo_type: "array", + subfields: [ + { + short_name: "Snippet Text", + long_name: "Snippet Text", + short_description: "Snippet Text", + long_description: "Snippet Text", + display_type: "long_string", + mongo_type: "string", + default: "", + placeholder: "", + permitted_values: [], + weight: 50, + instant_facet: false, + required: false, + public: true + }, + { + short_name: "Related Classifications", + long_name: "Related Classifications", + short_description: "The Potential AI Technical Failure classifications from above which this snippet supports", + long_description: "The Potential AI Technical Failure classifications from above which this snippet supports", + display_type: "list", + mongo_type: "array", + complete_from: { + current: [ + "Potential AI Technical Failure" + ] + }, + default: "", + placeholder: "", + permitted_values: [], + weight: 50, + instant_facet: false, + required: false, + public: true + }, + { + short_name: "Snippet Discussion", + long_name: "Snippet Discussion", + short_description: "Free text discussion on snippet usefulness, elaboration on information / terms included, etc.", + long_description: "Free text discussion on snippet usefulness, elaboration on information / terms included, etc.", + display_type: "long_string", + mongo_type: "string", + default: "", + placeholder: "", + permitted_values: [], + weight: 50, + instant_facet: false, + required: false, + public: true + } + ] + }, + { + field_number: "3.2.3", + short_name: "Potential AI Technical Failure Classification Discussion", + long_name: "Potential AI Technical Failure Classification Discussion", + short_description: "Free text with comments justifying the chosen classification (e.g. based on information on selected snippets and technical analysis), if needed.", + long_description: "Free text with comments justifying the chosen classification (e.g. based on information on selected snippets and technical analysis), if needed.", + display_type: "long_string", + mongo_type: "string", + default: "", + placeholder: "", + permitted_values: [], + weight: 40, + instant_facet: false, + required: false, + public: true + } + ], + + // TODO: ditto + // created_at: "1722269934342", + }, + + { + _id: new ObjectId("643f1a5558f256c7dbc4dc68"), + namespace: "CSETv1_Annotator-1", + weight: 70, + complete_entities: true, + description: "# What is the CSET Taxonomy?\n\nThe CSET AI Harm Taxonomy for AIID is the second edition of the \nCSET incident taxonomy.", + dummy_fields: [ + { + field_number: "1", + short_name: "Metadata" + }, + { + field_number: "2", + short_name: "Incident Domain" + }, + { + field_number: "3", + short_name: "AI Tangible Harm Assessment" + }, + { + field_number: "4", + short_name: "Special Interest Intangible Harm" + }, + { + field_number: "5", + short_name: "AI Special Interest Intangible Harm Assessment" + }, + { + field_number: "6", + short_name: "Environmental and Temporal Characteristics" + }, + { + field_number: "7", + short_name: "Characterizing Entities and the Harm" + }, + { + field_number: "8", + short_name: "Tangible Harm Quantities " + }, + { + field_number: "9", + short_name: "Information about AI System" + }, + { + field_number: "10", + short_name: "AI Functionality and Techniques" + } + ], + field_list: [ + { + field_number: "1.1", + short_name: "Incident Number", + long_name: "The number of the incident in the AI Incident Database.", + short_description: "The number of the incident in the AI Incident Database.", + long_description: "The number of the incident in the AI Incident Database.", + display_type: "int", + mongo_type: "int", + default: "", + weight: 5, + instant_facet: false, + required: false, + public: true, + hide_search: true + }, + { + field_number: "1.2", + short_name: "Annotator", + long_name: "Person responsible for the annotations", + short_description: "This is the researcher that is responsible for applying the classifications of the CSET taxonomy.", + long_description: "An ID designating the individual who classified this incident according to the CSET taxonomy.", + display_type: "string", + mongo_type: "string", + default: "", + placeholder: "Select name here", + permitted_values: [], + weight: 5, + instant_facet: false, + required: false, + public: false, + hide_search: true + }, + { + field_number: "1.3", + short_name: "Annotation Status", + long_name: "Where in the annotation process is this incident?", + short_description: "What is the quality assurance status of the CSET classifications for this incident?", + long_description: "What is the quality assurance status of the CSET classifications for this incident?", + display_type: "enum", + mongo_type: "string", + default: "", + placeholder: "Select process status here", + permitted_values: [ + "1. Annotation in progress", + "2. Initial annotation complete", + "3. In peer review", + "4. Peer review complete", + "5. In quality control", + "6. Complete and final" + ], + weight: 10, + instant_facet: false, + required: false, + public: false, + hide_search: true, + notes: "When you start a row, switch this field from blank to “Annotation in progress” so other annotators know not to work on the same row (and can be sure it wasn’t skipped or left unfinished accidentally).\n\nOnce a row is marked “Initial annotation complete,” we will assume that any remaining blanks were left deliberately - that is, you looked, but couldn’t find enough information to fill out the blank fields. For this reason, please don’t mark a row “Initial annotation complete” until you’ve truly finished filling it out.\n\nWhen peer review begins, the assigned reviewer should switch the status to “In peer review.” When the review is complete and all comments have been resolved, either the peer reviewer or the original annotator should switch the status to “Peer review complete.”\n\nOptions 5 and 6 should only ever be selected by the project lead" + }, + { + field_number: "1.4", + short_name: "Peer Reviewer", + long_name: "Person responsible for reviewing annotations", + short_description: "This is the researcher that is responsible for ensuring the quality of the classifications applied to this incident.", + long_description: "The CSET taxonomy assigns individual researchers to each incident as the primary parties responsible for classifying the incident according to the taxonomy. This is the person responsible for assuring the integrity of annotator's classifications.", + display_type: "string", + mongo_type: "string", + default: "", + placeholder: "Select name here", + permitted_values: [], + weight: 20, + instant_facet: false, + required: false, + public: false, + hide_search: true, + notes: "The project lead will assign a peer reviewer to each incident." + }, + { + field_number: "1.5", + short_name: "Quality Control", + long_name: "Was this incident randomly selected for additional quality control?", + short_description: "Has someone flagged a potential issue with this incident's classifications? Annotators should leave this field blank.", + long_description: "The peer review process sometimes uncovers issues with the classifications that have been applied by the annotator. This field serves as a flag when there is a need for additional thought and input on the classifications applied", + display_type: "bool", + mongo_type: "bool", + default: "false", + placeholder: "", + permitted_values: [], + weight: 15, + instant_facet: false, + required: false, + public: false, + hide_search: true + }, + { + field_number: "2.1", + short_name: "Physical Objects", + long_name: "Did the incident occur in a domain with physical objects ?", + short_description: "Did the incident occur in a domain with physical objects ?", + long_description: "“Yes” if the AI system(s) is embedded in hardware that can interact with, affect, and change the physical objects (cars, robots, medical facilities, etc.). Mark “No” if the system cannot. This includes systems that inform, detect, predict, or recommend.", + display_type: "enum", + mongo_type: "string", + default: "maybe", + permitted_values: [ + "yes", + "no", + "maybe" + ], + placeholder: "", + weight: 40, + instant_facet: false, + required: false, + notes: "Context matters. AI systems embedded in hardware that can physically interact are more likely to cause death, injury, or damage." + }, + { + field_number: "2.2", + short_name: "Entertainment Industry", + long_name: "Did the AI incident occur in the entertainment industry?", + short_description: "Did the AI incident occur in the entertainment industry?", + long_description: "“Yes” if the sector in which the AI was used is associated with entertainment. “No” if it was used in a different, clearly identifiable sector. “Maybe” if the sector of use could not be determined.", + display_type: "enum", + mongo_type: "string", + default: "maybe", + permitted_values: [ + "yes", + "no", + "maybe" + ], + placeholder: "", + weight: 40, + instant_facet: false, + required: false, + notes: "Context matters. AI systems used for entertainment are less likely to result in harm. For example a deepfake used in a movie is less likely to cause harm than a deepfake used for political misinformation." + }, + { + field_number: "2.3", + short_name: "Report, Test, or Study of data", + long_name: "Was the incident about a report, test, or study of data instead of the AI itself?", + short_description: "Was the incident about a report, test, or study of data instead of the AI itself?", + long_description: "“Yes” if the incident is about a report, test, or study of the data and does not discuss an instance of injury, damage, or loss. “Maybe” if it is unclear. Otherwise mark “No.”", + display_type: "enum", + mongo_type: "string", + default: "maybe", + permitted_values: [ + "yes", + "no", + "maybe" + ], + placeholder: "", + weight: 40, + instant_facet: false, + required: false, + notes: "Sometimes there are reports about issues with the data that could be used to develop AI systems. Since there are mitigation approaches, data issues do not automatically mean that the associated AI will have issues that lead to harm. A projection or hypothesis of the harm resulting from data issues is not sufficient. There must be harm that can be clearly linked to an AI." + }, + { + field_number: "2.4", + short_name: "Deployed", + long_name: "Was the reported system (even if AI involvement is unknown) deployed or sold to users?", + short_description: "Was the reported system (even if AI involvement is unknown) deployed or sold to users?", + long_description: "“Yes” if the involved system was deployed or sold to users. “No” if it was not. “Maybe” if there is not enough information or if the use is unclear.", + display_type: "enum", + mongo_type: "string", + default: "maybe", + permitted_values: [ + "yes", + "no", + "maybe" + ], + placeholder: "", + weight: 40, + instant_facet: false, + required: false, + notes: "Systems that are not deployed or sold to users tend to still be in the development stage and hence are less likely to cause harm. However, harm can still be possible. " + }, + { + field_number: "2.5", + short_name: "Producer Test in Controlled Conditions", + long_name: "Was this a test or demonstration of an AI system done by developers, producers or researchers (versus users) in controlled conditions?", + short_description: "Was this a test or demonstration of an AI system done by developers, producers or researchers (versus users) in controlled conditions?", + long_description: "“Yes” if it was a test/demonstration performed by developers, producers or journalists in controlled conditions. “No” if it was not a test/demonstration. “No” if the test/demonstration was done by a user. “No” if the test/demonstration was in operational or uncontrolled conditions. “Maybe” otherwise.", + display_type: "enum", + mongo_type: "string", + default: "maybe", + permitted_values: [ + "yes", + "no", + "maybe" + ], + placeholder: "", + weight: 40, + instant_facet: false, + required: false, + notes: "AI system tests or demonstrations by developers, producers, or researchers in controlled environments are less likely to expose people, organizations, property, institutions, or the natural environment to harm. Controlled environments may include situations such as an isolated compute system, a regulatory sandbox, or an autonomous vehicle testing range. " + }, + { + field_number: "2.6", + short_name: "Producer Test in Operational Conditions", + long_name: "Was this a test or demonstration of an AI system done by developers, producers or researchers (versus users) in operational conditions?", + short_description: "Was this a test or demonstration of an AI system done by developers, producers or researchers (versus users) in operational conditions?", + long_description: "“Yes” if it was a test/demonstration performed by developers, producers or journalists in controlled conditions. “No” if it was not a test/demonstration. “No” if the test/demonstration was done by a user. “No” if the test/demonstration was in controlled or non-operational conditions. “Maybe” otherwise.", + display_type: "enum", + mongo_type: "string", + default: "maybe", + permitted_values: [ + "yes", + "no", + "maybe" + ], + placeholder: "", + weight: 40, + instant_facet: false, + required: false, + notes: "While almost every AI system undergoes testing or demonstration in a controlled environment, some also undergo testing or demonstration in an operational environment. Testing in operational environments still occurs before the system is deployed or sold to end-users. However, relative to controlled environments, operational environments try to closely represent real-world conditions and end-users that affect use of the AI system. Therefore, testing in an operational environment typically poses a heightened risk of harm to people, organizations, property, institutions, or the environment." + }, + { + field_number: "2.7", + short_name: "User Test in Controlled Conditions", + long_name: "Was this a test or demonstration done by users in controlled conditions?", + short_description: "Was this a test or demonstration done by users in controlled conditions?", + long_description: "“Yes” if it was a test/demonstration performed by users in controlled conditions. “No” if it was not a test/demonstration. “No” if the test/demonstration was done by developers, producers or researchers. “No” if the test/demonstration was in controlled or non-controlled conditions.“Maybe” otherwise.", + display_type: "enum", + mongo_type: "string", + default: "maybe", + permitted_values: [ + "yes", + "no", + "maybe" + ], + placeholder: "", + weight: 40, + instant_facet: false, + required: false, + notes: "The involvement of a user (versus a developer, producer, or researcher) increases the likelihood that harm can occur even if the AI system is being tested. Relative to controlled environments, controlled environments try to closely represent real-world conditions and end-users that affect use of the AI system. Therefore, testing in an controlled environment typically poses a heightened risk of harm to people, organizations, property, institutions, or the environment." + }, + { + field_number: "2.8", + short_name: "User Test in Operational Conditions", + long_name: "Was this a test or demonstration done by users in operational conditions?", + short_description: "Was this a test or demonstration done by users in operational conditions?", + long_description: "“Yes” if it was a test/demonstration performed by users in operational conditions. “No” if it was not a test/demonstration. “No” if the test/demonstration was done by developers, producers or researchers. “No” if the test/demonstration was in controlled or non-operational conditions.“Maybe” otherwise.", + display_type: "enum", + mongo_type: "string", + default: "maybe", + permitted_values: [ + "yes", + "no", + "maybe" + ], + placeholder: "", + weight: 40, + instant_facet: false, + required: false, + notes: "Sometimes, prior to deployment, the user will perform a test or demonstration of the AI system. The involvement of a user (versus a developer, producer, or researcher) increases the likelihood that harm can occur even if the AI system is being tested in controlled environments." + }, + { + field_number: "2.9", + short_name: "Harm Domain", + long_name: "Incident occurred in a domain where we could expect harm to occur?", + short_description: "Incident occurred in a domain where we could likely expect harm to occur?", + long_description: "Using the answers to the 8 domain questions, assess if the incident occurred in a domain where harm could be expected to occur. If you are unclear, input “maybe.”", + display_type: "enum", + mongo_type: "string", + default: "maybe", + permitted_values: [ + "yes", + "no", + "maybe" + ], + placeholder: "", + weight: 40, + instant_facet: false, + required: false, + hide_search: true, + notes: "Reflecting upon the previously answered questions, decide if the reported incident or instance occurred in a domain in which harm could possibly occur. This is not a decision on whether or not harm did occur. Just a reflection on the operating conditions or context of the system." + }, + { + field_number: "3.1", + short_name: "Tangible Harm", + long_name: "Did tangible harm (loss, damage or injury ) occur? ", + short_description: "Did tangible harm (loss, damage or injury ) occur? ", + long_description: "An assessment of whether tangible harm, imminent tangible harm, or non-imminent tangible harm occurred. This assessment does not consider the context of the tangible harm, if an AI was involved, or if there is an identifiable, specific, and harmed entity. It is also not assessing if an intangible harm occurred. It is only asking if tangible harm occurred and what its imminency was.", + display_type: "enum", + mongo_type: "string", + default: "unclear", + placeholder: "", + permitted_values: [ + "tangible harm definitively occurred", + "imminent risk of tangible harm (near miss) did occur", + "non-imminent risk of tangible harm (an issue) occurred", + "no tangible harm, near-miss, or issue", + "unclear" + ], + weight: 40, + instant_facet: false, + required: false, + public: true + }, + { + field_number: "3.2", + short_name: "AI System", + long_name: "Does the incident involve an AI system?", + short_description: "Does the incident involve an AI system?", + long_description: "An assessment of whether or not an AI system was involved. It is sometimes difficult to judge between an AI and an automated system or expert rules system. In these cases select “maybe”", + display_type: "enum", + mongo_type: "string", + default: "maybe", + permitted_values: [ + "yes", + "no", + "maybe" + ], + placeholder: "", + weight: 40, + instant_facet: false, + required: false, + public: true, + notes: "Note, over time more information about the incident may become available, allowing a ‘maybe’ to be changed to a ‘yes’ or ‘no.’" + }, + { + field_number: "3.3", + short_name: "Clear link to technology", + long_name: "Can the technology be directly and clearly linked to the adverse outcome of the incident", + short_description: "Can the technology be directly and clearly linked to the adverse outcome of the incident", + long_description: "An assessment of the technology's involvement in the chain of harm. \"Yes\" indicates that the technology was involved in harm, its behavior can be directly linked to the harm, and the harm may not have occurred if the technology acted differently. \"No\", indicates that the technology's behavior cannot be linked to the harm outcome. \"Maybe\" indicates that the link is unclear.", + hide_search: true, + display_type: "enum", + mongo_type: "string", + default: "maybe", + permitted_values: [ + "yes", + "no", + "maybe" + ], + placeholder: "", + weight: 40, + instant_facet: false, + required: false, + public: true + }, + { + field_number: "3.4", + short_name: "There is a potentially identifiable specific entity that experienced the harm", + long_name: "There is a potentially identifiable specific entity that experienced the harm", + short_description: "A potentially identifiable specific entity that experienced the harm can be characterized or identified.", + long_description: "“Yes” if it is theoretically possible to both specify and identify the entity. Having that information is not required. The information just needs to exist and be potentially discoverable. “No” if there are not any potentially identifiable specific entities or if the harmed entities are a class or subgroup that can only be characterized. ", + display_type: "bool", + mongo_type: "bool", + permitted_values: [], + placeholder: "", + default: "", + weight: 40, + instant_facet: false, + required: false, + public: true, + hide_search: true, + notes: "A potentially identifiable specific entity is an entity that can be described in detail such that the name (Mr. Joe Smith, Acme Inc, etc.) or a unique identifier (e.g. 100 Main Street, Anywhere USA) of the entity could be found. We may not know the name or identifier of the entity from the reports, but it does exist and could be found. For example, the general public is not a potentially identifiable specific entity. However, incarcerated people in the Springfield penitentiary would be specific entities because it would be possible to get a list of all the prisoners in the facility." + }, + { + field_number: "3.5", + short_name: "AI Harm Level", + long_name: "Annotator's AI tangible harm level assessment", + short_description: "An assessment of the AI tangible harm level, which takes into account the CSET definitions of AI tangible harm levels, along with the inputs for annotation fields about the AI, harm, chain of harm, and entity. ", + long_description: "An assessment of the AI tangible harm level, which takes into account the CSET definitions of AI tangible harm levels, along with the inputs for annotation fields about the AI, harm, chain of harm, and entity.", + display_type: "enum", + mongo_type: "string", + default: "unclear", + placeholder: "", + permitted_values: [ + "AI tangible harm event", + "AI tangible harm near-miss", + "AI tangible harm issue", + "none", + "unclear" + ], + weight: 40, + instant_facet: false, + required: false, + public: true, + notes: "Special interest intangible harm is determined in a different field. The determination of a special interest intangible harm is not dependant upon the AI tangible harm level" + }, + { + field_number: "3.6", + short_name: "AI Tangible Harm Level Notes", + long_name: "AI Tangible Harm Level Notes", + short_description: "Notes about the AI tangible harm level assessment", + long_description: "If for 3.5 you select unclear or leave it blank, please provide a brief description of why.\n\n You can also add notes if you want to provide justification for a level", + display_type: "long_string", + mongo_type: "string", + default: "", + placeholder: "Notes about the AI tangible harm level assessment", + permitted_values: [], + weight: 5, + instant_facet: false, + required: false, + public: true, + hide_search: true + }, + { + field_number: "4.1", + short_name: "Impact on Critical Services", + long_name: "Did this impact people's access to critical or public services (health care, social services, voting, transportation, etc)?", + short_description: "Indicates if people’s access to critical public services was impacted.", + long_description: "Did this impact people's access to critical or public services (health care, social services, voting, transportation, etc)?", + display_type: "enum", + mongo_type: "string", + default: "maybe", + permitted_values: [ + "yes", + "no", + "maybe" + ], + placeholder: "", + weight: 40, + instant_facet: false, + required: false, + public: true, + notes: "Public services include healthcare, social services, voting, public transportation, education, and consumer protection.\n\nNote, if ‘yes’ is selected then there was likely a violation of civil liberties and there was a special interest intangible harm." + }, + { + field_number: "4.2", + short_name: "Rights Violation", + long_name: "Was this a violation of human rights, civil liberties, civil rights, or democratic norms?", + short_description: "Indicate if a violation of human rights, civil rights, civil liberties, or democratic norms occurred.", + long_description: "Indicate if a violation of human rights, civil rights, civil liberties, or democratic norms occurred.", + display_type: "enum", + mongo_type: "string", + default: "maybe", + permitted_values: [ + "yes", + "no", + "maybe" + ], + placeholder: "", + weight: 40, + instant_facet: false, + required: false, + public: true, + notes: "It can often be difficult for the typical annotator to differentiate between violations of civil liberties, civil rights, human rights, and democratic norms. For this reason CSET grouped them together.\n\nHuman rights are rights inherent to all human beings, regardless of race, sex, nationality, ethnicity, language, religion, or any other status. They include the right to life and liberty, freedom from slavery and torture, freedom of opinion and expression, and the right to work and education. Civil rights are legal provisions that originate from notions of equality and can be enforced by law. Civil liberties are personal freedoms that are referenced in the Bill of Rights. Democratic norms are traditions, customs, and best practices that support democracy. An example of a democratic norm is accepting election results and facilitating a peaceful transfer of political power." + }, + { + field_number: "4.3", + short_name: "Involving Minor", + long_name: "Was a minor involved in the incident (disproportionally treated or specifically targeted/affected)", + short_description: "Was a minor involved in the incident (disproportionally treated or specifically targeted/affected)", + long_description: "Indicate if a minor was disproportionately targeted or affected", + display_type: "enum", + mongo_type: "string", + default: "maybe", + permitted_values: [ + "yes", + "no", + "maybe" + ], + placeholder: "", + weight: 40, + instant_facet: false, + required: false, + public: true, + notes: "Generally, governments have an interest in establishing heightened protections for minors. These protections are often associated with media content or privacy. For example, if an AI system illegally tracked a minor’s activity online, then answer “yes” to this question. There are instances where an AI system causes indiscriminate harm to a group of people, and it is plausible that some of those people are minors. However, in this case the entire group of people, adults and children alike, shared the distribution of harm equally and therefore the answer to this question would be “no.”" + }, + { + field_number: "4.4", + short_name: "Detrimental Content", + long_name: "Was detrimental content (misinformation, hate speech) involved?", + short_description: "Was detrimental content (misinformation, hate speech) involved?", + long_description: "Detrimental content can include deepfakes, identity misrepresentation, insults, threats of violence, eating disorder or self harm promotion, extremist content, misinformation, sexual abuse material, and scam emails. Detrimental content in itself is often not harmful, however, it can lead to or instigate injury, damage, or loss.", + display_type: "enum", + mongo_type: "string", + default: "maybe", + permitted_values: [ + "yes", + "no", + "maybe" + ], + placeholder: "", + weight: 40, + instant_facet: false, + required: false, + public: true + }, + { + field_number: "4.5", + short_name: "Protected Characteristic", + long_name: "Was a group of people or an individual treated differently based upon a protected characteristic?", + short_description: "Was a group of people treated differently based upon a protected characteristic (e.g. race, ethnicity, creed, immigrant status, color, religion, sex, national origin, age, disability, genetic information)?", + long_description: "Protected characteristics include religion, commercial facilities, geography, age, sex, sexual orientation or gender identity, familial status (e.g., having or not having children) or pregnancy, disability, veteran status, genetic information, financial means, race or creed, Ideology, nation of origin, citizenship, and immigrant status.\n\nAt the federal level in the US, age is a protected characteristic for people over the age of 40. Minors are not considered a protected class. For this reason the CSET annotation taxonomy has a separate field to note if a minor was involved.\n\nOnly mark yes if there is clear evidence discrimination occurred. If there are conflicting accounts, mark unsure. Do not mark that discrimination occurred based on expectation alone.", + display_type: "enum", + mongo_type: "string", + default: "maybe", + permitted_values: [ + "yes", + "no", + "maybe" + ], + placeholder: "", + weight: 40, + instant_facet: false, + required: false, + public: true + }, + { + field_number: "4.6", + short_name: "Harm Distribution Basis", + long_name: "If harms were potentially unevenly distributed among people, on what basis?", + short_description: "Indicates how the harms were potentially distributed.", + long_description: "Multiple can occur.\n\nGenetic information refers to information about a person’s genetic tests or the genetic tests of their relatives. Genetic information can predict the manifestation of a disease or disorder.", + display_type: "multi", + mongo_type: "string", + default: "unclear", + placeholder: "", + permitted_values: [ + "none", + "age", + "disability", + "familial status (e.g., having or not having children) or pregnancy", + "financial means", + "genetic information", + "geography", + "ideology", + "nation of origin, citizenship, immigrant status", + "race", + "religion", + "sex", + "sexual orientation or gender identity", + "veteran status", + "unclear", + "other" + ], + weight: 60, + instant_facet: false, + required: false, + public: true + }, + { + field_number: "4.7", + short_name: "Notes (special interest intangible harm)", + long_name: "Input any notes that may help explain your answers.", + short_description: "Input any notes that may help explain your answers.", + long_description: "Input any notes that may help explain your answers.", + display_type: "long_string", + mongo_type: "string", + default: "", + placeholder: "Notes", + permitted_values: [], + weight: 5, + instant_facet: false, + required: false, + public: true, + hide_search: true + }, + { + field_number: "5.1", + short_name: "Special Interest Intangible Harm", + long_name: "Was there a special interest intangible harm or risk of harm?", + short_description: "An assessment of whether a special interest intangible harm occurred. This assessment does not consider the context of the intangible harm, if an AI was involved, or if there is characterizable class or subgroup of harmed entities. It is also not assessing if an intangible harm occurred. It is only asking if a special interest intangible harm occurred.", + long_description: "An assessment of whether a special interest intangible harm occurred. This assessment does not consider the context of the intangible harm, if an AI was involved, or if there is characterizable class or subgroup of harmed entities. It is also not assessing if an intangible harm occurred. It is only asking if a special interest intangible harm occurred.", + display_type: "enum", + mongo_type: "string", + default: "maybe", + permitted_values: [ + "yes", + "no", + "maybe" + ], + placeholder: "", + weight: 5, + instant_facet: false, + required: false, + public: true + }, + { + field_number: "5.2", + short_name: "AI System", + long_name: "Does the incident involve an AI system?", + short_description: "Does the incident involve an AI system?", + long_description: "An assessment of whether or not an AI system was involved. It is sometimes difficult to judge between an AI and an automated system or expert rules system. In these cases select “maybe”", + display_type: "enum", + mongo_type: "string", + default: "maybe", + permitted_values: [ + "yes", + "no", + "maybe" + ], + placeholder: "", + weight: 40, + instant_facet: false, + required: false, + public: true, + hide_search: true, + notes: "Note, over time more information about the incident may become available, allowing a ‘maybe’ to be changed to a ‘yes’ or ‘no.’" + }, + { + field_number: "5.3", + short_name: "Clear link to Technology", + long_name: "Can the technology be directly and clearly linked to the adverse outcome of the incident?", + short_description: "Can the technology be directly and clearly linked to the adverse outcome of the incident?", + long_description: "An assessment of the technology's involvement in the chain of harm. \"Yes\" indicates that the technology was involved in harm, its behavior can be directly linked to the harm, and the harm may not have occurred if the technology acted differently. \"No\", indicates that the technology's behavior cannot be linked to the harm outcome. \"Maybe\" indicates that the link is unclear.", + display_type: "enum", + mongo_type: "string", + default: "maybe", + permitted_values: [ + "yes", + "no", + "maybe" + ], + placeholder: "", + weight: 40, + instant_facet: false, + required: false, + public: true + }, + { + field_number: "5.4", + short_name: "Harmed Class of Entities", + long_name: "There is a characterizable class or subgroup of entities that experienced the harm", + short_description: "“Yes” if the harmed entity or entities can be characterized. “No” if there are not any characterizable entities.", + long_description: "A characterizable class or subgroup are descriptions of different populations of people. Often they are characteristics by which people qualify for special protection by a law, policy, or similar authority.\n\n Sometimes, groups may be characterized by their exposure to the incident via geographical proximity (e.g., ‘visitors to the park’) or participation in an activity (e.g.,‘Twitter users’).", + display_type: "bool", + mongo_type: "bool", + permitted_values: [], + placeholder: "", + weight: 40, + instant_facet: false, + required: false, + public: true, + hide_search: true, + notes: "" + }, + { + field_number: "5.5", + short_name: "Annotator’s AI special interest intangible harm assessment", + long_name: "The annotator’s assessment of if an AI special interest intangible harm occurred.", + short_description: "The annotator’s assessment of if an AI special interest intangible harm occurred.", + long_description: "AI tangible harm is determined in a different field. The determination of a special interest intangible harm is not dependant upon the AI tangible harm level.", + display_type: "enum", + mongo_type: "string", + default: "maybe", + permitted_values: [ + "yes", + "no", + "maybe" + ], + placeholder: "", + weight: 40, + instant_facet: false, + required: false, + public: true, + notes: "" + }, + { + field_number: "5.6", + short_name: "Notes (AI special interest intangible harm)", + long_name: "If for 5.5 you select unclear or leave it blank, please provide a brief description of why.\n\nYou can also add notes if you want to provide justification for a level.", + short_description: "If for 5.5 you select unclear or leave it blank, please provide a brief description of why.\n\nYou can also add notes if you want to provide justification for a level.", + long_description: "If for 5.5 you select unclear or leave it blank, please provide a brief description of why.\n\nYou can also add notes if you want to provide justification for a level.", + display_type: "long_string", + mongo_type: "string", + default: "", + placeholder: "Notes", + permitted_values: [], + weight: 5, + instant_facet: false, + required: false, + public: true, + hide_search: true + }, + { + field_number: "6.1", + short_name: "Date of Incident Year", + long_name: "The year in which the incident first occurred.", + short_description: "The year in which the incident occurred. If there are multiple harms or occurrences of the incident, list the earliest. If a precise date is unavailable, but the available sources provide a basis for estimating the year, estimate. Otherwise, leave blank.\n\nEnter in the format of YYYY", + long_description: "The year in which the incident occurred. If there are multiple harms or occurrences of the incident, list the earliest. If a precise date is unavailable, but the available sources provide a basis for estimating the year, estimate. Otherwise, leave blank.", + display_type: "string", + mongo_type: "string", + default: "", + placeholder: "YYYY", + permitted_values: [], + weight: 5, + instant_facet: false, + required: false, + public: true + }, + { + field_number: "6.2", + short_name: "Date of Incident Month", + long_name: "The month in which the incident first occurred.", + short_description: "The month in which the incident occurred. If there are multiple harms or occurrences of the incident, list the earliest. If a precise date is unavailable, but the available sources provide a basis for estimating the month, estimate. Otherwise, leave blank.\n\nEnter in the format of MM", + long_description: "The month in which the incident occurred. If there are multiple harms or occurrences of the incident, list the earliest. If a precise date is unavailable, but the available sources provide a basis for estimating the month, estimate. Otherwise, leave blank.", + display_type: "string", + mongo_type: "string", + default: "", + placeholder: "MM", + permitted_values: [], + weight: 5, + instant_facet: false, + required: false, + public: true, + hide_search: true + }, + { + field_number: "6.3", + short_name: "Date of Incident Day", + long_name: "The day on which the first incident occurred.", + short_description: "The day on which the incident occurred. If a precise date is unavailable, leave blank.\n\nEnter in the format of DD", + long_description: "The day on which the incident occurred. If a precise date is unavailable, leave blank.\n\nEnter in the format of DD", + display_type: "string", + mongo_type: "string", + default: "", + placeholder: "DD", + permitted_values: [], + weight: 5, + instant_facet: false, + required: false, + public: true, + hide_search: true + }, + { + field_number: "6.4", + short_name: "Estimated Date", + long_name: "Is the date estimated?", + short_description: "“Yes” if the data was estimated. “No” otherwise.", + long_description: "“Yes” if the data was estimated. “No” otherwise.", + display_type: "bool", + mongo_type: "bool", + permitted_values: [], + placeholder: "", + weight: 5, + instant_facet: false, + required: false, + public: true, + hide_search: true + }, + { + field_number: "6.5", + short_name: "Multiple AI Interaction", + long_name: "Was the AI interacting with another AI?", + short_description: "“Yes” if two or more independently operating AI systems were involved. “No” otherwise.", + long_description: "This happens very rarely but is possible. Examples include two chatbots having a conversation with each other, or two autonomous vehicles in a crash.", + display_type: "enum", + mongo_type: "string", + default: "maybe", + permitted_values: [ + "yes", + "no", + "maybe" + ], + placeholder: "", + weight: 5, + instant_facet: false, + required: false, + public: true + }, + { + field_number: "6.6", + short_name: "Embedded", + long_name: "Is the AI embedded in a physical system or have a physical presence?", + short_description: "“Yes” if the AI is embedded in a physical system. “No” if it is not. “Maybe” if it is unclear.", + long_description: "This question is slightly different from the one in field 2.1.1. That question asks about there being interaction with physical objects–an ability to manipulate or change. A system can be embedded in a physical object and able to interact with the physical environment, e.g. a vacuum robot. A system can be embedded in a physical object and not interact with a physical environment, e.g. a camera system that only records images when the AI detects that dogs are present. AI systems that are accessed through API, web-browser, etc by using a mobile device or computer are not considered to be embedded in hardware systems. They are accessed through hardware.", + display_type: "enum", + mongo_type: "string", + default: "maybe", + permitted_values: [ + "yes", + "no", + "maybe" + ], + placeholder: "", + weight: 5, + instant_facet: false, + required: false, + public: true + }, + { + field_number: "6.7", + short_name: "Location City", + long_name: "If the incident occurred at a specific known location, note the city.", + short_description: "If the incident occurred at a specific known location, note the city.", + long_description: "If the incident occurred at a specific known location, note the city. If there are multiple relevant locations, enter multiple city/state/country values.", + display_type: "string", + mongo_type: "string", + default: "", + placeholder: "City", + permitted_values: [], + weight: 5, + instant_facet: false, + required: false, + public: true, + hide_search: true + }, + { + field_number: "6.8", + short_name: "Location State/Province (two letters)", + long_name: "If the incident occurred at a specific known location, note the state/province.", + short_description: "If the incident occurred at a specific known location, note the state/province.", + long_description: "If the incident occurred at a specific known location, note the state/province. If there are multiple relevant locations, enter multiple city/state/country values.", + display_type: "string", + mongo_type: "string", + default: "", + placeholder: "State or Province", + permitted_values: [], + weight: 5, + instant_facet: false, + required: false, + public: true, + hide_search: true + }, + { + field_number: "6.9", + short_name: "Location Country (two letters)", + long_name: "If the incident occurred at a specific known location, note the country. ", + short_description: "If the incident occurred at a specific known location, note the country. Follow ISO 3166 for the 2-letter country codes.", + long_description: "Follow ISO 3166 for the 2-letter country codes.\n\nIf there are multiple relevant locations, enter multiple city/state/country values.", + display_type: "string", + mongo_type: "string", + default: "", + placeholder: "Country", + permitted_values: [], + weight: 5, + instant_facet: false, + required: false, + public: true + }, + { + field_number: "6.10", + short_name: "Location Region", + long_name: "Location Region", + short_description: "Select the region of the world where the incident occurred. If it occurred in multiple, leave blank.", + long_description: "Use this reference to map countries to regions: https://www.dhs.gov/geographic-regions", + display_type: "enum", + mongo_type: "string", + default: "unclear", + placeholder: "", + permitted_values: [ + "Global", + "Africa", + "Asia", + "Caribbean", + "Central America", + "Europe", + "North America", + "Oceania", + "South America", + "unclear" + ], + weight: 5, + instant_facet: false, + required: false, + public: true + }, + { + field_number: "6.11", + short_name: "Infrastructure Sectors", + long_name: "Which critical infrastructure sectors were affected, if any?", + short_description: "Which critical infrastructure sectors were affected, if any?", + long_description: "Which critical infrastructure sectors were affected, if any?", + display_type: "multi", + mongo_type: "string", + default: "unclear", + placeholder: "", + permitted_values: [ + "chemical", + "commercial facilities", + "communications", + "critical manufacturing", + "dams", + "defense-industrial base", + "emergency services", + "energy", + "financial services", + "food and agriculture", + "government facilities", + "healthcare and public health", + "information technology", + "nuclear ", + "transportation", + "water and wastewater", + "Other", + "unclear" + ], + weight: 5, + instant_facet: false, + required: false, + public: true + }, + { + field_number: "6.12", + short_name: "Operating Conditions", + long_name: "A record of any abnormal or atypical operational conditions that occurred.", + short_description: "A record of any abnormal or atypical operational conditions that occurred.", + long_description: "A record of any abnormal or atypical operational conditions that occurred. This field is most often blank.", + display_type: "list", + mongo_type: "array", + default: "", + placeholder: "e.g. raining; night; low visibility", + permitted_values: [], + weight: 5, + instant_facet: false, + required: false, + public: true + }, + { + field_number: "6.13", + short_name: "Notes (Environmental and Temporal Characteristics)", + long_name: "Notes (Environmental and Temporal Characteristics)", + short_description: "Input any notes that may help explain your answers.", + long_description: "Input any notes that may help explain your answers.", + display_type: "long_string", + mongo_type: "string", + default: "", + placeholder: "Notes", + permitted_values: [], + weight: 5, + instant_facet: false, + required: false, + public: true, + hide_search: true + }, + { + field_number: "7", + short_name: "Entities", + long_name: "Characterizing Entities and the Harm", + short_description: "Characterizing Entities and the Harm", + long_description: "Characterizing Entities and the Harm", + display_type: "object-list", + mongo_type: "array", + hide_search: true, + subfields: [ + { + field_number: "7.1", + short_name: "Entity", + long_name: "A short 1 to 2 word description of the entity.", + short_description: "A short 1 to 2 word description of the entity. When possible use a proper name for the entity, making it a Named Entity.", + long_description: "A short 1 to 2 word description of the entity. When possible use a proper name for the entity, making it a Named Entity.\n\nAnnotate information for each entity involved in the report. Try to capture every entity directly linked to the harm. Think about the entity that experienced the harm, all of the entities between them and the AI, and then all of the entities involved in producing and deploying the AI.\n\nEmployees representing a company in a media or public relations capacity should not be included as an entity.", + display_type: "string", + mongo_type: "string", + complete_from: { + entities: true + }, + default: "", + placeholder: "", + permitted_values: [], + weight: 5, + instant_facet: false, + required: false, + public: true + }, + { + field_number: "7.2", + short_name: "Named Entity", + long_name: "Named Entity Indicator", + short_description: "Indicates if the entity is a Named Entity.", + long_description: "Indicates if the entity is a Named Entity. “Yes” if the entity is a named entity. “No” otherwise.", + display_type: "bool", + mongo_type: "bool", + permitted_values: [], + placeholder: "", + weight: 5, + instant_facet: false, + required: false, + public: true + }, + { + field_number: "7.3", + short_name: "Entity type", + long_name: "Indicates the type of entity", + short_description: "Indicates the type of entity", + long_description: "Indicates the type of entity. If multiple selections could characterize the entity, select the primary function of the entity.", + display_type: "enum", + mongo_type: "string", + default: "unclear", + placeholder: "Entity Type", + permitted_values: [ + "individual", + "group of individuals", + "for-profit organization", + "non-profit organization", + "government entity", + "privately owned space", + "public space", + "infrastructure", + "social or political system", + "product", + "other", + "unclear" + ], + weight: 5, + instant_facet: false, + required: false, + public: true, + hide_search: true + }, + { + field_number: "7.4", + short_name: "Entity Relationship to the AI", + long_name: "Entity Relationship to the AI", + short_description: "Indicates the entity’s relationship to the AI.", + long_description: "Indicates the entity’s relationship to the AI. Note, the smallest possible chain of harm has just two elements; an AI and an entity experiencing harm, near-miss, or issue.", + display_type: "list", + mongo_type: "array", + default: "", + placeholder: "", + permitted_values: [ + "developer", + "deployer", + "government oversight", + "user", + "AI", + "geographic area of use", + "researcher", + "product containing AI", + "watchdog" + ], + weight: 5, + instant_facet: false, + required: false, + public: true + }, + { + field_number: "7.5", + short_name: "Harm Category Experienced", + long_name: "Was an AI special interest intangible harm, tangible harm event, near-miss, or issue experienced by this entity", + short_description: "Was an AI special interest intangible harm, tangible harm event, near-miss, or issue experienced by this entity", + long_description: "Was an AI special interest intangible harm, tangible harm event, near-miss, or issue experienced by this entity. For each recorded entity, indicate the harm category that they experienced. Because recorded entities have a variety of roles in the AI incident, not every recorded entity will experience harm.", + display_type: "enum", + mongo_type: "string", + default: "unclear", + placeholder: "", + permitted_values: [ + "AI special interest intangible harm", + "AI tangible harm event", + "AI tangible harm near-miss", + "AI tangible harm issue", + "Other harm not meeting CSET definitions", + "not applicable", + "unclear" + ], + weight: 5, + instant_facet: false, + required: false, + public: true + }, + { + field_number: "7.6", + short_name: "Harm Type Experienced", + long_name: "Type of harm experienced by entity ", + short_description: "Indicates the type of harm experienced by the harmed entity", + long_description: "Indicates the type of harm experienced by the harmed entity. Only entities experiencing harm should have an assigned type. If the entity did not experience the harm, ‘not applicable’ should be selected.", + display_type: "enum", + mongo_type: "string", + default: "not applicable", + placeholder: "", + permitted_values: [ + "physical health/safety", + "financial loss", + "physical property", + "intangible property", + "infrastructure", + "natural environment", + "social or political systems", + "violation of human rights, civil liberties, civil rights, or democratic norms", + "detrimental content", + "disproportionate treatment based upon a protected characteristic", + "other tangible harm", + "other intangible harm", + "not applicable" + ], + weight: 5, + instant_facet: false, + required: false, + public: true + }, + { + field_number: "7.7", + short_name: "Notes (Characterizing Entities and the Harm)", + long_name: "Notes (Characterizing Entities and the Harm)", + short_description: "Input any notes that may help explain your answers.", + long_description: "Input any notes that may help explain your answers.", + display_type: "long_string", + mongo_type: "string", + default: "", + placeholder: "Notes", + permitted_values: [], + weight: 5, + instant_facet: false, + required: false, + public: true, + hide_search: true + } + ], + default: "[]", + placeholder: "", + permitted_values: [], + weight: 5, + instant_facet: false, + required: false, + public: true + }, + { + field_number: "8.1", + short_name: "Lives Lost", + long_name: "How many human lives were lost?", + short_description: "Indicates the number of deaths reported", + long_description: "This field cannot be greater than zero if the harm is anything besides ‘Physical health/safety.’ ", + display_type: "int", + mongo_type: "int", + default: "", + placeholder: "", + permitted_values: [], + weight: 5, + instant_facet: false, + required: false, + public: true + }, + { + field_number: "8.2", + short_name: "Injuries", + long_name: "How many humans were injured?", + short_description: "Indicate the number of injuries reported.", + long_description: "This field cannot be greater than zero if the harm is anything besides 'Physical health/safety'.\n\nAll reported injuries should count, regardless of their severity level. If a person lost their limb and another person scraped their elbow, both cases would be considered injuries. Do not include the number of deaths in this count.", + display_type: "int", + mongo_type: "int", + default: "", + placeholder: "", + permitted_values: [], + weight: 5, + instant_facet: false, + required: false, + public: true + }, + { + field_number: "8.3", + short_name: "Estimated Harm Quantities", + long_name: "Are any quantities estimated?", + short_description: "Indicates if the amount was estimated.", + long_description: "Indicates if the amount was estimated.", + display_type: "bool", + mongo_type: "bool", + permitted_values: [], + placeholder: "", + weight: 5, + instant_facet: false, + required: false, + hide_search: true, + public: true + }, + { + field_number: "8.4", + short_name: "Notes ( Tangible Harm Quantities Information)", + long_name: "Notes ( Tangible Harm Quantities Information)", + short_description: "Input any notes that may help explain your answers.", + long_description: "Input any notes that may help explain your answers.", + display_type: "long_string", + mongo_type: "string", + default: "", + placeholder: "Notes", + permitted_values: [], + weight: 5, + instant_facet: false, + required: false, + public: true, + hide_search: true + }, + { + field_number: "9.1", + short_name: "AI System Description", + long_name: "Description of the AI system involved", + short_description: "A description of the AI system (when possible)", + long_description: "Describe the AI system in as much detail as the reports will allow.\n\nA high level description of the AI system is sufficient, but if more technical details about the AI system are available, include them in the description as well.", + display_type: "long_string", + mongo_type: "string", + default: "", + placeholder: "Description of the AI system involved", + permitted_values: [], + weight: 5, + instant_facet: false, + required: false, + public: true, + hide_search: true + }, + { + field_number: "9.2", + short_name: "Data Inputs", + long_name: "Description of data inputs to the AI system", + short_description: "A list of the types of data inputs for the AI system.", + long_description: "This is a freeform field that can have any value. There could be multiple entries for this field.\n\nCommon ones include\n\n- still images\n- video\n- text\n- speech\n- Personally Identifiable Information\n- structured data\n- other\n- unclear\n\nStill images are static images. Video images consist of moving images. Text and speech data are considered an important category of unstructured data. They consist of written and spoken words that are not in a tabular format. Personally identifiable information is data that can uniquely identify an individual and may contain sensitive information. Structured data is often in a tabular, machine readable format and can typically be used by an AI system without much preprocessing.\n\nAvoid using ‘unstructured data’ data in this field. Instead specify the type of unstructured data; text, images, audio files, etc. It is ok to use ‘structured data’ in this field.\n\nRecord what the media report explicitly states. If the report does not explicitly state an input modality but it is likely that a particular kind of input contributed to the harm or near harm, record that input. If you are still unsure, do not record anything.", + display_type: "list", + mongo_type: "array", + default: "", + placeholder: "", + permitted_values: [], + weight: 5, + instant_facet: false, + required: false, + public: true + }, + { + field_number: "9.3", + short_name: "Sector of Deployment", + long_name: "Indicates the sector in which the AI system is deployed", + short_description: "Indicate the sector in which the AI system is deployed", + long_description: "Indicate the sector in which the AI system is deployed\n\nThere could be multiple entries for this field.", + display_type: "multi", + mongo_type: "array", + default: "", + placeholder: "", + permitted_values: [ + "agriculture, forestry and fishing", + "mining and quarrying", + "manufacturing", + "electricity, gas, steam and air conditioning supply", + "water supply", + "construction", + "wholesale and retail trade", + "transportation and storage", + "accommodation and food service activities", + "information and communication", + "financial and insurance activities", + "real estate activities", + "professional, scientific and technical activities", + "administrative and support service activities", + "public administration", + "defense", + "law enforcement", + "Education", + "human health and social work activities", + "Arts, entertainment and recreation", + "other service activities", + "activities of households as employers", + "activities of extraterritorial organizations and bodies", + "other", + "unclear" + ], + weight: 60, + instant_facet: false, + required: false, + public: true + }, + { + field_number: "9.4", + short_name: "Public Sector Deployment", + long_name: "Indicates whether the AI system is deployed in the public sector", + short_description: "Indicate whether the AI system is deployed in the public sector", + long_description: "Indicate whether the AI system is deployed in the public sector. The public sector is the part of the economy that is controlled and operated by the government.", + display_type: "enum", + mongo_type: "string", + default: "maybe", + permitted_values: [ + "yes", + "no", + "maybe" + ], + placeholder: "", + weight: 40, + instant_facet: false, + required: false, + public: true + }, + { + field_number: "9.5", + short_name: "Autonomy Level", + long_name: "Autonomy Level", + short_description: "Autonomy1: The system operates independently without simultaneous human oversight, interaction, or intervention.\n\nAutonomy2: The system operates independently but with human oversight, where a human can observe and override the system’s decisions in real time.\n\nAutonomy3: The system does not independently make decisions but instead provides information to a human who actively chooses to proceed with the AI’s information.", + long_description: "Autonomy1: The system operates independently without simultaneous human oversight, interaction, or intervention.\n\nAutonomy2: The system operates independently but with human oversight, where a human can observe and override the system’s decisions in real time.\n\nAutonomy3: The system does not independently make decisions but instead provides information to a human who actively chooses to proceed with the AI’s information.", + display_type: "enum", + mongo_type: "string", + default: "", + placeholder: "", + permitted_values: [ + "Autonomy1", + "Autonomy2", + "Autonomy3", + "unclear" + ], + weight: 40, + instant_facet: false, + required: false, + public: true + }, + { + field_number: "9.8", + short_name: "Notes (Information about AI System)", + long_name: "Notes (Information about AI System)", + short_description: "Input any notes that may help explain your answers.", + long_description: "Input any notes that may help explain your answers.", + display_type: "long_string", + mongo_type: "string", + default: "", + placeholder: "Notes", + permitted_values: [], + weight: 5, + instant_facet: false, + required: false, + public: true, + hide_search: true + }, + { + field_number: "10.1", + short_name: "Intentional Harm", + long_name: "Was the AI intentionally developed or deployed to perform the harm?", + short_description: "Was the AI intentionally developed or deployed to perform the harm?\n\nIf yes, did the AI’s behavior result in unintended or intended harm?", + long_description: "Indicates if the system was designed to do harm. If it was designed to perform harm, the field will indicate if the AI system did or did not create unintended harm–i.e. was the reported harm the harm that AI was expected to perform or a different unexpected harm? ", + display_type: "enum", + mongo_type: "string", + default: "unclear", + placeholder: "", + permitted_values: [ + "Yes. Intentionally designed to perform harm and did create intended harm", + "Yes. Intentionally designed to perform harm but created an unintended harm (a different harm may have occurred)", + "No. Not intentionally designed to perform harm", + "unclear" + ], + weight: 40, + instant_facet: false, + required: false, + public: true, + notes: "Tracking and analyzing harm from AI systems designed to do harm is valuable and worthwhile. However, analysts may want to separately analyze harm from AI systems that were or were not designed to produce the observed harm." + }, + { + field_number: "10.2", + short_name: "Physical System Type", + long_name: "Into what type of physical system was the AI integrated, if any?", + short_description: "Describe the type of physical system that the AI was integrated into.", + long_description: "Describe the type of physical system that the AI was integrated into. ", + display_type: "string", + mongo_type: "string", + default: "", + placeholder: "Physical System Type (e.g. trash sorting robot)", + permitted_values: [], + weight: 5, + instant_facet: false, + required: false, + public: true + }, + { + field_number: "10.3", + short_name: "AI Task", + long_name: "AI task or core application area", + short_description: "Describe the AI’s application.", + long_description: "Describe the AI’s application.\n\nIt is likely that the annotator will not have enough information to complete this field. If this occurs, enter unclear.\n\nThis is a freeform field. Some possible entries are\n\n- unclear\n- human language technologies\n- computer vision\n- robotics\n- automation and/or optimization\n- other\n\nThe application area of an AI is the high level task that the AI is intended to perform. It does not describe the technical methods by which the AI performs the task. Considering what an AI’s technical methods enable it to do is another way of arriving at what an AI’s application is. \n\nIt is possible for multiple application areas to be involved. When possible pick the principle or domain area, but it is ok to select multiple areas.", + display_type: "list", + mongo_type: "array", + default: "", + placeholder: "", + permitted_values: [], + weight: 5, + instant_facet: false, + required: false, + public: true + }, + { + field_number: "10.4", + short_name: "AI tools and methods", + long_name: "AI tools and methods", + short_description: "Describe the tools and methods that enable the AI’s application.", + long_description: "Describe the tools and methods that enable the AI’s application.\n\nIt is likely that the annotator will not have enough information to complete this field. If this occurs, enter unclear\n\nThis is a freeform field. Some possible entries are\n\n- unclear\n- reinforcement learning\n- neural networks\n- decision trees\n- bias mitigation\n- optimization\n- classifier\n- NLP/text analytics\n- continuous learning\n- unsupervised learning\n- supervised learning\n- clustering\n- prediction\n- rules\n- random forest\n\nAI tools and methods are the technical building blocks that enable the AI’s application.", + display_type: "list", + mongo_type: "array", + default: "unclear", + placeholder: "", + permitted_values: [], + weight: 40, + instant_facet: false, + required: false, + public: true + }, + { + field_number: "10.5", + short_name: "Notes (AI Functionality and Techniques)", + long_name: "Notes (AI Functionality and Techniques)", + short_description: "Input any notes that may help explain your answers.", + long_description: "Input any notes that may help explain your answers.", + display_type: "long_string", + mongo_type: "string", + default: "", + placeholder: "Notes", + permitted_values: [], + weight: 5, + instant_facet: false, + required: false, + public: true, + hide_search: true + } + ], + + // TODO: created_at is missing from Atlas schema + // created_at: new Date("1722269934190") + }, + { + _id: new ObjectId("5f5f3b3b4f3b9b001f3b9b00"), + namespace: "CSETv1_Annotator-2", + weight: 70, + complete_entities: true, + description: "# What is the CSET Taxonomy?\n\nThe CSET AI Harm Taxonomy for AIID is the second edition of the \nCSET incident taxonomy.", + dummy_fields: [ + { + field_number: "1", + short_name: "Metadata" + }, + { + field_number: "2", + short_name: "Incident Domain" + }, + { + field_number: "3", + short_name: "AI Tangible Harm Assessment" + }, + { + field_number: "4", + short_name: "Special Interest Intangible Harm" + }, + { + field_number: "5", + short_name: "AI Special Interest Intangible Harm Assessment" + }, + { + field_number: "6", + short_name: "Environmental and Temporal Characteristics" + }, + { + field_number: "7", + short_name: "Characterizing Entities and the Harm" + }, + { + field_number: "8", + short_name: "Tangible Harm Quantities " + }, + { + field_number: "9", + short_name: "Information about AI System" + }, + { + field_number: "10", + short_name: "AI Functionality and Techniques" + } + ], + field_list: [ + { + field_number: "1.1", + short_name: "Incident Number", + long_name: "The number of the incident in the AI Incident Database.", + short_description: "The number of the incident in the AI Incident Database.", + long_description: "The number of the incident in the AI Incident Database.", + display_type: "int", + mongo_type: "int", + default: "", + weight: 5, + instant_facet: false, + required: false, + public: true, + hide_search: true + }, + { + field_number: "1.2", + short_name: "Annotator", + long_name: "Person responsible for the annotations", + short_description: "This is the researcher that is responsible for applying the classifications of the CSET taxonomy.", + long_description: "An ID designating the individual who classified this incident according to the CSET taxonomy.", + display_type: "string", + mongo_type: "string", + default: "", + placeholder: "Select name here", + permitted_values: [], + weight: 5, + instant_facet: false, + required: false, + public: false, + hide_search: true + }, + { + field_number: "1.3", + short_name: "Annotation Status", + long_name: "Where in the annotation process is this incident?", + short_description: "What is the quality assurance status of the CSET classifications for this incident?", + long_description: "What is the quality assurance status of the CSET classifications for this incident?", + display_type: "enum", + mongo_type: "string", + default: "", + placeholder: "Select process status here", + permitted_values: [ + "1. Annotation in progress", + "2. Initial annotation complete", + "3. In peer review", + "4. Peer review complete", + "5. In quality control", + "6. Complete and final" + ], + weight: 10, + instant_facet: false, + required: false, + public: false, + hide_search: true, + notes: "When you start a row, switch this field from blank to “Annotation in progress” so other annotators know not to work on the same row (and can be sure it wasn’t skipped or left unfinished accidentally).\n\nOnce a row is marked “Initial annotation complete,” we will assume that any remaining blanks were left deliberately - that is, you looked, but couldn’t find enough information to fill out the blank fields. For this reason, please don’t mark a row “Initial annotation complete” until you’ve truly finished filling it out.\n\nWhen peer review begins, the assigned reviewer should switch the status to “In peer review.” When the review is complete and all comments have been resolved, either the peer reviewer or the original annotator should switch the status to “Peer review complete.”\n\nOptions 5 and 6 should only ever be selected by the project lead" + }, + { + field_number: "1.4", + short_name: "Peer Reviewer", + long_name: "Person responsible for reviewing annotations", + short_description: "This is the researcher that is responsible for ensuring the quality of the classifications applied to this incident.", + long_description: "The CSET taxonomy assigns individual researchers to each incident as the primary parties responsible for classifying the incident according to the taxonomy. This is the person responsible for assuring the integrity of annotator's classifications.", + display_type: "string", + mongo_type: "string", + default: "", + placeholder: "Select name here", + permitted_values: [], + weight: 20, + instant_facet: false, + required: false, + public: false, + hide_search: true, + notes: "The project lead will assign a peer reviewer to each incident." + }, + { + field_number: "1.5", + short_name: "Quality Control", + long_name: "Was this incident randomly selected for additional quality control?", + short_description: "Has someone flagged a potential issue with this incident's classifications? Annotators should leave this field blank.", + long_description: "The peer review process sometimes uncovers issues with the classifications that have been applied by the annotator. This field serves as a flag when there is a need for additional thought and input on the classifications applied", + display_type: "bool", + mongo_type: "bool", + default: "false", + placeholder: "", + permitted_values: [], + weight: 15, + instant_facet: false, + required: false, + public: false, + hide_search: true + }, + { + field_number: "2.1", + short_name: "Physical Objects", + long_name: "Did the incident occur in a domain with physical objects ?", + short_description: "Did the incident occur in a domain with physical objects ?", + long_description: "“Yes” if the AI system(s) is embedded in hardware that can interact with, affect, and change the physical objects (cars, robots, medical facilities, etc.). Mark “No” if the system cannot. This includes systems that inform, detect, predict, or recommend.", + display_type: "enum", + mongo_type: "string", + default: "maybe", + permitted_values: [ + "yes", + "no", + "maybe" + ], + placeholder: "", + weight: 40, + instant_facet: false, + required: false, + notes: "Context matters. AI systems embedded in hardware that can physically interact are more likely to cause death, injury, or damage." + }, + { + field_number: "2.2", + short_name: "Entertainment Industry", + long_name: "Did the AI incident occur in the entertainment industry?", + short_description: "Did the AI incident occur in the entertainment industry?", + long_description: "“Yes” if the sector in which the AI was used is associated with entertainment. “No” if it was used in a different, clearly identifiable sector. “Maybe” if the sector of use could not be determined.", + display_type: "enum", + mongo_type: "string", + default: "maybe", + permitted_values: [ + "yes", + "no", + "maybe" + ], + placeholder: "", + weight: 40, + instant_facet: false, + required: false, + notes: "Context matters. AI systems used for entertainment are less likely to result in harm. For example a deepfake used in a movie is less likely to cause harm than a deepfake used for political misinformation." + }, + { + field_number: "2.3", + short_name: "Report, Test, or Study of data", + long_name: "Was the incident about a report, test, or study of data instead of the AI itself?", + short_description: "Was the incident about a report, test, or study of data instead of the AI itself?", + long_description: "“Yes” if the incident is about a report, test, or study of the data and does not discuss an instance of injury, damage, or loss. “Maybe” if it is unclear. Otherwise mark “No.”", + display_type: "enum", + mongo_type: "string", + default: "maybe", + permitted_values: [ + "yes", + "no", + "maybe" + ], + placeholder: "", + weight: 40, + instant_facet: false, + required: false, + notes: "Sometimes there are reports about issues with the data that could be used to develop AI systems. Since there are mitigation approaches, data issues do not automatically mean that the associated AI will have issues that lead to harm. A projection or hypothesis of the harm resulting from data issues is not sufficient. There must be harm that can be clearly linked to an AI." + }, + { + field_number: "2.4", + short_name: "Deployed", + long_name: "Was the reported system (even if AI involvement is unknown) deployed or sold to users?", + short_description: "Was the reported system (even if AI involvement is unknown) deployed or sold to users?", + long_description: "“Yes” if the involved system was deployed or sold to users. “No” if it was not. “Maybe” if there is not enough information or if the use is unclear.", + display_type: "enum", + mongo_type: "string", + default: "maybe", + permitted_values: [ + "yes", + "no", + "maybe" + ], + placeholder: "", + weight: 40, + instant_facet: false, + required: false, + notes: "Systems that are not deployed or sold to users tend to still be in the development stage and hence are less likely to cause harm. However, harm can still be possible. " + }, + { + field_number: "2.5", + short_name: "Producer Test in Controlled Conditions", + long_name: "Was this a test or demonstration of an AI system done by developers, producers or researchers (versus users) in controlled conditions?", + short_description: "Was this a test or demonstration of an AI system done by developers, producers or researchers (versus users) in controlled conditions?", + long_description: "“Yes” if it was a test/demonstration performed by developers, producers or journalists in controlled conditions. “No” if it was not a test/demonstration. “No” if the test/demonstration was done by a user. “No” if the test/demonstration was in operational or uncontrolled conditions. “Maybe” otherwise.", + display_type: "enum", + mongo_type: "string", + default: "maybe", + permitted_values: [ + "yes", + "no", + "maybe" + ], + placeholder: "", + weight: 40, + instant_facet: false, + required: false, + notes: "AI system tests or demonstrations by developers, producers, or researchers in controlled environments are less likely to expose people, organizations, property, institutions, or the natural environment to harm. Controlled environments may include situations such as an isolated compute system, a regulatory sandbox, or an autonomous vehicle testing range. " + }, + { + field_number: "2.6", + short_name: "Producer Test in Operational Conditions", + long_name: "Was this a test or demonstration of an AI system done by developers, producers or researchers (versus users) in operational conditions?", + short_description: "Was this a test or demonstration of an AI system done by developers, producers or researchers (versus users) in operational conditions?", + long_description: "“Yes” if it was a test/demonstration performed by developers, producers or journalists in controlled conditions. “No” if it was not a test/demonstration. “No” if the test/demonstration was done by a user. “No” if the test/demonstration was in controlled or non-operational conditions. “Maybe” otherwise.", + display_type: "enum", + mongo_type: "string", + default: "maybe", + permitted_values: [ + "yes", + "no", + "maybe" + ], + placeholder: "", + weight: 40, + instant_facet: false, + required: false, + notes: "While almost every AI system undergoes testing or demonstration in a controlled environment, some also undergo testing or demonstration in an operational environment. Testing in operational environments still occurs before the system is deployed or sold to end-users. However, relative to controlled environments, operational environments try to closely represent real-world conditions and end-users that affect use of the AI system. Therefore, testing in an operational environment typically poses a heightened risk of harm to people, organizations, property, institutions, or the environment." + }, + { + field_number: "2.7", + short_name: "User Test in Controlled Conditions", + long_name: "Was this a test or demonstration done by users in controlled conditions?", + short_description: "Was this a test or demonstration done by users in controlled conditions?", + long_description: "“Yes” if it was a test/demonstration performed by users in controlled conditions. “No” if it was not a test/demonstration. “No” if the test/demonstration was done by developers, producers or researchers. “No” if the test/demonstration was in controlled or non-controlled conditions.“Maybe” otherwise.", + display_type: "enum", + mongo_type: "string", + default: "maybe", + permitted_values: [ + "yes", + "no", + "maybe" + ], + placeholder: "", + weight: 40, + instant_facet: false, + required: false, + notes: "The involvement of a user (versus a developer, producer, or researcher) increases the likelihood that harm can occur even if the AI system is being tested. Relative to controlled environments, controlled environments try to closely represent real-world conditions and end-users that affect use of the AI system. Therefore, testing in an controlled environment typically poses a heightened risk of harm to people, organizations, property, institutions, or the environment." + }, + { + field_number: "2.8", + short_name: "User Test in Operational Conditions", + long_name: "Was this a test or demonstration done by users in operational conditions?", + short_description: "Was this a test or demonstration done by users in operational conditions?", + long_description: "“Yes” if it was a test/demonstration performed by users in operational conditions. “No” if it was not a test/demonstration. “No” if the test/demonstration was done by developers, producers or researchers. “No” if the test/demonstration was in controlled or non-operational conditions.“Maybe” otherwise.", + display_type: "enum", + mongo_type: "string", + default: "maybe", + permitted_values: [ + "yes", + "no", + "maybe" + ], + placeholder: "", + weight: 40, + instant_facet: false, + required: false, + notes: "Sometimes, prior to deployment, the user will perform a test or demonstration of the AI system. The involvement of a user (versus a developer, producer, or researcher) increases the likelihood that harm can occur even if the AI system is being tested in controlled environments." + }, + { + field_number: "2.9", + short_name: "Harm Domain", + long_name: "Incident occurred in a domain where we could expect harm to occur?", + short_description: "Incident occurred in a domain where we could likely expect harm to occur?", + long_description: "Using the answers to the 8 domain questions, assess if the incident occurred in a domain where harm could be expected to occur. If you are unclear, input “maybe.”", + display_type: "enum", + mongo_type: "string", + default: "maybe", + permitted_values: [ + "yes", + "no", + "maybe" + ], + placeholder: "", + weight: 40, + instant_facet: false, + required: false, + hide_search: true, + notes: "Reflecting upon the previously answered questions, decide if the reported incident or instance occurred in a domain in which harm could possibly occur. This is not a decision on whether or not harm did occur. Just a reflection on the operating conditions or context of the system." + }, + { + field_number: "3.1", + short_name: "Tangible Harm", + long_name: "Did tangible harm (loss, damage or injury ) occur? ", + short_description: "Did tangible harm (loss, damage or injury ) occur? ", + long_description: "An assessment of whether tangible harm, imminent tangible harm, or non-imminent tangible harm occurred. This assessment does not consider the context of the tangible harm, if an AI was involved, or if there is an identifiable, specific, and harmed entity. It is also not assessing if an intangible harm occurred. It is only asking if tangible harm occurred and what its imminency was.", + display_type: "enum", + mongo_type: "string", + default: "unclear", + placeholder: "", + permitted_values: [ + "tangible harm definitively occurred", + "imminent risk of tangible harm (near miss) did occur", + "non-imminent risk of tangible harm (an issue) occurred", + "no tangible harm, near-miss, or issue", + "unclear" + ], + weight: 40, + instant_facet: false, + required: false, + public: true + }, + { + field_number: "3.2", + short_name: "AI System", + long_name: "Does the incident involve an AI system?", + short_description: "Does the incident involve an AI system?", + long_description: "An assessment of whether or not an AI system was involved. It is sometimes difficult to judge between an AI and an automated system or expert rules system. In these cases select “maybe”", + display_type: "enum", + mongo_type: "string", + default: "maybe", + permitted_values: [ + "yes", + "no", + "maybe" + ], + placeholder: "", + weight: 40, + instant_facet: false, + required: false, + public: true, + notes: "Note, over time more information about the incident may become available, allowing a ‘maybe’ to be changed to a ‘yes’ or ‘no.’" + }, + { + field_number: "3.3", + short_name: "Clear link to technology", + long_name: "Can the technology be directly and clearly linked to the adverse outcome of the incident", + short_description: "Can the technology be directly and clearly linked to the adverse outcome of the incident", + long_description: "An assessment of the technology's involvement in the chain of harm. \"Yes\" indicates that the technology was involved in harm, its behavior can be directly linked to the harm, and the harm may not have occurred if the technology acted differently. \"No\", indicates that the technology's behavior cannot be linked to the harm outcome. \"Maybe\" indicates that the link is unclear.", + hide_search: true, + display_type: "enum", + mongo_type: "string", + default: "maybe", + permitted_values: [ + "yes", + "no", + "maybe" + ], + placeholder: "", + weight: 40, + instant_facet: false, + required: false, + public: true + }, + { + field_number: "3.4", + short_name: "There is a potentially identifiable specific entity that experienced the harm", + long_name: "There is a potentially identifiable specific entity that experienced the harm", + short_description: "A potentially identifiable specific entity that experienced the harm can be characterized or identified.", + long_description: "“Yes” if it is theoretically possible to both specify and identify the entity. Having that information is not required. The information just needs to exist and be potentially discoverable. “No” if there are not any potentially identifiable specific entities or if the harmed entities are a class or subgroup that can only be characterized. ", + display_type: "bool", + mongo_type: "bool", + permitted_values: [], + placeholder: "", + default: "", + weight: 40, + instant_facet: false, + required: false, + public: true, + hide_search: true, + notes: "A potentially identifiable specific entity is an entity that can be described in detail such that the name (Mr. Joe Smith, Acme Inc, etc.) or a unique identifier (e.g. 100 Main Street, Anywhere USA) of the entity could be found. We may not know the name or identifier of the entity from the reports, but it does exist and could be found. For example, the general public is not a potentially identifiable specific entity. However, incarcerated people in the Springfield penitentiary would be specific entities because it would be possible to get a list of all the prisoners in the facility." + }, + { + field_number: "3.5", + short_name: "AI Harm Level", + long_name: "Annotator's AI tangible harm level assessment", + short_description: "An assessment of the AI tangible harm level, which takes into account the CSET definitions of AI tangible harm levels, along with the inputs for annotation fields about the AI, harm, chain of harm, and entity. ", + long_description: "An assessment of the AI tangible harm level, which takes into account the CSET definitions of AI tangible harm levels, along with the inputs for annotation fields about the AI, harm, chain of harm, and entity.", + display_type: "enum", + mongo_type: "string", + default: "unclear", + placeholder: "", + permitted_values: [ + "AI tangible harm event", + "AI tangible harm near-miss", + "AI tangible harm issue", + "none", + "unclear" + ], + weight: 40, + instant_facet: false, + required: false, + public: true, + notes: "Special interest intangible harm is determined in a different field. The determination of a special interest intangible harm is not dependant upon the AI tangible harm level" + }, + { + field_number: "3.6", + short_name: "AI Tangible Harm Level Notes", + long_name: "AI Tangible Harm Level Notes", + short_description: "Notes about the AI tangible harm level assessment", + long_description: "If for 3.5 you select unclear or leave it blank, please provide a brief description of why.\n\n You can also add notes if you want to provide justification for a level", + display_type: "long_string", + mongo_type: "string", + default: "", + placeholder: "Notes about the AI tangible harm level assessment", + permitted_values: [], + weight: 5, + instant_facet: false, + required: false, + public: true, + hide_search: true + }, + { + field_number: "4.1", + short_name: "Impact on Critical Services", + long_name: "Did this impact people's access to critical or public services (health care, social services, voting, transportation, etc)?", + short_description: "Indicates if people’s access to critical public services was impacted.", + long_description: "Did this impact people's access to critical or public services (health care, social services, voting, transportation, etc)?", + display_type: "enum", + mongo_type: "string", + default: "maybe", + permitted_values: [ + "yes", + "no", + "maybe" + ], + placeholder: "", + weight: 40, + instant_facet: false, + required: false, + public: true, + notes: "Public services include healthcare, social services, voting, public transportation, education, and consumer protection.\n\nNote, if ‘yes’ is selected then there was likely a violation of civil liberties and there was a special interest intangible harm." + }, + { + field_number: "4.2", + short_name: "Rights Violation", + long_name: "Was this a violation of human rights, civil liberties, civil rights, or democratic norms?", + short_description: "Indicate if a violation of human rights, civil rights, civil liberties, or democratic norms occurred.", + long_description: "Indicate if a violation of human rights, civil rights, civil liberties, or democratic norms occurred.", + display_type: "enum", + mongo_type: "string", + default: "maybe", + permitted_values: [ + "yes", + "no", + "maybe" + ], + placeholder: "", + weight: 40, + instant_facet: false, + required: false, + public: true, + notes: "It can often be difficult for the typical annotator to differentiate between violations of civil liberties, civil rights, human rights, and democratic norms. For this reason CSET grouped them together.\n\nHuman rights are rights inherent to all human beings, regardless of race, sex, nationality, ethnicity, language, religion, or any other status. They include the right to life and liberty, freedom from slavery and torture, freedom of opinion and expression, and the right to work and education. Civil rights are legal provisions that originate from notions of equality and can be enforced by law. Civil liberties are personal freedoms that are referenced in the Bill of Rights. Democratic norms are traditions, customs, and best practices that support democracy. An example of a democratic norm is accepting election results and facilitating a peaceful transfer of political power." + }, + { + field_number: "4.3", + short_name: "Involving Minor", + long_name: "Was a minor involved in the incident (disproportionally treated or specifically targeted/affected)", + short_description: "Was a minor involved in the incident (disproportionally treated or specifically targeted/affected)", + long_description: "Indicate if a minor was disproportionately targeted or affected", + display_type: "enum", + mongo_type: "string", + default: "maybe", + permitted_values: [ + "yes", + "no", + "maybe" + ], + placeholder: "", + weight: 40, + instant_facet: false, + required: false, + public: true, + notes: "Generally, governments have an interest in establishing heightened protections for minors. These protections are often associated with media content or privacy. For example, if an AI system illegally tracked a minor’s activity online, then answer “yes” to this question. There are instances where an AI system causes indiscriminate harm to a group of people, and it is plausible that some of those people are minors. However, in this case the entire group of people, adults and children alike, shared the distribution of harm equally and therefore the answer to this question would be “no.”" + }, + { + field_number: "4.4", + short_name: "Detrimental Content", + long_name: "Was detrimental content (misinformation, hate speech) involved?", + short_description: "Was detrimental content (misinformation, hate speech) involved?", + long_description: "Detrimental content can include deepfakes, identity misrepresentation, insults, threats of violence, eating disorder or self harm promotion, extremist content, misinformation, sexual abuse material, and scam emails. Detrimental content in itself is often not harmful, however, it can lead to or instigate injury, damage, or loss.", + display_type: "enum", + mongo_type: "string", + default: "maybe", + permitted_values: [ + "yes", + "no", + "maybe" + ], + placeholder: "", + weight: 40, + instant_facet: false, + required: false, + public: true + }, + { + field_number: "4.5", + short_name: "Protected Characteristic", + long_name: "Was a group of people or an individual treated differently based upon a protected characteristic?", + short_description: "Was a group of people treated differently based upon a protected characteristic (e.g. race, ethnicity, creed, immigrant status, color, religion, sex, national origin, age, disability, genetic information)?", + long_description: "Protected characteristics include religion, commercial facilities, geography, age, sex, sexual orientation or gender identity, familial status (e.g., having or not having children) or pregnancy, disability, veteran status, genetic information, financial means, race or creed, Ideology, nation of origin, citizenship, and immigrant status.\n\nAt the federal level in the US, age is a protected characteristic for people over the age of 40. Minors are not considered a protected class. For this reason the CSET annotation taxonomy has a separate field to note if a minor was involved.\n\nOnly mark yes if there is clear evidence discrimination occurred. If there are conflicting accounts, mark unsure. Do not mark that discrimination occurred based on expectation alone.", + display_type: "enum", + mongo_type: "string", + default: "maybe", + permitted_values: [ + "yes", + "no", + "maybe" + ], + placeholder: "", + weight: 40, + instant_facet: false, + required: false, + public: true + }, + { + field_number: "4.6", + short_name: "Harm Distribution Basis", + long_name: "If harms were potentially unevenly distributed among people, on what basis?", + short_description: "Indicates how the harms were potentially distributed.", + long_description: "Multiple can occur.\n\nGenetic information refers to information about a person’s genetic tests or the genetic tests of their relatives. Genetic information can predict the manifestation of a disease or disorder.", + display_type: "multi", + mongo_type: "string", + default: "unclear", + placeholder: "", + permitted_values: [ + "none", + "age", + "disability", + "familial status (e.g., having or not having children) or pregnancy", + "financial means", + "genetic information", + "geography", + "ideology", + "nation of origin, citizenship, immigrant status", + "race", + "religion", + "sex", + "sexual orientation or gender identity", + "veteran status", + "unclear", + "other" + ], + weight: 60, + instant_facet: false, + required: false, + public: true + }, + { + field_number: "4.7", + short_name: "Notes (special interest intangible harm)", + long_name: "Input any notes that may help explain your answers.", + short_description: "Input any notes that may help explain your answers.", + long_description: "Input any notes that may help explain your answers.", + display_type: "long_string", + mongo_type: "string", + default: "", + placeholder: "Notes", + permitted_values: [], + weight: 5, + instant_facet: false, + required: false, + public: true, + hide_search: true + }, + { + field_number: "5.1", + short_name: "Special Interest Intangible Harm", + long_name: "Was there a special interest intangible harm or risk of harm?", + short_description: "An assessment of whether a special interest intangible harm occurred. This assessment does not consider the context of the intangible harm, if an AI was involved, or if there is characterizable class or subgroup of harmed entities. It is also not assessing if an intangible harm occurred. It is only asking if a special interest intangible harm occurred.", + long_description: "An assessment of whether a special interest intangible harm occurred. This assessment does not consider the context of the intangible harm, if an AI was involved, or if there is characterizable class or subgroup of harmed entities. It is also not assessing if an intangible harm occurred. It is only asking if a special interest intangible harm occurred.", + display_type: "enum", + mongo_type: "string", + default: "maybe", + permitted_values: [ + "yes", + "no", + "maybe" + ], + placeholder: "", + weight: 5, + instant_facet: false, + required: false, + public: true + }, + { + field_number: "5.2", + short_name: "AI System", + long_name: "Does the incident involve an AI system?", + short_description: "Does the incident involve an AI system?", + long_description: "An assessment of whether or not an AI system was involved. It is sometimes difficult to judge between an AI and an automated system or expert rules system. In these cases select “maybe”", + display_type: "enum", + mongo_type: "string", + default: "maybe", + permitted_values: [ + "yes", + "no", + "maybe" + ], + placeholder: "", + weight: 40, + instant_facet: false, + required: false, + public: true, + hide_search: true, + notes: "Note, over time more information about the incident may become available, allowing a ‘maybe’ to be changed to a ‘yes’ or ‘no.’" + }, + { + field_number: "5.3", + short_name: "Clear link to Technology", + long_name: "Can the technology be directly and clearly linked to the adverse outcome of the incident?", + short_description: "Can the technology be directly and clearly linked to the adverse outcome of the incident?", + long_description: "An assessment of the technology's involvement in the chain of harm. \"Yes\" indicates that the technology was involved in harm, its behavior can be directly linked to the harm, and the harm may not have occurred if the technology acted differently. \"No\", indicates that the technology's behavior cannot be linked to the harm outcome. \"Maybe\" indicates that the link is unclear.", + display_type: "enum", + mongo_type: "string", + default: "maybe", + permitted_values: [ + "yes", + "no", + "maybe" + ], + placeholder: "", + weight: 40, + instant_facet: false, + required: false, + public: true + }, + { + field_number: "5.4", + short_name: "Harmed Class of Entities", + long_name: "There is a characterizable class or subgroup of entities that experienced the harm", + short_description: "“Yes” if the harmed entity or entities can be characterized. “No” if there are not any characterizable entities.", + long_description: "A characterizable class or subgroup are descriptions of different populations of people. Often they are characteristics by which people qualify for special protection by a law, policy, or similar authority.\n\n Sometimes, groups may be characterized by their exposure to the incident via geographical proximity (e.g., ‘visitors to the park’) or participation in an activity (e.g.,‘Twitter users’).", + display_type: "bool", + mongo_type: "bool", + permitted_values: [], + placeholder: "", + weight: 40, + instant_facet: false, + required: false, + public: true, + hide_search: true, + notes: "" + }, + { + field_number: "5.5", + short_name: "Annotator’s AI special interest intangible harm assessment", + long_name: "The annotator’s assessment of if an AI special interest intangible harm occurred.", + short_description: "The annotator’s assessment of if an AI special interest intangible harm occurred.", + long_description: "AI tangible harm is determined in a different field. The determination of a special interest intangible harm is not dependant upon the AI tangible harm level.", + display_type: "enum", + mongo_type: "string", + default: "maybe", + permitted_values: [ + "yes", + "no", + "maybe" + ], + placeholder: "", + weight: 40, + instant_facet: false, + required: false, + public: true, + notes: "" + }, + { + field_number: "5.6", + short_name: "Notes (AI special interest intangible harm)", + long_name: "If for 5.5 you select unclear or leave it blank, please provide a brief description of why.\n\nYou can also add notes if you want to provide justification for a level.", + short_description: "If for 5.5 you select unclear or leave it blank, please provide a brief description of why.\n\nYou can also add notes if you want to provide justification for a level.", + long_description: "If for 5.5 you select unclear or leave it blank, please provide a brief description of why.\n\nYou can also add notes if you want to provide justification for a level.", + display_type: "long_string", + mongo_type: "string", + default: "", + placeholder: "Notes", + permitted_values: [], + weight: 5, + instant_facet: false, + required: false, + public: true, + hide_search: true + }, + { + field_number: "6.1", + short_name: "Date of Incident Year", + long_name: "The year in which the incident first occurred.", + short_description: "The year in which the incident occurred. If there are multiple harms or occurrences of the incident, list the earliest. If a precise date is unavailable, but the available sources provide a basis for estimating the year, estimate. Otherwise, leave blank.\n\nEnter in the format of YYYY", + long_description: "The year in which the incident occurred. If there are multiple harms or occurrences of the incident, list the earliest. If a precise date is unavailable, but the available sources provide a basis for estimating the year, estimate. Otherwise, leave blank.", + display_type: "string", + mongo_type: "string", + default: "", + placeholder: "YYYY", + permitted_values: [], + weight: 5, + instant_facet: false, + required: false, + public: true + }, + { + field_number: "6.2", + short_name: "Date of Incident Month", + long_name: "The month in which the incident first occurred.", + short_description: "The month in which the incident occurred. If there are multiple harms or occurrences of the incident, list the earliest. If a precise date is unavailable, but the available sources provide a basis for estimating the month, estimate. Otherwise, leave blank.\n\nEnter in the format of MM", + long_description: "The month in which the incident occurred. If there are multiple harms or occurrences of the incident, list the earliest. If a precise date is unavailable, but the available sources provide a basis for estimating the month, estimate. Otherwise, leave blank.", + display_type: "string", + mongo_type: "string", + default: "", + placeholder: "MM", + permitted_values: [], + weight: 5, + instant_facet: false, + required: false, + public: true, + hide_search: true + }, + { + field_number: "6.3", + short_name: "Date of Incident Day", + long_name: "The day on which the first incident occurred.", + short_description: "The day on which the incident occurred. If a precise date is unavailable, leave blank.\n\nEnter in the format of DD", + long_description: "The day on which the incident occurred. If a precise date is unavailable, leave blank.\n\nEnter in the format of DD", + display_type: "string", + mongo_type: "string", + default: "", + placeholder: "DD", + permitted_values: [], + weight: 5, + instant_facet: false, + required: false, + public: true, + hide_search: true + }, + { + field_number: "6.4", + short_name: "Estimated Date", + long_name: "Is the date estimated?", + short_description: "“Yes” if the data was estimated. “No” otherwise.", + long_description: "“Yes” if the data was estimated. “No” otherwise.", + display_type: "bool", + mongo_type: "bool", + permitted_values: [], + placeholder: "", + weight: 5, + instant_facet: false, + required: false, + public: true, + hide_search: true + }, + { + field_number: "6.5", + short_name: "Multiple AI Interaction", + long_name: "Was the AI interacting with another AI?", + short_description: "“Yes” if two or more independently operating AI systems were involved. “No” otherwise.", + long_description: "This happens very rarely but is possible. Examples include two chatbots having a conversation with each other, or two autonomous vehicles in a crash.", + display_type: "enum", + mongo_type: "string", + default: "maybe", + permitted_values: [ + "yes", + "no", + "maybe" + ], + placeholder: "", + weight: 5, + instant_facet: false, + required: false, + public: true + }, + { + field_number: "6.6", + short_name: "Embedded", + long_name: "Is the AI embedded in a physical system or have a physical presence?", + short_description: "“Yes” if the AI is embedded in a physical system. “No” if it is not. “Maybe” if it is unclear.", + long_description: "This question is slightly different from the one in field 2.1.1. That question asks about there being interaction with physical objects–an ability to manipulate or change. A system can be embedded in a physical object and able to interact with the physical environment, e.g. a vacuum robot. A system can be embedded in a physical object and not interact with a physical environment, e.g. a camera system that only records images when the AI detects that dogs are present. AI systems that are accessed through API, web-browser, etc by using a mobile device or computer are not considered to be embedded in hardware systems. They are accessed through hardware.", + display_type: "enum", + mongo_type: "string", + default: "maybe", + permitted_values: [ + "yes", + "no", + "maybe" + ], + placeholder: "", + weight: 5, + instant_facet: false, + required: false, + public: true + }, + { + field_number: "6.7", + short_name: "Location City", + long_name: "If the incident occurred at a specific known location, note the city.", + short_description: "If the incident occurred at a specific known location, note the city.", + long_description: "If the incident occurred at a specific known location, note the city. If there are multiple relevant locations, enter multiple city/state/country values.", + display_type: "string", + mongo_type: "string", + default: "", + placeholder: "City", + permitted_values: [], + weight: 5, + instant_facet: false, + required: false, + public: true, + hide_search: true + }, + { + field_number: "6.8", + short_name: "Location State/Province (two letters)", + long_name: "If the incident occurred at a specific known location, note the state/province.", + short_description: "If the incident occurred at a specific known location, note the state/province.", + long_description: "If the incident occurred at a specific known location, note the state/province. If there are multiple relevant locations, enter multiple city/state/country values.", + display_type: "string", + mongo_type: "string", + default: "", + placeholder: "State or Province", + permitted_values: [], + weight: 5, + instant_facet: false, + required: false, + public: true, + hide_search: true + }, + { + field_number: "6.9", + short_name: "Location Country (two letters)", + long_name: "If the incident occurred at a specific known location, note the country. ", + short_description: "If the incident occurred at a specific known location, note the country. Follow ISO 3166 for the 2-letter country codes.", + long_description: "Follow ISO 3166 for the 2-letter country codes.\n\nIf there are multiple relevant locations, enter multiple city/state/country values.", + display_type: "string", + mongo_type: "string", + default: "", + placeholder: "Country", + permitted_values: [], + weight: 5, + instant_facet: false, + required: false, + public: true + }, + { + field_number: "6.10", + short_name: "Location Region", + long_name: "Location Region", + short_description: "Select the region of the world where the incident occurred. If it occurred in multiple, leave blank.", + long_description: "Use this reference to map countries to regions: https://www.dhs.gov/geographic-regions", + display_type: "enum", + mongo_type: "string", + default: "unclear", + placeholder: "", + permitted_values: [ + "Global", + "Africa", + "Asia", + "Caribbean", + "Central America", + "Europe", + "North America", + "Oceania", + "South America", + "unclear" + ], + weight: 5, + instant_facet: false, + required: false, + public: true + }, + { + field_number: "6.11", + short_name: "Infrastructure Sectors", + long_name: "Which critical infrastructure sectors were affected, if any?", + short_description: "Which critical infrastructure sectors were affected, if any?", + long_description: "Which critical infrastructure sectors were affected, if any?", + display_type: "multi", + mongo_type: "string", + default: "unclear", + placeholder: "", + permitted_values: [ + "chemical", + "commercial facilities", + "communications", + "critical manufacturing", + "dams", + "defense-industrial base", + "emergency services", + "energy", + "financial services", + "food and agriculture", + "government facilities", + "healthcare and public health", + "information technology", + "nuclear ", + "transportation", + "water and wastewater", + "Other", + "unclear" + ], + weight: 5, + instant_facet: false, + required: false, + public: true + }, + { + field_number: "6.12", + short_name: "Operating Conditions", + long_name: "A record of any abnormal or atypical operational conditions that occurred.", + short_description: "A record of any abnormal or atypical operational conditions that occurred.", + long_description: "A record of any abnormal or atypical operational conditions that occurred. This field is most often blank.", + display_type: "list", + mongo_type: "array", + default: "", + placeholder: "e.g. raining; night; low visibility", + permitted_values: [], + weight: 5, + instant_facet: false, + required: false, + public: true + }, + { + field_number: "6.13", + short_name: "Notes (Environmental and Temporal Characteristics)", + long_name: "Notes (Environmental and Temporal Characteristics)", + short_description: "Input any notes that may help explain your answers.", + long_description: "Input any notes that may help explain your answers.", + display_type: "long_string", + mongo_type: "string", + default: "", + placeholder: "Notes", + permitted_values: [], + weight: 5, + instant_facet: false, + required: false, + public: true, + hide_search: true + }, + { + field_number: "7", + short_name: "Entities", + long_name: "Characterizing Entities and the Harm", + short_description: "Characterizing Entities and the Harm", + long_description: "Characterizing Entities and the Harm", + display_type: "object-list", + mongo_type: "array", + hide_search: true, + subfields: [ + { + field_number: "7.1", + short_name: "Entity", + long_name: "A short 1 to 2 word description of the entity.", + short_description: "A short 1 to 2 word description of the entity. When possible use a proper name for the entity, making it a Named Entity.", + long_description: "A short 1 to 2 word description of the entity. When possible use a proper name for the entity, making it a Named Entity.\n\nAnnotate information for each entity involved in the report. Try to capture every entity directly linked to the harm. Think about the entity that experienced the harm, all of the entities between them and the AI, and then all of the entities involved in producing and deploying the AI.\n\nEmployees representing a company in a media or public relations capacity should not be included as an entity.", + display_type: "string", + mongo_type: "string", + complete_from: { + entities: true + }, + default: "", + placeholder: "", + permitted_values: [], + weight: 5, + instant_facet: false, + required: false, + public: true + }, + { + field_number: "7.2", + short_name: "Named Entity", + long_name: "Named Entity Indicator", + short_description: "Indicates if the entity is a Named Entity.", + long_description: "Indicates if the entity is a Named Entity. “Yes” if the entity is a named entity. “No” otherwise.", + display_type: "bool", + mongo_type: "bool", + permitted_values: [], + placeholder: "", + weight: 5, + instant_facet: false, + required: false, + public: true + }, + { + field_number: "7.3", + short_name: "Entity type", + long_name: "Indicates the type of entity", + short_description: "Indicates the type of entity", + long_description: "Indicates the type of entity. If multiple selections could characterize the entity, select the primary function of the entity.", + display_type: "enum", + mongo_type: "string", + default: "unclear", + placeholder: "Entity Type", + permitted_values: [ + "individual", + "group of individuals", + "for-profit organization", + "non-profit organization", + "government entity", + "privately owned space", + "public space", + "infrastructure", + "social or political system", + "product", + "other", + "unclear" + ], + weight: 5, + instant_facet: false, + required: false, + public: true, + hide_search: true + }, + { + field_number: "7.4", + short_name: "Entity Relationship to the AI", + long_name: "Entity Relationship to the AI", + short_description: "Indicates the entity’s relationship to the AI.", + long_description: "Indicates the entity’s relationship to the AI. Note, the smallest possible chain of harm has just two elements; an AI and an entity experiencing harm, near-miss, or issue.", + display_type: "list", + mongo_type: "array", + default: "", + placeholder: "", + permitted_values: [ + "developer", + "deployer", + "government oversight", + "user", + "AI", + "geographic area of use", + "researcher", + "product containing AI", + "watchdog" + ], + weight: 5, + instant_facet: false, + required: false, + public: true + }, + { + field_number: "7.5", + short_name: "Harm Category Experienced", + long_name: "Was an AI special interest intangible harm, tangible harm event, near-miss, or issue experienced by this entity", + short_description: "Was an AI special interest intangible harm, tangible harm event, near-miss, or issue experienced by this entity", + long_description: "Was an AI special interest intangible harm, tangible harm event, near-miss, or issue experienced by this entity. For each recorded entity, indicate the harm category that they experienced. Because recorded entities have a variety of roles in the AI incident, not every recorded entity will experience harm.", + display_type: "enum", + mongo_type: "string", + default: "unclear", + placeholder: "", + permitted_values: [ + "AI special interest intangible harm", + "AI tangible harm event", + "AI tangible harm near-miss", + "AI tangible harm issue", + "Other harm not meeting CSET definitions", + "not applicable", + "unclear" + ], + weight: 5, + instant_facet: false, + required: false, + public: true + }, + { + field_number: "7.6", + short_name: "Harm Type Experienced", + long_name: "Type of harm experienced by entity ", + short_description: "Indicates the type of harm experienced by the harmed entity", + long_description: "Indicates the type of harm experienced by the harmed entity. Only entities experiencing harm should have an assigned type. If the entity did not experience the harm, ‘not applicable’ should be selected.", + display_type: "enum", + mongo_type: "string", + default: "not applicable", + placeholder: "", + permitted_values: [ + "physical health/safety", + "financial loss", + "physical property", + "intangible property", + "infrastructure", + "natural environment", + "social or political systems", + "violation of human rights, civil liberties, civil rights, or democratic norms", + "detrimental content", + "disproportionate treatment based upon a protected characteristic", + "other tangible harm", + "other intangible harm", + "not applicable" + ], + weight: 5, + instant_facet: false, + required: false, + public: true + }, + { + field_number: "7.7", + short_name: "Notes (Characterizing Entities and the Harm)", + long_name: "Notes (Characterizing Entities and the Harm)", + short_description: "Input any notes that may help explain your answers.", + long_description: "Input any notes that may help explain your answers.", + display_type: "long_string", + mongo_type: "string", + default: "", + placeholder: "Notes", + permitted_values: [], + weight: 5, + instant_facet: false, + required: false, + public: true, + hide_search: true + } + ], + default: "[]", + placeholder: "", + permitted_values: [], + weight: 5, + instant_facet: false, + required: false, + public: true + }, + { + field_number: "8.1", + short_name: "Lives Lost", + long_name: "How many human lives were lost?", + short_description: "Indicates the number of deaths reported", + long_description: "This field cannot be greater than zero if the harm is anything besides ‘Physical health/safety.’ ", + display_type: "int", + mongo_type: "int", + default: "", + placeholder: "", + permitted_values: [], + weight: 5, + instant_facet: false, + required: false, + public: true + }, + { + field_number: "8.2", + short_name: "Injuries", + long_name: "How many humans were injured?", + short_description: "Indicate the number of injuries reported.", + long_description: "This field cannot be greater than zero if the harm is anything besides 'Physical health/safety'.\n\nAll reported injuries should count, regardless of their severity level. If a person lost their limb and another person scraped their elbow, both cases would be considered injuries. Do not include the number of deaths in this count.", + display_type: "int", + mongo_type: "int", + default: "", + placeholder: "", + permitted_values: [], + weight: 5, + instant_facet: false, + required: false, + public: true + }, + { + field_number: "8.3", + short_name: "Estimated Harm Quantities", + long_name: "Are any quantities estimated?", + short_description: "Indicates if the amount was estimated.", + long_description: "Indicates if the amount was estimated.", + display_type: "bool", + mongo_type: "bool", + permitted_values: [], + placeholder: "", + weight: 5, + instant_facet: false, + required: false, + hide_search: true, + public: true + }, + { + field_number: "8.4", + short_name: "Notes ( Tangible Harm Quantities Information)", + long_name: "Notes ( Tangible Harm Quantities Information)", + short_description: "Input any notes that may help explain your answers.", + long_description: "Input any notes that may help explain your answers.", + display_type: "long_string", + mongo_type: "string", + default: "", + placeholder: "Notes", + permitted_values: [], + weight: 5, + instant_facet: false, + required: false, + public: true, + hide_search: true + }, + { + field_number: "9.1", + short_name: "AI System Description", + long_name: "Description of the AI system involved", + short_description: "A description of the AI system (when possible)", + long_description: "Describe the AI system in as much detail as the reports will allow.\n\nA high level description of the AI system is sufficient, but if more technical details about the AI system are available, include them in the description as well.", + display_type: "long_string", + mongo_type: "string", + default: "", + placeholder: "Description of the AI system involved", + permitted_values: [], + weight: 5, + instant_facet: false, + required: false, + public: true, + hide_search: true + }, + { + field_number: "9.2", + short_name: "Data Inputs", + long_name: "Description of data inputs to the AI system", + short_description: "A list of the types of data inputs for the AI system.", + long_description: "This is a freeform field that can have any value. There could be multiple entries for this field.\n\nCommon ones include\n\n- still images\n- video\n- text\n- speech\n- Personally Identifiable Information\n- structured data\n- other\n- unclear\n\nStill images are static images. Video images consist of moving images. Text and speech data are considered an important category of unstructured data. They consist of written and spoken words that are not in a tabular format. Personally identifiable information is data that can uniquely identify an individual and may contain sensitive information. Structured data is often in a tabular, machine readable format and can typically be used by an AI system without much preprocessing.\n\nAvoid using ‘unstructured data’ data in this field. Instead specify the type of unstructured data; text, images, audio files, etc. It is ok to use ‘structured data’ in this field.\n\nRecord what the media report explicitly states. If the report does not explicitly state an input modality but it is likely that a particular kind of input contributed to the harm or near harm, record that input. If you are still unsure, do not record anything.", + display_type: "list", + mongo_type: "array", + default: "", + placeholder: "", + permitted_values: [], + weight: 5, + instant_facet: false, + required: false, + public: true + }, + { + field_number: "9.3", + short_name: "Sector of Deployment", + long_name: "Indicates the sector in which the AI system is deployed", + short_description: "Indicate the sector in which the AI system is deployed", + long_description: "Indicate the sector in which the AI system is deployed\n\nThere could be multiple entries for this field.", + display_type: "multi", + mongo_type: "array", + default: "", + placeholder: "", + permitted_values: [ + "agriculture, forestry and fishing", + "mining and quarrying", + "manufacturing", + "electricity, gas, steam and air conditioning supply", + "water supply", + "construction", + "wholesale and retail trade", + "transportation and storage", + "accommodation and food service activities", + "information and communication", + "financial and insurance activities", + "real estate activities", + "professional, scientific and technical activities", + "administrative and support service activities", + "public administration", + "defense", + "law enforcement", + "Education", + "human health and social work activities", + "Arts, entertainment and recreation", + "other service activities", + "activities of households as employers", + "activities of extraterritorial organizations and bodies", + "other", + "unclear" + ], + weight: 60, + instant_facet: false, + required: false, + public: true + }, + { + field_number: "9.4", + short_name: "Public Sector Deployment", + long_name: "Indicates whether the AI system is deployed in the public sector", + short_description: "Indicate whether the AI system is deployed in the public sector", + long_description: "Indicate whether the AI system is deployed in the public sector. The public sector is the part of the economy that is controlled and operated by the government.", + display_type: "enum", + mongo_type: "string", + default: "maybe", + permitted_values: [ + "yes", + "no", + "maybe" + ], + placeholder: "", + weight: 40, + instant_facet: false, + required: false, + public: true + }, + { + field_number: "9.5", + short_name: "Autonomy Level", + long_name: "Autonomy Level", + short_description: "Autonomy1: The system operates independently without simultaneous human oversight, interaction, or intervention.\n\nAutonomy2: The system operates independently but with human oversight, where a human can observe and override the system’s decisions in real time.\n\nAutonomy3: The system does not independently make decisions but instead provides information to a human who actively chooses to proceed with the AI’s information.", + long_description: "Autonomy1: The system operates independently without simultaneous human oversight, interaction, or intervention.\n\nAutonomy2: The system operates independently but with human oversight, where a human can observe and override the system’s decisions in real time.\n\nAutonomy3: The system does not independently make decisions but instead provides information to a human who actively chooses to proceed with the AI’s information.", + display_type: "enum", + mongo_type: "string", + default: "", + placeholder: "", + permitted_values: [ + "Autonomy1", + "Autonomy2", + "Autonomy3", + "unclear" + ], + weight: 40, + instant_facet: false, + required: false, + public: true + }, + { + field_number: "9.8", + short_name: "Notes (Information about AI System)", + long_name: "Notes (Information about AI System)", + short_description: "Input any notes that may help explain your answers.", + long_description: "Input any notes that may help explain your answers.", + display_type: "long_string", + mongo_type: "string", + default: "", + placeholder: "Notes", + permitted_values: [], + weight: 5, + instant_facet: false, + required: false, + public: true, + hide_search: true + }, + { + field_number: "10.1", + short_name: "Intentional Harm", + long_name: "Was the AI intentionally developed or deployed to perform the harm?", + short_description: "Was the AI intentionally developed or deployed to perform the harm?\n\nIf yes, did the AI’s behavior result in unintended or intended harm?", + long_description: "Indicates if the system was designed to do harm. If it was designed to perform harm, the field will indicate if the AI system did or did not create unintended harm–i.e. was the reported harm the harm that AI was expected to perform or a different unexpected harm? ", + display_type: "enum", + mongo_type: "string", + default: "unclear", + placeholder: "", + permitted_values: [ + "Yes. Intentionally designed to perform harm and did create intended harm", + "Yes. Intentionally designed to perform harm but created an unintended harm (a different harm may have occurred)", + "No. Not intentionally designed to perform harm", + "unclear" + ], + weight: 40, + instant_facet: false, + required: false, + public: true, + notes: "Tracking and analyzing harm from AI systems designed to do harm is valuable and worthwhile. However, analysts may want to separately analyze harm from AI systems that were or were not designed to produce the observed harm." + }, + { + field_number: "10.2", + short_name: "Physical System Type", + long_name: "Into what type of physical system was the AI integrated, if any?", + short_description: "Describe the type of physical system that the AI was integrated into.", + long_description: "Describe the type of physical system that the AI was integrated into. ", + display_type: "string", + mongo_type: "string", + default: "", + placeholder: "Physical System Type (e.g. trash sorting robot)", + permitted_values: [], + weight: 5, + instant_facet: false, + required: false, + public: true + }, + { + field_number: "10.3", + short_name: "AI Task", + long_name: "AI task or core application area", + short_description: "Describe the AI’s application.", + long_description: "Describe the AI’s application.\n\nIt is likely that the annotator will not have enough information to complete this field. If this occurs, enter unclear.\n\nThis is a freeform field. Some possible entries are\n\n- unclear\n- human language technologies\n- computer vision\n- robotics\n- automation and/or optimization\n- other\n\nThe application area of an AI is the high level task that the AI is intended to perform. It does not describe the technical methods by which the AI performs the task. Considering what an AI’s technical methods enable it to do is another way of arriving at what an AI’s application is. \n\nIt is possible for multiple application areas to be involved. When possible pick the principle or domain area, but it is ok to select multiple areas.", + display_type: "list", + mongo_type: "array", + default: "", + placeholder: "", + permitted_values: [], + weight: 5, + instant_facet: false, + required: false, + public: true + }, + { + field_number: "10.4", + short_name: "AI tools and methods", + long_name: "AI tools and methods", + short_description: "Describe the tools and methods that enable the AI’s application.", + long_description: "Describe the tools and methods that enable the AI’s application.\n\nIt is likely that the annotator will not have enough information to complete this field. If this occurs, enter unclear\n\nThis is a freeform field. Some possible entries are\n\n- unclear\n- reinforcement learning\n- neural networks\n- decision trees\n- bias mitigation\n- optimization\n- classifier\n- NLP/text analytics\n- continuous learning\n- unsupervised learning\n- supervised learning\n- clustering\n- prediction\n- rules\n- random forest\n\nAI tools and methods are the technical building blocks that enable the AI’s application.", + display_type: "list", + mongo_type: "array", + default: "unclear", + placeholder: "", + permitted_values: [], + weight: 40, + instant_facet: false, + required: false, + public: true + }, + { + field_number: "10.5", + short_name: "Notes (AI Functionality and Techniques)", + long_name: "Notes (AI Functionality and Techniques)", + short_description: "Input any notes that may help explain your answers.", + long_description: "Input any notes that may help explain your answers.", + display_type: "long_string", + mongo_type: "string", + default: "", + placeholder: "Notes", + permitted_values: [], + weight: 5, + instant_facet: false, + required: false, + public: true, + hide_search: true + } + ], + + // TODO: created_at is missing from Atlas schema + // created_at: new Date("1722269934190") + }, +] + +export default items; \ No newline at end of file diff --git a/site/gatsby-site/playwright/utils.ts b/site/gatsby-site/playwright/utils.ts index 1174f5394b..e67bfc335f 100644 --- a/site/gatsby-site/playwright/utils.ts +++ b/site/gatsby-site/playwright/utils.ts @@ -5,6 +5,7 @@ import config from './config'; import assert from 'node:assert'; import fs from 'fs'; import path from 'path'; +import * as memoryMongo from './memory-mongo'; declare module '@playwright/test' { interface Request { @@ -17,7 +18,7 @@ export type Options = { defaultItem: string }; type TestFixtures = { skipOnEmptyEnvironment: () => Promise, runOnlyOnEmptyEnvironment: () => Promise, - login: (username: string, password: string, options?: { skipSession?: boolean }) => Promise, + login: (username: string, password: string, options?: { customData?: Record }) => Promise, }; const getUserIdFromLocalStorage = async (page: Page) => { @@ -53,9 +54,10 @@ export const test = base.extend({ login: async ({ page }, use, testInfo) => { + // TODO: this should be removed since we pass the username and password as arguments testInfo.skip(!config.E2E_ADMIN_USERNAME || !config.E2E_ADMIN_PASSWORD, 'E2E_ADMIN_USERNAME or E2E_ADMIN_PASSWORD not set'); - await use(async (email, password) => { + await use(async (email, password, { customData } = {}) => { await page.context().clearCookies(); @@ -63,6 +65,24 @@ export const test = base.extend({ const userId = await getUserIdFromLocalStorage(page); + if (customData) { + + await page.evaluate(({ customData }) => { + + localStorage.setItem('__CUSTOM_DATA_MOCK', JSON.stringify(customData)); + + }, { customData }); + + // upsert user with custom data + await memoryMongo.execute(async (client) => { + + const db = client.db('customData'); + const collection = db.collection('users'); + + await collection.updateOne({ userId }, { $set: customData }, { upsert: true }); + }); + } + return userId!; // to be able to restore session state, we'll need to refactor when we perform the login call, but that's for another PR @@ -233,4 +253,4 @@ export async function fillAutoComplete(page: Page, selector: string, sequence: s await page.locator(selector).pressSequentially(sequence, { delay: 500 }); await page.getByText(target).click({ timeout: 1000 }); }).toPass(); -} \ No newline at end of file +} diff --git a/site/gatsby-site/server/fields/classifications.ts b/site/gatsby-site/server/fields/classifications.ts new file mode 100644 index 0000000000..90276119bd --- /dev/null +++ b/site/gatsby-site/server/fields/classifications.ts @@ -0,0 +1,27 @@ +import { GraphQLFieldConfigMap } from "graphql"; +import { allow } from "graphql-shield"; +import { generateMutationFields, generateQueryFields } from "../utils"; +import { ClassificationType } from "../types/classification"; +import { isRole } from "../rules"; + + +export const queryFields: GraphQLFieldConfigMap = { + + ...generateQueryFields({ collectionName: 'classifications', Type: ClassificationType }) +} + + +export const mutationFields: GraphQLFieldConfigMap = { + + ...generateMutationFields({ collectionName: 'classifications', Type: ClassificationType, generateFields: ['upsertOne'] }), +} + +export const permissions = { + Query: { + classification: allow, + classifications: allow, + }, + Mutation: { + upsertOneClassification: isRole('incident_editor'), + } +} \ No newline at end of file diff --git a/site/gatsby-site/server/fields/taxa.ts b/site/gatsby-site/server/fields/taxa.ts new file mode 100644 index 0000000000..c06297dec7 --- /dev/null +++ b/site/gatsby-site/server/fields/taxa.ts @@ -0,0 +1,18 @@ +import { GraphQLFieldConfigMap } from "graphql"; +import { allow } from "graphql-shield"; +import { generateQueryFields } from "../utils"; +import { TaxaType } from "../types/taxa"; + + +export const queryFields: GraphQLFieldConfigMap = { + + ...generateQueryFields({ collectionName: 'taxa', Type: TaxaType }) +} + + +export const permissions = { + Query: { + taxa: allow, + taxas: allow, + } +} \ No newline at end of file diff --git a/site/gatsby-site/server/generated/gql.ts b/site/gatsby-site/server/generated/gql.ts index 7f76288f54..ff87243211 100644 --- a/site/gatsby-site/server/generated/gql.ts +++ b/site/gatsby-site/server/generated/gql.ts @@ -13,8 +13,8 @@ import { TypedDocumentNode as DocumentNode } from '@graphql-typed-document-node/ * Therefore it is highly recommended to use the babel or swc plugin for production. */ const documents = { - "\n query FindClassifications($query: ClassificationQueryInput) {\n classifications(query: $query) {\n _id\n incidents {\n incident_id\n }\n reports {\n report_number\n }\n notes\n namespace\n attributes {\n short_name\n value_json\n }\n publish\n }\n }\n": types.FindClassificationsDocument, - "\n mutation UpsertClassification(\n $query: ClassificationQueryInput\n $data: ClassificationInsertInput!\n ) {\n upsertOneClassification(query: $query, data: $data) {\n _id\n incidents {\n incident_id\n }\n reports {\n report_number\n }\n notes\n namespace\n attributes {\n short_name\n value_json\n }\n publish\n }\n }\n": types.UpsertClassificationDocument, + "\n query FindClassifications($filter: ClassificationFilterType) {\n classifications(filter: $filter) {\n _id\n incidents {\n incident_id\n }\n reports {\n report_number\n }\n notes\n namespace\n attributes {\n short_name\n value_json\n }\n publish\n }\n }\n": types.FindClassificationsDocument, + "\n mutation UpsertClassification(\n $filter: ClassificationFilterType!\n $update: ClassificationInsertType!\n ) {\n upsertOneClassification(filter: $filter, update: $update) {\n _id\n incidents {\n incident_id\n }\n reports {\n report_number\n }\n notes\n namespace\n attributes {\n short_name\n value_json\n }\n publish\n }\n }\n": types.UpsertClassificationDocument, "\n mutation InsertDuplicate($duplicate: DuplicateInsertInput!) {\n insertOneDuplicate(data: $duplicate) {\n duplicate_incident_number\n true_incident_number\n }\n }\n": types.InsertDuplicateDocument, "\n mutation UpsertEntity($filter: EntityFilterType!, $update: EntityInsertType!) {\n upsertOneEntity(filter: $filter, update: $update) {\n entity_id\n name\n }\n }\n": types.UpsertEntityDocument, "\n query FindEntities {\n entities {\n entity_id\n name\n }\n }\n": types.FindEntitiesDocument, @@ -88,11 +88,11 @@ export function gql(source: string): unknown; /** * The gql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. */ -export function gql(source: "\n query FindClassifications($query: ClassificationQueryInput) {\n classifications(query: $query) {\n _id\n incidents {\n incident_id\n }\n reports {\n report_number\n }\n notes\n namespace\n attributes {\n short_name\n value_json\n }\n publish\n }\n }\n"): (typeof documents)["\n query FindClassifications($query: ClassificationQueryInput) {\n classifications(query: $query) {\n _id\n incidents {\n incident_id\n }\n reports {\n report_number\n }\n notes\n namespace\n attributes {\n short_name\n value_json\n }\n publish\n }\n }\n"]; +export function gql(source: "\n query FindClassifications($filter: ClassificationFilterType) {\n classifications(filter: $filter) {\n _id\n incidents {\n incident_id\n }\n reports {\n report_number\n }\n notes\n namespace\n attributes {\n short_name\n value_json\n }\n publish\n }\n }\n"): (typeof documents)["\n query FindClassifications($filter: ClassificationFilterType) {\n classifications(filter: $filter) {\n _id\n incidents {\n incident_id\n }\n reports {\n report_number\n }\n notes\n namespace\n attributes {\n short_name\n value_json\n }\n publish\n }\n }\n"]; /** * The gql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. */ -export function gql(source: "\n mutation UpsertClassification(\n $query: ClassificationQueryInput\n $data: ClassificationInsertInput!\n ) {\n upsertOneClassification(query: $query, data: $data) {\n _id\n incidents {\n incident_id\n }\n reports {\n report_number\n }\n notes\n namespace\n attributes {\n short_name\n value_json\n }\n publish\n }\n }\n"): (typeof documents)["\n mutation UpsertClassification(\n $query: ClassificationQueryInput\n $data: ClassificationInsertInput!\n ) {\n upsertOneClassification(query: $query, data: $data) {\n _id\n incidents {\n incident_id\n }\n reports {\n report_number\n }\n notes\n namespace\n attributes {\n short_name\n value_json\n }\n publish\n }\n }\n"]; +export function gql(source: "\n mutation UpsertClassification(\n $filter: ClassificationFilterType!\n $update: ClassificationInsertType!\n ) {\n upsertOneClassification(filter: $filter, update: $update) {\n _id\n incidents {\n incident_id\n }\n reports {\n report_number\n }\n notes\n namespace\n attributes {\n short_name\n value_json\n }\n publish\n }\n }\n"): (typeof documents)["\n mutation UpsertClassification(\n $filter: ClassificationFilterType!\n $update: ClassificationInsertType!\n ) {\n upsertOneClassification(filter: $filter, update: $update) {\n _id\n incidents {\n incident_id\n }\n reports {\n report_number\n }\n notes\n namespace\n attributes {\n short_name\n value_json\n }\n publish\n }\n }\n"]; /** * The gql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. */ diff --git a/site/gatsby-site/server/generated/graphql.ts b/site/gatsby-site/server/generated/graphql.ts index ed8c28397c..e665091a4c 100644 --- a/site/gatsby-site/server/generated/graphql.ts +++ b/site/gatsby-site/server/generated/graphql.ts @@ -24,6 +24,23 @@ export type AppUser = { email?: Maybe; }; +export type Attribute = { + __typename?: 'Attribute'; + short_name?: Maybe; + value_json?: Maybe; +}; + +export type AttributeInsertType = { + short_name?: InputMaybe; + value_json?: InputMaybe; +}; + +export type AttributeObjectFilterType = { + opr?: InputMaybe; + short_name?: InputMaybe; + value_json?: InputMaybe; +}; + /** Filter type for Boolean scalar */ export type BooleanFilter = { /** $all */ @@ -791,12 +808,12 @@ export type ChecklistUpdateInput = { export type Classification = { __typename?: 'Classification'; _id?: Maybe; - attributes?: Maybe>>; - incidents: Array>; + attributes?: Maybe>>; + incidents?: Maybe>>; namespace: Scalars['String']['output']; notes?: Maybe; publish?: Maybe; - reports: Array>; + reports?: Maybe>>; }; export type ClassificationAttribute = { @@ -840,71 +857,35 @@ export type ClassificationAttributeUpdateInput = { value_json_unset?: InputMaybe; }; -export type ClassificationIncidentsRelationInput = { - create?: InputMaybe>>; - link?: InputMaybe>>; +export type ClassificationFilterType = { + AND?: InputMaybe>>; + NOR?: InputMaybe>>; + OR?: InputMaybe>>; + _id?: InputMaybe; + attributes?: InputMaybe; + incidents?: InputMaybe; + namespace?: InputMaybe; + notes?: InputMaybe; + publish?: InputMaybe; + reports?: InputMaybe; }; -export type ClassificationInsertInput = { - _id?: InputMaybe; - attributes?: InputMaybe>>; - incidents: ClassificationIncidentsRelationInput; - namespace: Scalars['String']['input']; - notes?: InputMaybe; - publish?: InputMaybe; - reports: ClassificationReportsRelationInput; +export type ClassificationIncidentsRelationInput = { + link: Array>; }; -export type ClassificationQueryInput = { - AND?: InputMaybe>; - OR?: InputMaybe>; +export type ClassificationInsertType = { _id?: InputMaybe; - _id_exists?: InputMaybe; - _id_gt?: InputMaybe; - _id_gte?: InputMaybe; - _id_in?: InputMaybe>>; - _id_lt?: InputMaybe; - _id_lte?: InputMaybe; - _id_ne?: InputMaybe; - _id_nin?: InputMaybe>>; - attributes?: InputMaybe>>; - attributes_exists?: InputMaybe; - attributes_in?: InputMaybe>>; - attributes_nin?: InputMaybe>>; - incidents?: InputMaybe>>; - incidents_exists?: InputMaybe; - incidents_in?: InputMaybe>>; - incidents_nin?: InputMaybe>>; - namespace?: InputMaybe; - namespace_exists?: InputMaybe; - namespace_gt?: InputMaybe; - namespace_gte?: InputMaybe; - namespace_in?: InputMaybe>>; - namespace_lt?: InputMaybe; - namespace_lte?: InputMaybe; - namespace_ne?: InputMaybe; - namespace_nin?: InputMaybe>>; + attributes?: InputMaybe>>; + incidents?: InputMaybe; + namespace: Scalars['String']['input']; notes?: InputMaybe; - notes_exists?: InputMaybe; - notes_gt?: InputMaybe; - notes_gte?: InputMaybe; - notes_in?: InputMaybe>>; - notes_lt?: InputMaybe; - notes_lte?: InputMaybe; - notes_ne?: InputMaybe; - notes_nin?: InputMaybe>>; publish?: InputMaybe; - publish_exists?: InputMaybe; - publish_ne?: InputMaybe; - reports?: InputMaybe>>; - reports_exists?: InputMaybe; - reports_in?: InputMaybe>>; - reports_nin?: InputMaybe>>; + reports?: InputMaybe; }; export type ClassificationReportsRelationInput = { - create?: InputMaybe>>; - link?: InputMaybe>>; + link: Array>; }; export enum ClassificationSortByInput { @@ -916,21 +897,23 @@ export enum ClassificationSortByInput { IdDesc = '_ID_DESC' } -export type ClassificationUpdateInput = { - _id?: InputMaybe; - _id_unset?: InputMaybe; - attributes?: InputMaybe>>; - attributes_unset?: InputMaybe; - incidents?: InputMaybe; - incidents_unset?: InputMaybe; - namespace?: InputMaybe; - namespace_unset?: InputMaybe; - notes?: InputMaybe; - notes_unset?: InputMaybe; - publish?: InputMaybe; - publish_unset?: InputMaybe; - reports?: InputMaybe; - reports_unset?: InputMaybe; +export type ClassificationSortType = { + _id?: InputMaybe; + namespace?: InputMaybe; + notes?: InputMaybe; + publish?: InputMaybe; +}; + +export type CompleteFrom = { + __typename?: 'CompleteFrom'; + all?: Maybe>>; + current?: Maybe>>; +}; + +export type CompleteFromObjectFilterType = { + all?: InputMaybe; + current?: InputMaybe; + opr?: InputMaybe; }; export type CreateDefaultAdminUserInput = { @@ -938,8 +921,10 @@ export type CreateDefaultAdminUserInput = { password?: InputMaybe; }; +/** Input type for creating a variant, including incident ID and variant details. */ export type CreateVariantInput = { - incidentId?: InputMaybe; + /** The unique identifier for the incident. */ + incidentId: Scalars['Int']['input']; /** Details about the variant. */ variant?: InputMaybe; }; @@ -1028,6 +1013,18 @@ export type DeleteManyPayload = { deletedCount: Scalars['Int']['output']; }; +export type DummyFields = { + __typename?: 'DummyFields'; + field_number?: Maybe; + short_name?: Maybe; +}; + +export type DummyFieldsObjectFilterType = { + field_number?: InputMaybe; + opr?: InputMaybe; + short_name?: InputMaybe; +}; + export type Duplicate = { __typename?: 'Duplicate'; _id?: Maybe; @@ -1140,14 +1137,6 @@ export type EntityFilterType = { name?: InputMaybe; }; -export type EntityInsertInput = { - _id?: InputMaybe; - created_at?: InputMaybe; - date_modified?: InputMaybe; - entity_id: Scalars['String']['input']; - name: Scalars['String']['input']; -}; - export type EntityInsertType = { _id?: InputMaybe; created_at?: InputMaybe; @@ -1155,56 +1144,6 @@ export type EntityInsertType = { name: Scalars['String']['input']; }; -export type EntityQueryInput = { - AND?: InputMaybe>; - OR?: InputMaybe>; - _id?: InputMaybe; - _id_exists?: InputMaybe; - _id_gt?: InputMaybe; - _id_gte?: InputMaybe; - _id_in?: InputMaybe>>; - _id_lt?: InputMaybe; - _id_lte?: InputMaybe; - _id_ne?: InputMaybe; - _id_nin?: InputMaybe>>; - created_at?: InputMaybe; - created_at_exists?: InputMaybe; - created_at_gt?: InputMaybe; - created_at_gte?: InputMaybe; - created_at_in?: InputMaybe>>; - created_at_lt?: InputMaybe; - created_at_lte?: InputMaybe; - created_at_ne?: InputMaybe; - created_at_nin?: InputMaybe>>; - date_modified?: InputMaybe; - date_modified_exists?: InputMaybe; - date_modified_gt?: InputMaybe; - date_modified_gte?: InputMaybe; - date_modified_in?: InputMaybe>>; - date_modified_lt?: InputMaybe; - date_modified_lte?: InputMaybe; - date_modified_ne?: InputMaybe; - date_modified_nin?: InputMaybe>>; - entity_id?: InputMaybe; - entity_id_exists?: InputMaybe; - entity_id_gt?: InputMaybe; - entity_id_gte?: InputMaybe; - entity_id_in?: InputMaybe>>; - entity_id_lt?: InputMaybe; - entity_id_lte?: InputMaybe; - entity_id_ne?: InputMaybe; - entity_id_nin?: InputMaybe>>; - name?: InputMaybe; - name_exists?: InputMaybe; - name_gt?: InputMaybe; - name_gte?: InputMaybe; - name_in?: InputMaybe>>; - name_lt?: InputMaybe; - name_lte?: InputMaybe; - name_ne?: InputMaybe; - name_nin?: InputMaybe>>; -}; - export type EntitySetType = { _id?: InputMaybe; created_at?: InputMaybe; @@ -1232,23 +1171,56 @@ export type EntitySortType = { name?: InputMaybe; }; -export type EntityUpdateInput = { - _id?: InputMaybe; - _id_unset?: InputMaybe; - created_at?: InputMaybe; - created_at_unset?: InputMaybe; - date_modified?: InputMaybe; - date_modified_unset?: InputMaybe; - entity_id?: InputMaybe; - entity_id_unset?: InputMaybe; - name?: InputMaybe; - name_unset?: InputMaybe; -}; - export type EntityUpdateType = { set?: InputMaybe; }; +export type FieldList = { + __typename?: 'FieldList'; + complete_from?: Maybe; + default?: Maybe; + display_type?: Maybe; + field_number?: Maybe; + hide_search?: Maybe; + instant_facet?: Maybe; + item_fields?: Maybe; + long_description?: Maybe; + long_name?: Maybe; + mongo_type?: Maybe; + notes?: Maybe; + permitted_values?: Maybe>>; + placeholder?: Maybe; + public?: Maybe; + required?: Maybe; + short_description?: Maybe; + short_name?: Maybe; + subfields?: Maybe>>; + weight?: Maybe; +}; + +export type FieldListObjectFilterType = { + complete_from?: InputMaybe; + default?: InputMaybe; + display_type?: InputMaybe; + field_number?: InputMaybe; + hide_search?: InputMaybe; + instant_facet?: InputMaybe; + item_fields?: InputMaybe; + long_description?: InputMaybe; + long_name?: InputMaybe; + mongo_type?: InputMaybe; + notes?: InputMaybe; + opr?: InputMaybe; + permitted_values?: InputMaybe; + placeholder?: InputMaybe; + public?: InputMaybe; + required?: InputMaybe; + short_description?: InputMaybe; + short_name?: InputMaybe; + subfields?: InputMaybe; + weight?: InputMaybe; +}; + /** Filter type for Float scalar */ export type FloatFilter = { /** $all */ @@ -2113,23 +2085,20 @@ export type Incident = { flagged_dissimilar_incidents?: Maybe>>; incident_id: Scalars['Int']['output']; nlp_similar_incidents?: Maybe>>; - reports: Array>; + reports?: Maybe>>; title: Scalars['String']['output']; tsne?: Maybe; }; export type IncidentAllegedDeployerOfAiSystemRelationInput = { - create?: InputMaybe>>; link?: InputMaybe>>; }; export type IncidentAllegedDeveloperOfAiSystemRelationInput = { - create?: InputMaybe>>; link?: InputMaybe>>; }; export type IncidentAllegedHarmedOrNearlyHarmedPartiesRelationInput = { - create?: InputMaybe>>; link?: InputMaybe>>; }; @@ -2227,25 +2196,6 @@ export type IncidentFilterType = { tsne?: InputMaybe; }; -export type IncidentInsertInput = { - AllegedDeployerOfAISystem?: InputMaybe; - AllegedDeveloperOfAISystem?: InputMaybe; - AllegedHarmedOrNearlyHarmedParties?: InputMaybe; - _id?: InputMaybe; - date: Scalars['String']['input']; - description?: InputMaybe; - editor_dissimilar_incidents?: InputMaybe>>; - editor_notes?: InputMaybe; - editor_similar_incidents?: InputMaybe>>; - embedding?: InputMaybe; - epoch_date_modified?: InputMaybe; - flagged_dissimilar_incidents?: InputMaybe>>; - incident_id: Scalars['Int']['input']; - nlp_similar_incidents?: InputMaybe>>; - title: Scalars['String']['input']; - tsne?: InputMaybe; -}; - export type IncidentInsertType = { AllegedDeployerOfAISystem?: InputMaybe; AllegedDeveloperOfAISystem?: InputMaybe; @@ -2326,114 +2276,6 @@ export type IncidentNlp_Similar_IncidentUpdateInput = { similarity_unset?: InputMaybe; }; -export type IncidentQueryInput = { - AND?: InputMaybe>; - AllegedDeployerOfAISystem?: InputMaybe>>; - AllegedDeployerOfAISystem_exists?: InputMaybe; - AllegedDeployerOfAISystem_in?: InputMaybe>>; - AllegedDeployerOfAISystem_nin?: InputMaybe>>; - AllegedDeveloperOfAISystem?: InputMaybe>>; - AllegedDeveloperOfAISystem_exists?: InputMaybe; - AllegedDeveloperOfAISystem_in?: InputMaybe>>; - AllegedDeveloperOfAISystem_nin?: InputMaybe>>; - AllegedHarmedOrNearlyHarmedParties?: InputMaybe>>; - AllegedHarmedOrNearlyHarmedParties_exists?: InputMaybe; - AllegedHarmedOrNearlyHarmedParties_in?: InputMaybe>>; - AllegedHarmedOrNearlyHarmedParties_nin?: InputMaybe>>; - OR?: InputMaybe>; - _id?: InputMaybe; - _id_exists?: InputMaybe; - _id_gt?: InputMaybe; - _id_gte?: InputMaybe; - _id_in?: InputMaybe>>; - _id_lt?: InputMaybe; - _id_lte?: InputMaybe; - _id_ne?: InputMaybe; - _id_nin?: InputMaybe>>; - date?: InputMaybe; - date_exists?: InputMaybe; - date_gt?: InputMaybe; - date_gte?: InputMaybe; - date_in?: InputMaybe>>; - date_lt?: InputMaybe; - date_lte?: InputMaybe; - date_ne?: InputMaybe; - date_nin?: InputMaybe>>; - description?: InputMaybe; - description_exists?: InputMaybe; - description_gt?: InputMaybe; - description_gte?: InputMaybe; - description_in?: InputMaybe>>; - description_lt?: InputMaybe; - description_lte?: InputMaybe; - description_ne?: InputMaybe; - description_nin?: InputMaybe>>; - editor_dissimilar_incidents?: InputMaybe>>; - editor_dissimilar_incidents_exists?: InputMaybe; - editor_dissimilar_incidents_in?: InputMaybe>>; - editor_dissimilar_incidents_nin?: InputMaybe>>; - editor_notes?: InputMaybe; - editor_notes_exists?: InputMaybe; - editor_notes_gt?: InputMaybe; - editor_notes_gte?: InputMaybe; - editor_notes_in?: InputMaybe>>; - editor_notes_lt?: InputMaybe; - editor_notes_lte?: InputMaybe; - editor_notes_ne?: InputMaybe; - editor_notes_nin?: InputMaybe>>; - editor_similar_incidents?: InputMaybe>>; - editor_similar_incidents_exists?: InputMaybe; - editor_similar_incidents_in?: InputMaybe>>; - editor_similar_incidents_nin?: InputMaybe>>; - editors?: InputMaybe>>; - editors_exists?: InputMaybe; - editors_in?: InputMaybe>>; - editors_nin?: InputMaybe>>; - embedding?: InputMaybe; - embedding_exists?: InputMaybe; - epoch_date_modified?: InputMaybe; - epoch_date_modified_exists?: InputMaybe; - epoch_date_modified_gt?: InputMaybe; - epoch_date_modified_gte?: InputMaybe; - epoch_date_modified_in?: InputMaybe>>; - epoch_date_modified_lt?: InputMaybe; - epoch_date_modified_lte?: InputMaybe; - epoch_date_modified_ne?: InputMaybe; - epoch_date_modified_nin?: InputMaybe>>; - flagged_dissimilar_incidents?: InputMaybe>>; - flagged_dissimilar_incidents_exists?: InputMaybe; - flagged_dissimilar_incidents_in?: InputMaybe>>; - flagged_dissimilar_incidents_nin?: InputMaybe>>; - incident_id?: InputMaybe; - incident_id_exists?: InputMaybe; - incident_id_gt?: InputMaybe; - incident_id_gte?: InputMaybe; - incident_id_in?: InputMaybe>>; - incident_id_lt?: InputMaybe; - incident_id_lte?: InputMaybe; - incident_id_ne?: InputMaybe; - incident_id_nin?: InputMaybe>>; - nlp_similar_incidents?: InputMaybe>>; - nlp_similar_incidents_exists?: InputMaybe; - nlp_similar_incidents_in?: InputMaybe>>; - nlp_similar_incidents_nin?: InputMaybe>>; - reports?: InputMaybe>>; - reports_exists?: InputMaybe; - reports_in?: InputMaybe>>; - reports_nin?: InputMaybe>>; - title?: InputMaybe; - title_exists?: InputMaybe; - title_gt?: InputMaybe; - title_gte?: InputMaybe; - title_in?: InputMaybe>>; - title_lt?: InputMaybe; - title_lte?: InputMaybe; - title_ne?: InputMaybe; - title_nin?: InputMaybe>>; - tsne?: InputMaybe; - tsne_exists?: InputMaybe; -}; - export type IncidentReportsRelationInput = { link: Array>; }; @@ -2554,45 +2396,6 @@ export type IncidentTsneUpdateInput = { y_unset?: InputMaybe; }; -export type IncidentUpdateInput = { - AllegedDeployerOfAISystem?: InputMaybe; - AllegedDeployerOfAISystem_unset?: InputMaybe; - AllegedDeveloperOfAISystem?: InputMaybe; - AllegedDeveloperOfAISystem_unset?: InputMaybe; - AllegedHarmedOrNearlyHarmedParties?: InputMaybe; - AllegedHarmedOrNearlyHarmedParties_unset?: InputMaybe; - _id?: InputMaybe; - _id_unset?: InputMaybe; - date?: InputMaybe; - date_unset?: InputMaybe; - description?: InputMaybe; - description_unset?: InputMaybe; - editor_dissimilar_incidents?: InputMaybe>>; - editor_dissimilar_incidents_unset?: InputMaybe; - editor_notes?: InputMaybe; - editor_notes_unset?: InputMaybe; - editor_similar_incidents?: InputMaybe>>; - editor_similar_incidents_unset?: InputMaybe; - editors_unset?: InputMaybe; - embedding?: InputMaybe; - embedding_unset?: InputMaybe; - epoch_date_modified?: InputMaybe; - epoch_date_modified_inc?: InputMaybe; - epoch_date_modified_unset?: InputMaybe; - flagged_dissimilar_incidents?: InputMaybe>>; - flagged_dissimilar_incidents_unset?: InputMaybe; - incident_id?: InputMaybe; - incident_id_inc?: InputMaybe; - incident_id_unset?: InputMaybe; - nlp_similar_incidents?: InputMaybe>>; - nlp_similar_incidents_unset?: InputMaybe; - reports_unset?: InputMaybe; - title?: InputMaybe; - title_unset?: InputMaybe; - tsne?: InputMaybe; - tsne_unset?: InputMaybe; -}; - export type IncidentUpdateType = { set?: InputMaybe; }; @@ -2656,6 +2459,44 @@ export type IntNotFilter = { NIN?: InputMaybe>>; }; +export type ItemFields = { + __typename?: 'ItemFields'; + complete_from?: Maybe; + default?: Maybe; + display_type?: Maybe; + field_number?: Maybe; + instant_facet?: Maybe; + long_description?: Maybe; + long_name?: Maybe; + mongo_type?: Maybe; + permitted_values?: Maybe>>; + placeholder?: Maybe; + public?: Maybe; + required?: Maybe; + short_description?: Maybe; + short_name?: Maybe; + weight?: Maybe; +}; + +export type ItemFieldsObjectFilterType = { + complete_from?: InputMaybe; + default?: InputMaybe; + display_type?: InputMaybe; + field_number?: InputMaybe; + instant_facet?: InputMaybe; + long_description?: InputMaybe; + long_name?: InputMaybe; + mongo_type?: InputMaybe; + opr?: InputMaybe; + permitted_values?: InputMaybe; + placeholder?: InputMaybe; + public?: InputMaybe; + required?: InputMaybe; + short_description?: InputMaybe; + short_name?: InputMaybe; + weight?: InputMaybe; +}; + export type LinkReportsToIncidentsInput = { incident_ids?: InputMaybe>; report_numbers?: InputMaybe>; @@ -2731,17 +2572,14 @@ export type Mutation = { createVariant?: Maybe; deleteManyCandidates?: Maybe; deleteManyChecklists?: Maybe; - deleteManyClassifications?: Maybe; deleteManyDuplicates?: Maybe; deleteManyHistory_incidents?: Maybe; deleteManyHistory_reports?: Maybe; deleteManyNotifications?: Maybe; deleteManyQuickadds?: Maybe; deleteManySubscriptions?: Maybe; - deleteManyTaxas?: Maybe; deleteOneCandidate?: Maybe; deleteOneChecklist?: Maybe; - deleteOneClassification?: Maybe; deleteOneDuplicate?: Maybe; deleteOneHistory_incident?: Maybe; deleteOneHistory_report?: Maybe; @@ -2750,23 +2588,19 @@ export type Mutation = { deleteOneReport?: Maybe; deleteOneSubmission?: Maybe; deleteOneSubscription?: Maybe; - deleteOneTaxa?: Maybe; flagIncidentSimilarity?: Maybe; flagReport?: Maybe; getUser?: Maybe; insertManyCandidates?: Maybe; insertManyChecklists?: Maybe; - insertManyClassifications?: Maybe; insertManyDuplicates?: Maybe; insertManyHistory_incidents?: Maybe; insertManyHistory_reports?: Maybe; insertManyNotifications?: Maybe; insertManyQuickadds?: Maybe; insertManySubscriptions?: Maybe; - insertManyTaxas?: Maybe; insertOneCandidate?: Maybe; insertOneChecklist?: Maybe; - insertOneClassification?: Maybe; insertOneDuplicate?: Maybe; insertOneHistory_incident?: Maybe; insertOneHistory_report?: Maybe; @@ -2776,7 +2610,6 @@ export type Mutation = { insertOneReport?: Maybe; insertOneSubmission?: Maybe; insertOneSubscription?: Maybe; - insertOneTaxa?: Maybe; linkReportsToIncidents?: Maybe>>; logIncidentHistory?: Maybe; logReportHistory?: Maybe; @@ -2784,20 +2617,16 @@ export type Mutation = { promoteSubmissionToReport: PromoteSubmissionToReportPayload; replaceOneCandidate?: Maybe; replaceOneChecklist?: Maybe; - replaceOneClassification?: Maybe; replaceOneDuplicate?: Maybe; replaceOneEntity?: Maybe; replaceOneHistory_incident?: Maybe; replaceOneHistory_report?: Maybe; replaceOneIncident?: Maybe; replaceOneNotification?: Maybe; - replaceOneReport?: Maybe; replaceOneSubscription?: Maybe; - replaceOneTaxa?: Maybe; replaceOneUser?: Maybe; updateManyCandidates?: Maybe; updateManyChecklists?: Maybe; - updateManyClassifications?: Maybe; updateManyDuplicates?: Maybe; updateManyHistory_incidents?: Maybe; updateManyHistory_reports?: Maybe; @@ -2805,10 +2634,8 @@ export type Mutation = { updateManyNotifications?: Maybe; updateManyQuickadds?: Maybe; updateManySubscriptions?: Maybe; - updateManyTaxas?: Maybe; updateOneCandidate?: Maybe; updateOneChecklist?: Maybe; - updateOneClassification?: Maybe; updateOneDuplicate?: Maybe; updateOneEntity?: Maybe; updateOneHistory_incident?: Maybe; @@ -2820,7 +2647,6 @@ export type Mutation = { updateOneReportTranslation?: Maybe; updateOneSubmission?: Maybe; updateOneSubscription?: Maybe; - updateOneTaxa?: Maybe; updateOneUser?: Maybe; upsertOneCandidate?: Maybe; upsertOneChecklist?: Maybe; @@ -2832,7 +2658,6 @@ export type Mutation = { upsertOneNotification?: Maybe; upsertOneQuickadd?: Maybe; upsertOneSubscription?: Maybe; - upsertOneTaxa?: Maybe; }; @@ -2856,11 +2681,6 @@ export type MutationDeleteManyChecklistsArgs = { }; -export type MutationDeleteManyClassificationsArgs = { - query?: InputMaybe; -}; - - export type MutationDeleteManyDuplicatesArgs = { query?: InputMaybe; }; @@ -2893,11 +2713,6 @@ export type MutationDeleteManySubscriptionsArgs = { }; -export type MutationDeleteManyTaxasArgs = { - query?: InputMaybe; -}; - - export type MutationDeleteOneCandidateArgs = { query: CandidateQueryInput; }; @@ -2908,11 +2723,6 @@ export type MutationDeleteOneChecklistArgs = { }; -export type MutationDeleteOneClassificationArgs = { - query: ClassificationQueryInput; -}; - - export type MutationDeleteOneDuplicateArgs = { query: DuplicateQueryInput; }; @@ -2959,11 +2769,6 @@ export type MutationDeleteOneSubscriptionArgs = { }; -export type MutationDeleteOneTaxaArgs = { - query: TaxaQueryInput; -}; - - export type MutationFlagIncidentSimilarityArgs = { dissimilarIds?: InputMaybe>; incidentId: Scalars['Int']['input']; @@ -2991,11 +2796,6 @@ export type MutationInsertManyChecklistsArgs = { }; -export type MutationInsertManyClassificationsArgs = { - data: Array; -}; - - export type MutationInsertManyDuplicatesArgs = { data: Array; }; @@ -3026,11 +2826,6 @@ export type MutationInsertManySubscriptionsArgs = { }; -export type MutationInsertManyTaxasArgs = { - data: Array; -}; - - export type MutationInsertOneCandidateArgs = { data: CandidateInsertInput; }; @@ -3041,11 +2836,6 @@ export type MutationInsertOneChecklistArgs = { }; -export type MutationInsertOneClassificationArgs = { - data: ClassificationInsertInput; -}; - - export type MutationInsertOneDuplicateArgs = { data: DuplicateInsertInput; }; @@ -3091,11 +2881,6 @@ export type MutationInsertOneSubscriptionArgs = { }; -export type MutationInsertOneTaxaArgs = { - data: TaxaInsertInput; -}; - - export type MutationLinkReportsToIncidentsArgs = { input: LinkReportsToIncidentsInput; }; @@ -3128,24 +2913,12 @@ export type MutationReplaceOneChecklistArgs = { }; -export type MutationReplaceOneClassificationArgs = { - data: ClassificationInsertInput; - query?: InputMaybe; -}; - - export type MutationReplaceOneDuplicateArgs = { data: DuplicateInsertInput; query?: InputMaybe; }; -export type MutationReplaceOneEntityArgs = { - data: EntityInsertInput; - query?: InputMaybe; -}; - - export type MutationReplaceOneHistory_IncidentArgs = { data: History_IncidentInsertInput; query?: InputMaybe; @@ -3158,42 +2931,18 @@ export type MutationReplaceOneHistory_ReportArgs = { }; -export type MutationReplaceOneIncidentArgs = { - data: IncidentInsertInput; - query?: InputMaybe; -}; - - export type MutationReplaceOneNotificationArgs = { data: NotificationInsertInput; query?: InputMaybe; }; -export type MutationReplaceOneReportArgs = { - data: ReportInsertInput; - query?: InputMaybe; -}; - - export type MutationReplaceOneSubscriptionArgs = { data: SubscriptionInsertInput; query?: InputMaybe; }; -export type MutationReplaceOneTaxaArgs = { - data: TaxaInsertInput; - query?: InputMaybe; -}; - - -export type MutationReplaceOneUserArgs = { - data: UserInsertInput; - query?: InputMaybe; -}; - - export type MutationUpdateManyCandidatesArgs = { query?: InputMaybe; set: CandidateUpdateInput; @@ -3206,12 +2955,6 @@ export type MutationUpdateManyChecklistsArgs = { }; -export type MutationUpdateManyClassificationsArgs = { - query?: InputMaybe; - set: ClassificationUpdateInput; -}; - - export type MutationUpdateManyDuplicatesArgs = { query?: InputMaybe; set: DuplicateUpdateInput; @@ -3254,12 +2997,6 @@ export type MutationUpdateManySubscriptionsArgs = { }; -export type MutationUpdateManyTaxasArgs = { - query?: InputMaybe; - set: TaxaUpdateInput; -}; - - export type MutationUpdateOneCandidateArgs = { query?: InputMaybe; set: CandidateUpdateInput; @@ -3272,12 +3009,6 @@ export type MutationUpdateOneChecklistArgs = { }; -export type MutationUpdateOneClassificationArgs = { - query?: InputMaybe; - set: ClassificationUpdateInput; -}; - - export type MutationUpdateOneDuplicateArgs = { query?: InputMaybe; set: DuplicateUpdateInput; @@ -3343,12 +3074,6 @@ export type MutationUpdateOneSubscriptionArgs = { }; -export type MutationUpdateOneTaxaArgs = { - query?: InputMaybe; - set: TaxaUpdateInput; -}; - - export type MutationUpdateOneUserArgs = { filter: UserFilterType; update: UserUpdateType; @@ -3368,8 +3093,8 @@ export type MutationUpsertOneChecklistArgs = { export type MutationUpsertOneClassificationArgs = { - data: ClassificationInsertInput; - query?: InputMaybe; + filter: ClassificationFilterType; + update: ClassificationInsertType; }; @@ -3414,12 +3139,6 @@ export type MutationUpsertOneSubscriptionArgs = { query?: InputMaybe; }; - -export type MutationUpsertOneTaxaArgs = { - data: TaxaInsertInput; - query?: InputMaybe; -}; - export type Notification = { __typename?: 'Notification'; _id?: Maybe; @@ -3481,7 +3200,6 @@ export type NotificationQueryInput = { type_lte?: InputMaybe; type_ne?: InputMaybe; type_nin?: InputMaybe>>; - userId?: InputMaybe; userId_exists?: InputMaybe; }; @@ -3515,7 +3233,6 @@ export type NotificationUpdateInput = { }; export type NotificationUserIdRelationInput = { - create?: InputMaybe; link?: InputMaybe; }; @@ -3616,7 +3333,7 @@ export type Query = { checklist?: Maybe; checklists: Array>; classification?: Maybe; - classifications: Array>; + classifications?: Maybe>>; duplicate?: Maybe; duplicates: Array>; entities?: Maybe>>; @@ -3639,7 +3356,7 @@ export type Query = { subscription?: Maybe; subscriptions: Array>; taxa?: Maybe; - taxas: Array>; + taxas?: Maybe>>; user?: Maybe; users?: Maybe>>; }; @@ -3670,14 +3387,16 @@ export type QueryChecklistsArgs = { export type QueryClassificationArgs = { - query?: InputMaybe; + filter?: InputMaybe; + pagination?: InputMaybe; + sort?: InputMaybe; }; export type QueryClassificationsArgs = { - limit?: InputMaybe; - query?: InputMaybe; - sortBy?: InputMaybe; + filter?: InputMaybe; + pagination?: InputMaybe; + sort?: InputMaybe; }; @@ -3817,14 +3536,16 @@ export type QuerySubscriptionsArgs = { export type QueryTaxaArgs = { - query?: InputMaybe; + filter?: InputMaybe; + pagination?: InputMaybe; + sort?: InputMaybe; }; export type QueryTaxasArgs = { - limit?: InputMaybe; - query?: InputMaybe; - sortBy?: InputMaybe; + filter?: InputMaybe; + pagination?: InputMaybe; + sort?: InputMaybe; }; @@ -3861,14 +3582,6 @@ export type QuickaddFilterType = { url?: InputMaybe; }; -export type QuickaddInsertInput = { - _id?: InputMaybe; - date_submitted: Scalars['String']['input']; - incident_id?: InputMaybe; - source_domain?: InputMaybe; - url: Scalars['String']['input']; -}; - export type QuickaddInsertType = { _id?: InputMaybe; date_submitted: Scalars['String']['input']; @@ -3906,19 +3619,6 @@ export type QuickaddSortType = { url?: InputMaybe; }; -export type QuickaddUpdateInput = { - _id?: InputMaybe; - _id_unset?: InputMaybe; - date_submitted?: InputMaybe; - date_submitted_unset?: InputMaybe; - incident_id?: InputMaybe; - incident_id_unset?: InputMaybe; - source_domain?: InputMaybe; - source_domain_unset?: InputMaybe; - url?: InputMaybe; - url_unset?: InputMaybe; -}; - export type QuickaddUpdateType = { set?: InputMaybe; }; @@ -3952,14 +3652,14 @@ export type Report = { tags: Array>; text: Scalars['String']['output']; title: Scalars['String']['output']; - translations?: Maybe; + translations?: Maybe; url: Scalars['String']['output']; user?: Maybe; }; export type ReportTranslationsArgs = { - input?: InputMaybe; + input: Scalars['String']['input']; }; export type ReportEmbedding = { @@ -4055,37 +3755,6 @@ export type ReportFilterType = { user?: InputMaybe; }; -export type ReportInsertInput = { - _id?: InputMaybe; - authors: Array>; - cloudinary_id: Scalars['String']['input']; - date_downloaded: Scalars['DateTime']['input']; - date_modified: Scalars['DateTime']['input']; - date_published: Scalars['DateTime']['input']; - date_submitted: Scalars['DateTime']['input']; - description?: InputMaybe; - editor_notes?: InputMaybe; - embedding?: InputMaybe; - epoch_date_downloaded: Scalars['Int']['input']; - epoch_date_modified: Scalars['Int']['input']; - epoch_date_published: Scalars['Int']['input']; - epoch_date_submitted: Scalars['Int']['input']; - flag?: InputMaybe; - image_url: Scalars['String']['input']; - inputs_outputs?: InputMaybe>>; - is_incident_report?: InputMaybe; - language: Scalars['String']['input']; - plain_text: Scalars['String']['input']; - quiet?: InputMaybe; - report_number: Scalars['Int']['input']; - source_domain: Scalars['String']['input']; - submitters: Array>; - tags: Array>; - text: Scalars['String']['input']; - title: Scalars['String']['input']; - url: Scalars['String']['input']; -}; - export type ReportInsertType = { _id?: InputMaybe; authors: Array>; @@ -4118,231 +3787,17 @@ export type ReportInsertType = { user?: InputMaybe; }; -export type ReportQueryInput = { - AND?: InputMaybe>; - OR?: InputMaybe>; +export type ReportSetType = { _id?: InputMaybe; - _id_exists?: InputMaybe; - _id_gt?: InputMaybe; - _id_gte?: InputMaybe; - _id_in?: InputMaybe>>; - _id_lt?: InputMaybe; - _id_lte?: InputMaybe; - _id_ne?: InputMaybe; - _id_nin?: InputMaybe>>; authors?: InputMaybe>>; - authors_exists?: InputMaybe; - authors_in?: InputMaybe>>; - authors_nin?: InputMaybe>>; cloudinary_id?: InputMaybe; - cloudinary_id_exists?: InputMaybe; - cloudinary_id_gt?: InputMaybe; - cloudinary_id_gte?: InputMaybe; - cloudinary_id_in?: InputMaybe>>; - cloudinary_id_lt?: InputMaybe; - cloudinary_id_lte?: InputMaybe; - cloudinary_id_ne?: InputMaybe; - cloudinary_id_nin?: InputMaybe>>; date_downloaded?: InputMaybe; - date_downloaded_exists?: InputMaybe; - date_downloaded_gt?: InputMaybe; - date_downloaded_gte?: InputMaybe; - date_downloaded_in?: InputMaybe>>; - date_downloaded_lt?: InputMaybe; - date_downloaded_lte?: InputMaybe; - date_downloaded_ne?: InputMaybe; - date_downloaded_nin?: InputMaybe>>; date_modified?: InputMaybe; - date_modified_exists?: InputMaybe; - date_modified_gt?: InputMaybe; - date_modified_gte?: InputMaybe; - date_modified_in?: InputMaybe>>; - date_modified_lt?: InputMaybe; - date_modified_lte?: InputMaybe; - date_modified_ne?: InputMaybe; - date_modified_nin?: InputMaybe>>; date_published?: InputMaybe; - date_published_exists?: InputMaybe; - date_published_gt?: InputMaybe; - date_published_gte?: InputMaybe; - date_published_in?: InputMaybe>>; - date_published_lt?: InputMaybe; - date_published_lte?: InputMaybe; - date_published_ne?: InputMaybe; - date_published_nin?: InputMaybe>>; date_submitted?: InputMaybe; - date_submitted_exists?: InputMaybe; - date_submitted_gt?: InputMaybe; - date_submitted_gte?: InputMaybe; - date_submitted_in?: InputMaybe>>; - date_submitted_lt?: InputMaybe; - date_submitted_lte?: InputMaybe; - date_submitted_ne?: InputMaybe; - date_submitted_nin?: InputMaybe>>; description?: InputMaybe; - description_exists?: InputMaybe; - description_gt?: InputMaybe; - description_gte?: InputMaybe; - description_in?: InputMaybe>>; - description_lt?: InputMaybe; - description_lte?: InputMaybe; - description_ne?: InputMaybe; - description_nin?: InputMaybe>>; editor_notes?: InputMaybe; - editor_notes_exists?: InputMaybe; - editor_notes_gt?: InputMaybe; - editor_notes_gte?: InputMaybe; - editor_notes_in?: InputMaybe>>; - editor_notes_lt?: InputMaybe; - editor_notes_lte?: InputMaybe; - editor_notes_ne?: InputMaybe; - editor_notes_nin?: InputMaybe>>; - embedding?: InputMaybe; - embedding_exists?: InputMaybe; - epoch_date_downloaded?: InputMaybe; - epoch_date_downloaded_exists?: InputMaybe; - epoch_date_downloaded_gt?: InputMaybe; - epoch_date_downloaded_gte?: InputMaybe; - epoch_date_downloaded_in?: InputMaybe>>; - epoch_date_downloaded_lt?: InputMaybe; - epoch_date_downloaded_lte?: InputMaybe; - epoch_date_downloaded_ne?: InputMaybe; - epoch_date_downloaded_nin?: InputMaybe>>; - epoch_date_modified?: InputMaybe; - epoch_date_modified_exists?: InputMaybe; - epoch_date_modified_gt?: InputMaybe; - epoch_date_modified_gte?: InputMaybe; - epoch_date_modified_in?: InputMaybe>>; - epoch_date_modified_lt?: InputMaybe; - epoch_date_modified_lte?: InputMaybe; - epoch_date_modified_ne?: InputMaybe; - epoch_date_modified_nin?: InputMaybe>>; - epoch_date_published?: InputMaybe; - epoch_date_published_exists?: InputMaybe; - epoch_date_published_gt?: InputMaybe; - epoch_date_published_gte?: InputMaybe; - epoch_date_published_in?: InputMaybe>>; - epoch_date_published_lt?: InputMaybe; - epoch_date_published_lte?: InputMaybe; - epoch_date_published_ne?: InputMaybe; - epoch_date_published_nin?: InputMaybe>>; - epoch_date_submitted?: InputMaybe; - epoch_date_submitted_exists?: InputMaybe; - epoch_date_submitted_gt?: InputMaybe; - epoch_date_submitted_gte?: InputMaybe; - epoch_date_submitted_in?: InputMaybe>>; - epoch_date_submitted_lt?: InputMaybe; - epoch_date_submitted_lte?: InputMaybe; - epoch_date_submitted_ne?: InputMaybe; - epoch_date_submitted_nin?: InputMaybe>>; - flag?: InputMaybe; - flag_exists?: InputMaybe; - flag_ne?: InputMaybe; - image_url?: InputMaybe; - image_url_exists?: InputMaybe; - image_url_gt?: InputMaybe; - image_url_gte?: InputMaybe; - image_url_in?: InputMaybe>>; - image_url_lt?: InputMaybe; - image_url_lte?: InputMaybe; - image_url_ne?: InputMaybe; - image_url_nin?: InputMaybe>>; - inputs_outputs?: InputMaybe>>; - inputs_outputs_exists?: InputMaybe; - inputs_outputs_in?: InputMaybe>>; - inputs_outputs_nin?: InputMaybe>>; - is_incident_report?: InputMaybe; - is_incident_report_exists?: InputMaybe; - is_incident_report_ne?: InputMaybe; - language?: InputMaybe; - language_exists?: InputMaybe; - language_gt?: InputMaybe; - language_gte?: InputMaybe; - language_in?: InputMaybe>>; - language_lt?: InputMaybe; - language_lte?: InputMaybe; - language_ne?: InputMaybe; - language_nin?: InputMaybe>>; - plain_text?: InputMaybe; - plain_text_exists?: InputMaybe; - plain_text_gt?: InputMaybe; - plain_text_gte?: InputMaybe; - plain_text_in?: InputMaybe>>; - plain_text_lt?: InputMaybe; - plain_text_lte?: InputMaybe; - plain_text_ne?: InputMaybe; - plain_text_nin?: InputMaybe>>; - quiet?: InputMaybe; - quiet_exists?: InputMaybe; - quiet_ne?: InputMaybe; - report_number?: InputMaybe; - report_number_exists?: InputMaybe; - report_number_gt?: InputMaybe; - report_number_gte?: InputMaybe; - report_number_in?: InputMaybe>>; - report_number_lt?: InputMaybe; - report_number_lte?: InputMaybe; - report_number_ne?: InputMaybe; - report_number_nin?: InputMaybe>>; - source_domain?: InputMaybe; - source_domain_exists?: InputMaybe; - source_domain_gt?: InputMaybe; - source_domain_gte?: InputMaybe; - source_domain_in?: InputMaybe>>; - source_domain_lt?: InputMaybe; - source_domain_lte?: InputMaybe; - source_domain_ne?: InputMaybe; - source_domain_nin?: InputMaybe>>; - submitters?: InputMaybe>>; - submitters_exists?: InputMaybe; - submitters_in?: InputMaybe>>; - submitters_nin?: InputMaybe>>; - tags?: InputMaybe>>; - tags_exists?: InputMaybe; - tags_in?: InputMaybe>>; - tags_nin?: InputMaybe>>; - text?: InputMaybe; - text_exists?: InputMaybe; - text_gt?: InputMaybe; - text_gte?: InputMaybe; - text_in?: InputMaybe>>; - text_lt?: InputMaybe; - text_lte?: InputMaybe; - text_ne?: InputMaybe; - text_nin?: InputMaybe>>; - title?: InputMaybe; - title_exists?: InputMaybe; - title_gt?: InputMaybe; - title_gte?: InputMaybe; - title_in?: InputMaybe>>; - title_lt?: InputMaybe; - title_lte?: InputMaybe; - title_ne?: InputMaybe; - title_nin?: InputMaybe>>; - url?: InputMaybe; - url_exists?: InputMaybe; - url_gt?: InputMaybe; - url_gte?: InputMaybe; - url_in?: InputMaybe>>; - url_lt?: InputMaybe; - url_lte?: InputMaybe; - url_ne?: InputMaybe; - url_nin?: InputMaybe>>; - user?: InputMaybe; - user_exists?: InputMaybe; -}; - -export type ReportSetType = { - _id?: InputMaybe; - authors?: InputMaybe>>; - cloudinary_id?: InputMaybe; - date_downloaded?: InputMaybe; - date_modified?: InputMaybe; - date_published?: InputMaybe; - date_submitted?: InputMaybe; - description?: InputMaybe; - editor_notes?: InputMaybe; - embedding?: InputMaybe; + embedding?: InputMaybe; epoch_date_downloaded?: InputMaybe; epoch_date_modified?: InputMaybe; epoch_date_published?: InputMaybe; @@ -4448,71 +3903,6 @@ export type ReportTranslations = { title?: Maybe; }; -export type ReportUpdateInput = { - _id?: InputMaybe; - _id_unset?: InputMaybe; - authors?: InputMaybe>>; - authors_unset?: InputMaybe; - cloudinary_id?: InputMaybe; - cloudinary_id_unset?: InputMaybe; - date_downloaded?: InputMaybe; - date_downloaded_unset?: InputMaybe; - date_modified?: InputMaybe; - date_modified_unset?: InputMaybe; - date_published?: InputMaybe; - date_published_unset?: InputMaybe; - date_submitted?: InputMaybe; - date_submitted_unset?: InputMaybe; - description?: InputMaybe; - description_unset?: InputMaybe; - editor_notes?: InputMaybe; - editor_notes_unset?: InputMaybe; - embedding?: InputMaybe; - embedding_unset?: InputMaybe; - epoch_date_downloaded?: InputMaybe; - epoch_date_downloaded_inc?: InputMaybe; - epoch_date_downloaded_unset?: InputMaybe; - epoch_date_modified?: InputMaybe; - epoch_date_modified_inc?: InputMaybe; - epoch_date_modified_unset?: InputMaybe; - epoch_date_published?: InputMaybe; - epoch_date_published_inc?: InputMaybe; - epoch_date_published_unset?: InputMaybe; - epoch_date_submitted?: InputMaybe; - epoch_date_submitted_inc?: InputMaybe; - epoch_date_submitted_unset?: InputMaybe; - flag?: InputMaybe; - flag_unset?: InputMaybe; - image_url?: InputMaybe; - image_url_unset?: InputMaybe; - inputs_outputs?: InputMaybe>>; - inputs_outputs_unset?: InputMaybe; - is_incident_report?: InputMaybe; - is_incident_report_unset?: InputMaybe; - language?: InputMaybe; - language_unset?: InputMaybe; - plain_text?: InputMaybe; - plain_text_unset?: InputMaybe; - quiet?: InputMaybe; - quiet_unset?: InputMaybe; - report_number?: InputMaybe; - report_number_inc?: InputMaybe; - report_number_unset?: InputMaybe; - source_domain?: InputMaybe; - source_domain_unset?: InputMaybe; - submitters?: InputMaybe>>; - submitters_unset?: InputMaybe; - tags?: InputMaybe>>; - tags_unset?: InputMaybe; - text?: InputMaybe; - text_unset?: InputMaybe; - title?: InputMaybe; - title_unset?: InputMaybe; - url?: InputMaybe; - url_unset?: InputMaybe; - user_unset?: InputMaybe; -}; - export type ReportUpdateType = { set?: InputMaybe; }; @@ -4641,6 +4031,60 @@ export type StringNotFilter = { REGEX?: InputMaybe; }; +export type Subfield = { + __typename?: 'Subfield'; + complete_from?: Maybe; + default?: Maybe; + display_type?: Maybe; + field_number?: Maybe; + hide_search?: Maybe; + instant_facet?: Maybe; + long_description?: Maybe; + long_name?: Maybe; + mongo_type?: Maybe; + permitted_values?: Maybe>>; + placeholder?: Maybe; + public?: Maybe; + required?: Maybe; + short_description?: Maybe; + short_name?: Maybe; + weight?: Maybe; +}; + +export type SubfieldCompleteFrom = { + __typename?: 'SubfieldCompleteFrom'; + all?: Maybe>>; + current?: Maybe>>; + entities?: Maybe; +}; + +export type SubfieldCompleteFromObjectFilterType = { + all?: InputMaybe; + current?: InputMaybe; + entities?: InputMaybe; + opr?: InputMaybe; +}; + +export type SubfieldObjectFilterType = { + complete_from?: InputMaybe; + default?: InputMaybe; + display_type?: InputMaybe; + field_number?: InputMaybe; + hide_search?: InputMaybe; + instant_facet?: InputMaybe; + long_description?: InputMaybe; + long_name?: InputMaybe; + mongo_type?: InputMaybe; + opr?: InputMaybe; + permitted_values?: InputMaybe; + placeholder?: InputMaybe; + public?: InputMaybe; + required?: InputMaybe; + short_description?: InputMaybe; + short_name?: InputMaybe; + weight?: InputMaybe; +}; + export type Submission = { __typename?: 'Submission'; _id?: Maybe; @@ -4769,37 +4213,6 @@ export type SubmissionIncident_EditorsRelationInput = { link: Array>; }; -export type SubmissionInsertInput = { - _id?: InputMaybe; - authors: Array>; - cloudinary_id?: InputMaybe; - date_downloaded: Scalars['String']['input']; - date_modified: Scalars['String']['input']; - date_published: Scalars['String']['input']; - date_submitted: Scalars['String']['input']; - description?: InputMaybe; - editor_dissimilar_incidents?: InputMaybe>>; - editor_notes?: InputMaybe; - editor_similar_incidents?: InputMaybe>>; - embedding?: InputMaybe; - epoch_date_modified?: InputMaybe; - image_url: Scalars['String']['input']; - incident_date?: InputMaybe; - incident_ids?: InputMaybe>>; - incident_title?: InputMaybe; - language: Scalars['String']['input']; - nlp_similar_incidents?: InputMaybe>>; - plain_text?: InputMaybe; - quiet?: InputMaybe; - source_domain: Scalars['String']['input']; - status?: InputMaybe; - submitters: Array>; - tags: Array>; - text: Scalars['String']['input']; - title: Scalars['String']['input']; - url: Scalars['String']['input']; -}; - export type SubmissionInsertType = { _id?: InputMaybe; authors: Array>; @@ -4982,71 +4395,6 @@ export type SubmissionSortType = { url?: InputMaybe; }; -export type SubmissionUpdateInput = { - _id?: InputMaybe; - _id_unset?: InputMaybe; - authors?: InputMaybe>>; - authors_unset?: InputMaybe; - cloudinary_id?: InputMaybe; - cloudinary_id_unset?: InputMaybe; - date_downloaded?: InputMaybe; - date_downloaded_unset?: InputMaybe; - date_modified?: InputMaybe; - date_modified_unset?: InputMaybe; - date_published?: InputMaybe; - date_published_unset?: InputMaybe; - date_submitted?: InputMaybe; - date_submitted_unset?: InputMaybe; - deployers_unset?: InputMaybe; - description?: InputMaybe; - description_unset?: InputMaybe; - developers_unset?: InputMaybe; - editor_dissimilar_incidents?: InputMaybe>>; - editor_dissimilar_incidents_unset?: InputMaybe; - editor_notes?: InputMaybe; - editor_notes_unset?: InputMaybe; - editor_similar_incidents?: InputMaybe>>; - editor_similar_incidents_unset?: InputMaybe; - embedding?: InputMaybe; - embedding_unset?: InputMaybe; - epoch_date_modified?: InputMaybe; - epoch_date_modified_inc?: InputMaybe; - epoch_date_modified_unset?: InputMaybe; - harmed_parties_unset?: InputMaybe; - image_url?: InputMaybe; - image_url_unset?: InputMaybe; - incident_date?: InputMaybe; - incident_date_unset?: InputMaybe; - incident_editors_unset?: InputMaybe; - incident_ids?: InputMaybe>>; - incident_ids_unset?: InputMaybe; - incident_title?: InputMaybe; - incident_title_unset?: InputMaybe; - language?: InputMaybe; - language_unset?: InputMaybe; - nlp_similar_incidents?: InputMaybe>>; - nlp_similar_incidents_unset?: InputMaybe; - plain_text?: InputMaybe; - plain_text_unset?: InputMaybe; - quiet?: InputMaybe; - quiet_unset?: InputMaybe; - source_domain?: InputMaybe; - source_domain_unset?: InputMaybe; - status?: InputMaybe; - status_unset?: InputMaybe; - submitters?: InputMaybe>>; - submitters_unset?: InputMaybe; - tags?: InputMaybe>>; - tags_unset?: InputMaybe; - text?: InputMaybe; - text_unset?: InputMaybe; - title?: InputMaybe; - title_unset?: InputMaybe; - url?: InputMaybe; - url_unset?: InputMaybe; - user_unset?: InputMaybe; -}; - export type SubmissionUpdateType = { set?: InputMaybe; }; @@ -5065,12 +4413,10 @@ export type Subscription = { }; export type SubscriptionEntityIdRelationInput = { - create?: InputMaybe; link?: InputMaybe; }; export type SubscriptionIncident_IdRelationInput = { - create?: InputMaybe; link?: InputMaybe; }; @@ -5094,9 +4440,7 @@ export type SubscriptionQueryInput = { _id_lte?: InputMaybe; _id_ne?: InputMaybe; _id_nin?: InputMaybe>>; - entityId?: InputMaybe; entityId_exists?: InputMaybe; - incident_id?: InputMaybe; incident_id_exists?: InputMaybe; type?: InputMaybe; type_exists?: InputMaybe; @@ -5107,7 +4451,6 @@ export type SubscriptionQueryInput = { type_lte?: InputMaybe; type_ne?: InputMaybe; type_nin?: InputMaybe>>; - userId?: InputMaybe; userId_exists?: InputMaybe; }; @@ -5138,7 +4481,6 @@ export type SubscriptionUpdateInput = { }; export type SubscriptionUserIdRelationInput = { - create?: InputMaybe; link?: InputMaybe; }; @@ -5147,8 +4489,8 @@ export type Taxa = { _id?: Maybe; complete_entities?: Maybe; description?: Maybe; - dummy_fields?: Maybe>>; - field_list?: Maybe>>; + dummy_fields?: Maybe>>; + field_list?: Maybe>>; namespace?: Maybe; weight?: Maybe; }; @@ -5638,66 +4980,17 @@ export type TaxaField_ListUpdateInput = { weight_unset?: InputMaybe; }; -export type TaxaInsertInput = { - _id?: InputMaybe; - complete_entities?: InputMaybe; - description?: InputMaybe; - dummy_fields?: InputMaybe>>; - field_list?: InputMaybe>>; - namespace?: InputMaybe; - weight?: InputMaybe; -}; - -export type TaxaQueryInput = { - AND?: InputMaybe>; - OR?: InputMaybe>; - _id?: InputMaybe; - _id_exists?: InputMaybe; - _id_gt?: InputMaybe; - _id_gte?: InputMaybe; - _id_in?: InputMaybe>>; - _id_lt?: InputMaybe; - _id_lte?: InputMaybe; - _id_ne?: InputMaybe; - _id_nin?: InputMaybe>>; - complete_entities?: InputMaybe; - complete_entities_exists?: InputMaybe; - complete_entities_ne?: InputMaybe; - description?: InputMaybe; - description_exists?: InputMaybe; - description_gt?: InputMaybe; - description_gte?: InputMaybe; - description_in?: InputMaybe>>; - description_lt?: InputMaybe; - description_lte?: InputMaybe; - description_ne?: InputMaybe; - description_nin?: InputMaybe>>; - dummy_fields?: InputMaybe>>; - dummy_fields_exists?: InputMaybe; - dummy_fields_in?: InputMaybe>>; - dummy_fields_nin?: InputMaybe>>; - field_list?: InputMaybe>>; - field_list_exists?: InputMaybe; - field_list_in?: InputMaybe>>; - field_list_nin?: InputMaybe>>; - namespace?: InputMaybe; - namespace_exists?: InputMaybe; - namespace_gt?: InputMaybe; - namespace_gte?: InputMaybe; - namespace_in?: InputMaybe>>; - namespace_lt?: InputMaybe; - namespace_lte?: InputMaybe; - namespace_ne?: InputMaybe; - namespace_nin?: InputMaybe>>; - weight?: InputMaybe; - weight_exists?: InputMaybe; - weight_gt?: InputMaybe; - weight_gte?: InputMaybe; - weight_in?: InputMaybe>>; - weight_lt?: InputMaybe; - weight_lte?: InputMaybe; - weight_ne?: InputMaybe; - weight_nin?: InputMaybe>>; +export type TaxaFilterType = { + AND?: InputMaybe>>; + NOR?: InputMaybe>>; + OR?: InputMaybe>>; + _id?: InputMaybe; + complete_entities?: InputMaybe; + description?: InputMaybe; + dummy_fields?: InputMaybe; + field_list?: InputMaybe; + namespace?: InputMaybe; + weight?: InputMaybe; }; export enum TaxaSortByInput { @@ -5711,22 +5004,12 @@ export enum TaxaSortByInput { IdDesc = '_ID_DESC' } -export type TaxaUpdateInput = { - _id?: InputMaybe; - _id_unset?: InputMaybe; - complete_entities?: InputMaybe; - complete_entities_unset?: InputMaybe; - description?: InputMaybe; - description_unset?: InputMaybe; - dummy_fields?: InputMaybe>>; - dummy_fields_unset?: InputMaybe; - field_list?: InputMaybe>>; - field_list_unset?: InputMaybe; - namespace?: InputMaybe; - namespace_unset?: InputMaybe; - weight?: InputMaybe; - weight_inc?: InputMaybe; - weight_unset?: InputMaybe; +export type TaxaSortType = { + _id?: InputMaybe; + complete_entities?: InputMaybe; + description?: InputMaybe; + namespace?: InputMaybe; + weight?: InputMaybe; }; export type UpdateManyPayload = { @@ -5772,59 +5055,6 @@ export type UserFilterType = { userId?: InputMaybe; }; -export type UserInsertInput = { - _id?: InputMaybe; - first_name?: InputMaybe; - last_name?: InputMaybe; - roles: Array>; - userId: Scalars['String']['input']; -}; - -export type UserQueryInput = { - AND?: InputMaybe>; - OR?: InputMaybe>; - _id?: InputMaybe; - _id_exists?: InputMaybe; - _id_gt?: InputMaybe; - _id_gte?: InputMaybe; - _id_in?: InputMaybe>>; - _id_lt?: InputMaybe; - _id_lte?: InputMaybe; - _id_ne?: InputMaybe; - _id_nin?: InputMaybe>>; - first_name?: InputMaybe; - first_name_exists?: InputMaybe; - first_name_gt?: InputMaybe; - first_name_gte?: InputMaybe; - first_name_in?: InputMaybe>>; - first_name_lt?: InputMaybe; - first_name_lte?: InputMaybe; - first_name_ne?: InputMaybe; - first_name_nin?: InputMaybe>>; - last_name?: InputMaybe; - last_name_exists?: InputMaybe; - last_name_gt?: InputMaybe; - last_name_gte?: InputMaybe; - last_name_in?: InputMaybe>>; - last_name_lt?: InputMaybe; - last_name_lte?: InputMaybe; - last_name_ne?: InputMaybe; - last_name_nin?: InputMaybe>>; - roles?: InputMaybe>>; - roles_exists?: InputMaybe; - roles_in?: InputMaybe>>; - roles_nin?: InputMaybe>>; - userId?: InputMaybe; - userId_exists?: InputMaybe; - userId_gt?: InputMaybe; - userId_gte?: InputMaybe; - userId_in?: InputMaybe>>; - userId_lt?: InputMaybe; - userId_lte?: InputMaybe; - userId_ne?: InputMaybe; - userId_nin?: InputMaybe>>; -}; - export type UserSetType = { _id?: InputMaybe; first_name?: InputMaybe; @@ -5851,37 +5081,24 @@ export type UserSortType = { userId?: InputMaybe; }; -export type UserUpdateInput = { - _id?: InputMaybe; - _id_unset?: InputMaybe; - first_name?: InputMaybe; - first_name_unset?: InputMaybe; - last_name?: InputMaybe; - last_name_unset?: InputMaybe; - roles?: InputMaybe>>; - roles_unset?: InputMaybe; - userId?: InputMaybe; - userId_unset?: InputMaybe; -}; - export type UserUpdateType = { set?: InputMaybe; }; export type FindClassificationsQueryVariables = Exact<{ - query?: InputMaybe; + filter?: InputMaybe; }>; -export type FindClassificationsQuery = { __typename?: 'Query', classifications: Array<{ __typename?: 'Classification', _id?: any | null, notes?: string | null, namespace: string, publish?: boolean | null, incidents: Array<{ __typename?: 'Incident', incident_id: number } | null>, reports: Array<{ __typename?: 'Report', report_number: number } | null>, attributes?: Array<{ __typename?: 'ClassificationAttribute', short_name?: string | null, value_json?: string | null } | null> | null } | null> }; +export type FindClassificationsQuery = { __typename?: 'Query', classifications?: Array<{ __typename?: 'Classification', _id?: any | null, notes?: string | null, namespace: string, publish?: boolean | null, incidents?: Array<{ __typename?: 'Incident', incident_id: number } | null> | null, reports?: Array<{ __typename?: 'Report', report_number: number } | null> | null, attributes?: Array<{ __typename?: 'Attribute', short_name?: string | null, value_json?: string | null } | null> | null } | null> | null }; export type UpsertClassificationMutationVariables = Exact<{ - query?: InputMaybe; - data: ClassificationInsertInput; + filter: ClassificationFilterType; + update: ClassificationInsertType; }>; -export type UpsertClassificationMutation = { __typename?: 'Mutation', upsertOneClassification?: { __typename?: 'Classification', _id?: any | null, notes?: string | null, namespace: string, publish?: boolean | null, incidents: Array<{ __typename?: 'Incident', incident_id: number } | null>, reports: Array<{ __typename?: 'Report', report_number: number } | null>, attributes?: Array<{ __typename?: 'ClassificationAttribute', short_name?: string | null, value_json?: string | null } | null> | null } | null }; +export type UpsertClassificationMutation = { __typename?: 'Mutation', upsertOneClassification?: { __typename?: 'Classification', _id?: any | null, notes?: string | null, namespace: string, publish?: boolean | null, incidents?: Array<{ __typename?: 'Incident', incident_id: number } | null> | null, reports?: Array<{ __typename?: 'Report', report_number: number } | null> | null, attributes?: Array<{ __typename?: 'Attribute', short_name?: string | null, value_json?: string | null } | null> | null } | null }; export type InsertDuplicateMutationVariables = Exact<{ duplicate: DuplicateInsertInput; @@ -5923,14 +5140,14 @@ export type FindIncidentQueryVariables = Exact<{ }>; -export type FindIncidentQuery = { __typename?: 'Query', incident?: { __typename?: 'Incident', incident_id: number, title: string, description?: string | null, date: string, editor_similar_incidents?: Array | null, editor_dissimilar_incidents?: Array | null, flagged_dissimilar_incidents?: Array | null, editor_notes?: string | null, editors: Array<{ __typename?: 'User', userId: string, first_name?: string | null, last_name?: string | null } | null>, AllegedDeployerOfAISystem?: Array<{ __typename?: 'Entity', entity_id: string, name: string } | null> | null, AllegedDeveloperOfAISystem?: Array<{ __typename?: 'Entity', entity_id: string, name: string } | null> | null, AllegedHarmedOrNearlyHarmedParties?: Array<{ __typename?: 'Entity', entity_id: string, name: string } | null> | null, nlp_similar_incidents?: Array<{ __typename?: 'IncidentNlp_similar_incident', incident_id?: number | null, similarity?: number | null } | null> | null, reports: Array<{ __typename?: 'Report', report_number: number } | null>, embedding?: { __typename?: 'IncidentEmbedding', from_reports?: Array | null, vector?: Array | null } | null } | null }; +export type FindIncidentQuery = { __typename?: 'Query', incident?: { __typename?: 'Incident', incident_id: number, title: string, description?: string | null, date: string, editor_similar_incidents?: Array | null, editor_dissimilar_incidents?: Array | null, flagged_dissimilar_incidents?: Array | null, editor_notes?: string | null, editors: Array<{ __typename?: 'User', userId: string, first_name?: string | null, last_name?: string | null } | null>, AllegedDeployerOfAISystem?: Array<{ __typename?: 'Entity', entity_id: string, name: string } | null> | null, AllegedDeveloperOfAISystem?: Array<{ __typename?: 'Entity', entity_id: string, name: string } | null> | null, AllegedHarmedOrNearlyHarmedParties?: Array<{ __typename?: 'Entity', entity_id: string, name: string } | null> | null, nlp_similar_incidents?: Array<{ __typename?: 'IncidentNlp_similar_incident', incident_id?: number | null, similarity?: number | null } | null> | null, reports?: Array<{ __typename?: 'Report', report_number: number } | null> | null, embedding?: { __typename?: 'IncidentEmbedding', from_reports?: Array | null, vector?: Array | null } | null } | null }; export type FindIncidentsTableQueryVariables = Exact<{ filter?: InputMaybe; }>; -export type FindIncidentsTableQuery = { __typename?: 'Query', incidents?: Array<{ __typename?: 'Incident', incident_id: number, title: string, description?: string | null, date: string, editors: Array<{ __typename?: 'User', userId: string, first_name?: string | null, last_name?: string | null } | null>, AllegedDeployerOfAISystem?: Array<{ __typename?: 'Entity', entity_id: string, name: string } | null> | null, AllegedDeveloperOfAISystem?: Array<{ __typename?: 'Entity', entity_id: string, name: string } | null> | null, AllegedHarmedOrNearlyHarmedParties?: Array<{ __typename?: 'Entity', entity_id: string, name: string } | null> | null, reports: Array<{ __typename?: 'Report', report_number: number } | null> } | null> | null }; +export type FindIncidentsTableQuery = { __typename?: 'Query', incidents?: Array<{ __typename?: 'Incident', incident_id: number, title: string, description?: string | null, date: string, editors: Array<{ __typename?: 'User', userId: string, first_name?: string | null, last_name?: string | null } | null>, AllegedDeployerOfAISystem?: Array<{ __typename?: 'Entity', entity_id: string, name: string } | null> | null, AllegedDeveloperOfAISystem?: Array<{ __typename?: 'Entity', entity_id: string, name: string } | null> | null, AllegedHarmedOrNearlyHarmedParties?: Array<{ __typename?: 'Entity', entity_id: string, name: string } | null> | null, reports?: Array<{ __typename?: 'Report', report_number: number } | null> | null } | null> | null }; export type FindIncidentEntitiesQueryVariables = Exact<{ filter?: InputMaybe; @@ -5944,7 +5161,7 @@ export type FindIncidentsQueryVariables = Exact<{ }>; -export type FindIncidentsQuery = { __typename?: 'Query', incidents?: Array<{ __typename?: 'Incident', incident_id: number, title: string, description?: string | null, date: string, editor_similar_incidents?: Array | null, editor_dissimilar_incidents?: Array | null, flagged_dissimilar_incidents?: Array | null, editors: Array<{ __typename?: 'User', userId: string, first_name?: string | null, last_name?: string | null } | null>, AllegedDeployerOfAISystem?: Array<{ __typename?: 'Entity', entity_id: string, name: string } | null> | null, AllegedDeveloperOfAISystem?: Array<{ __typename?: 'Entity', entity_id: string, name: string } | null> | null, AllegedHarmedOrNearlyHarmedParties?: Array<{ __typename?: 'Entity', entity_id: string, name: string } | null> | null, nlp_similar_incidents?: Array<{ __typename?: 'IncidentNlp_similar_incident', incident_id?: number | null, similarity?: number | null } | null> | null, reports: Array<{ __typename?: 'Report', report_number: number } | null>, embedding?: { __typename?: 'IncidentEmbedding', from_reports?: Array | null, vector?: Array | null } | null } | null> | null }; +export type FindIncidentsQuery = { __typename?: 'Query', incidents?: Array<{ __typename?: 'Incident', incident_id: number, title: string, description?: string | null, date: string, editor_similar_incidents?: Array | null, editor_dissimilar_incidents?: Array | null, flagged_dissimilar_incidents?: Array | null, editors: Array<{ __typename?: 'User', userId: string, first_name?: string | null, last_name?: string | null } | null>, AllegedDeployerOfAISystem?: Array<{ __typename?: 'Entity', entity_id: string, name: string } | null> | null, AllegedDeveloperOfAISystem?: Array<{ __typename?: 'Entity', entity_id: string, name: string } | null> | null, AllegedHarmedOrNearlyHarmedParties?: Array<{ __typename?: 'Entity', entity_id: string, name: string } | null> | null, nlp_similar_incidents?: Array<{ __typename?: 'IncidentNlp_similar_incident', incident_id?: number | null, similarity?: number | null } | null> | null, reports?: Array<{ __typename?: 'Report', report_number: number } | null> | null, embedding?: { __typename?: 'IncidentEmbedding', from_reports?: Array | null, vector?: Array | null } | null } | null> | null }; export type FindIncidentsTitlesQueryVariables = Exact<{ filter?: InputMaybe; @@ -5959,7 +5176,7 @@ export type UpdateIncidentMutationVariables = Exact<{ }>; -export type UpdateIncidentMutation = { __typename?: 'Mutation', updateOneIncident?: { __typename?: 'Incident', incident_id: number, title: string, description?: string | null, date: string, editor_similar_incidents?: Array | null, editor_dissimilar_incidents?: Array | null, flagged_dissimilar_incidents?: Array | null, editor_notes?: string | null, editors: Array<{ __typename?: 'User', userId: string, first_name?: string | null, last_name?: string | null } | null>, AllegedDeployerOfAISystem?: Array<{ __typename?: 'Entity', entity_id: string, name: string } | null> | null, AllegedDeveloperOfAISystem?: Array<{ __typename?: 'Entity', entity_id: string, name: string } | null> | null, AllegedHarmedOrNearlyHarmedParties?: Array<{ __typename?: 'Entity', entity_id: string, name: string } | null> | null, nlp_similar_incidents?: Array<{ __typename?: 'IncidentNlp_similar_incident', incident_id?: number | null, similarity?: number | null } | null> | null, reports: Array<{ __typename?: 'Report', report_number: number } | null>, embedding?: { __typename?: 'IncidentEmbedding', from_reports?: Array | null, vector?: Array | null } | null } | null }; +export type UpdateIncidentMutation = { __typename?: 'Mutation', updateOneIncident?: { __typename?: 'Incident', incident_id: number, title: string, description?: string | null, date: string, editor_similar_incidents?: Array | null, editor_dissimilar_incidents?: Array | null, flagged_dissimilar_incidents?: Array | null, editor_notes?: string | null, editors: Array<{ __typename?: 'User', userId: string, first_name?: string | null, last_name?: string | null } | null>, AllegedDeployerOfAISystem?: Array<{ __typename?: 'Entity', entity_id: string, name: string } | null> | null, AllegedDeveloperOfAISystem?: Array<{ __typename?: 'Entity', entity_id: string, name: string } | null> | null, AllegedHarmedOrNearlyHarmedParties?: Array<{ __typename?: 'Entity', entity_id: string, name: string } | null> | null, nlp_similar_incidents?: Array<{ __typename?: 'IncidentNlp_similar_incident', incident_id?: number | null, similarity?: number | null } | null> | null, reports?: Array<{ __typename?: 'Report', report_number: number } | null> | null, embedding?: { __typename?: 'IncidentEmbedding', from_reports?: Array | null, vector?: Array | null } | null } | null }; export type UpdateIncidentsMutationVariables = Exact<{ filter: IncidentFilterType; @@ -5986,7 +5203,7 @@ export type FindIncidentFullQueryVariables = Exact<{ }>; -export type FindIncidentFullQuery = { __typename?: 'Query', incident?: { __typename?: 'Incident', incident_id: number, title: string, description?: string | null, date: string, editor_similar_incidents?: Array | null, editor_dissimilar_incidents?: Array | null, flagged_dissimilar_incidents?: Array | null, editor_notes?: string | null, epoch_date_modified?: number | null, editors: Array<{ __typename?: 'User', userId: string, first_name?: string | null, last_name?: string | null } | null>, AllegedDeployerOfAISystem?: Array<{ __typename?: 'Entity', entity_id: string, name: string } | null> | null, AllegedDeveloperOfAISystem?: Array<{ __typename?: 'Entity', entity_id: string, name: string } | null> | null, AllegedHarmedOrNearlyHarmedParties?: Array<{ __typename?: 'Entity', entity_id: string, name: string } | null> | null, nlp_similar_incidents?: Array<{ __typename?: 'IncidentNlp_similar_incident', incident_id?: number | null, similarity?: number | null } | null> | null, reports: Array<{ __typename?: 'Report', submitters: Array, date_published: any, report_number: number, title: string, description?: string | null, url: string, image_url: string, cloudinary_id: string, source_domain: string, text: string, authors: Array, epoch_date_submitted: number, language: string, tags: Array, inputs_outputs?: Array | null } | null>, embedding?: { __typename?: 'IncidentEmbedding', from_reports?: Array | null, vector?: Array | null } | null, tsne?: { __typename?: 'IncidentTsne', x?: number | null, y?: number | null } | null } | null }; +export type FindIncidentFullQuery = { __typename?: 'Query', incident?: { __typename?: 'Incident', incident_id: number, title: string, description?: string | null, date: string, editor_similar_incidents?: Array | null, editor_dissimilar_incidents?: Array | null, flagged_dissimilar_incidents?: Array | null, editor_notes?: string | null, epoch_date_modified?: number | null, editors: Array<{ __typename?: 'User', userId: string, first_name?: string | null, last_name?: string | null } | null>, AllegedDeployerOfAISystem?: Array<{ __typename?: 'Entity', entity_id: string, name: string } | null> | null, AllegedDeveloperOfAISystem?: Array<{ __typename?: 'Entity', entity_id: string, name: string } | null> | null, AllegedHarmedOrNearlyHarmedParties?: Array<{ __typename?: 'Entity', entity_id: string, name: string } | null> | null, nlp_similar_incidents?: Array<{ __typename?: 'IncidentNlp_similar_incident', incident_id?: number | null, similarity?: number | null } | null> | null, reports?: Array<{ __typename?: 'Report', submitters: Array, date_published: any, report_number: number, title: string, description?: string | null, url: string, image_url: string, cloudinary_id: string, source_domain: string, text: string, authors: Array, epoch_date_submitted: number, language: string, tags: Array, inputs_outputs?: Array | null } | null> | null, embedding?: { __typename?: 'IncidentEmbedding', from_reports?: Array | null, vector?: Array | null } | null, tsne?: { __typename?: 'IncidentTsne', x?: number | null, y?: number | null } | null } | null }; export type LogIncidentHistoryMutationVariables = Exact<{ input: History_IncidentInsertInput; @@ -6043,7 +5260,7 @@ export type FindReportWithTranslationsQueryVariables = Exact<{ }>; -export type FindReportWithTranslationsQuery = { __typename?: 'Query', report?: { __typename?: 'Report', url: string, title: string, authors: Array, submitters: Array, date_published: any, date_downloaded: any, date_modified: any, image_url: string, text: string, plain_text: string, tags: Array, flag?: boolean | null, report_number: number, editor_notes?: string | null, language: string, is_incident_report?: boolean | null, inputs_outputs?: Array | null, quiet?: boolean | null, translations_es?: { __typename?: 'ReportTranslation', title?: string | null, text?: string | null } | null, translations_en?: { __typename?: 'ReportTranslation', title?: string | null, text?: string | null } | null, translations_fr?: { __typename?: 'ReportTranslation', title?: string | null, text?: string | null } | null, translations_ja?: { __typename?: 'ReportTranslation', title?: string | null, text?: string | null } | null } | null }; +export type FindReportWithTranslationsQuery = { __typename?: 'Query', report?: { __typename?: 'Report', url: string, title: string, authors: Array, submitters: Array, date_published: any, date_downloaded: any, date_modified: any, image_url: string, text: string, plain_text: string, tags: Array, flag?: boolean | null, report_number: number, editor_notes?: string | null, language: string, is_incident_report?: boolean | null, inputs_outputs?: Array | null, quiet?: boolean | null, translations_es?: { __typename?: 'ReportTranslations', title?: string | null, text?: string | null } | null, translations_en?: { __typename?: 'ReportTranslations', title?: string | null, text?: string | null } | null, translations_fr?: { __typename?: 'ReportTranslations', title?: string | null, text?: string | null } | null, translations_ja?: { __typename?: 'ReportTranslations', title?: string | null, text?: string | null } | null } | null }; export type UpdateReportMutationVariables = Exact<{ filter: ReportFilterType; @@ -6065,7 +5282,7 @@ export type LinkReportsToIncidentsMutationVariables = Exact<{ }>; -export type LinkReportsToIncidentsMutation = { __typename?: 'Mutation', linkReportsToIncidents?: Array<{ __typename?: 'Incident', incident_id: number, reports: Array<{ __typename?: 'Report', report_number: number } | null> } | null> | null }; +export type LinkReportsToIncidentsMutation = { __typename?: 'Mutation', linkReportsToIncidents?: Array<{ __typename?: 'Incident', incident_id: number, reports?: Array<{ __typename?: 'Report', report_number: number } | null> | null } | null> | null }; export type LogReportHistoryMutationVariables = Exact<{ input: History_ReportInsertInput; @@ -6234,7 +5451,7 @@ export type FindIncidentVariantsQueryVariables = Exact<{ }>; -export type FindIncidentVariantsQuery = { __typename?: 'Query', incident?: { __typename?: 'Incident', incident_id: number, reports: Array<{ __typename?: 'Report', report_number: number, title: string, text: string, url: string, source_domain: string, date_published: any, tags: Array, inputs_outputs?: Array | null } | null> } | null }; +export type FindIncidentVariantsQuery = { __typename?: 'Query', incident?: { __typename?: 'Incident', incident_id: number, reports?: Array<{ __typename?: 'Report', report_number: number, title: string, text: string, url: string, source_domain: string, date_published: any, tags: Array, inputs_outputs?: Array | null } | null> | null } | null }; export type FindVariantQueryVariables = Exact<{ filter?: InputMaybe; @@ -6266,8 +5483,8 @@ export type DeleteOneVariantMutationVariables = Exact<{ export type DeleteOneVariantMutation = { __typename?: 'Mutation', deleteOneReport?: { __typename?: 'Report', report_number: number } | null }; -export const FindClassificationsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"FindClassifications"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"query"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"ClassificationQueryInput"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"classifications"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"query"},"value":{"kind":"Variable","name":{"kind":"Name","value":"query"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"_id"}},{"kind":"Field","name":{"kind":"Name","value":"incidents"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"incident_id"}}]}},{"kind":"Field","name":{"kind":"Name","value":"reports"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"report_number"}}]}},{"kind":"Field","name":{"kind":"Name","value":"notes"}},{"kind":"Field","name":{"kind":"Name","value":"namespace"}},{"kind":"Field","name":{"kind":"Name","value":"attributes"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"short_name"}},{"kind":"Field","name":{"kind":"Name","value":"value_json"}}]}},{"kind":"Field","name":{"kind":"Name","value":"publish"}}]}}]}}]} as unknown as DocumentNode; -export const UpsertClassificationDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"UpsertClassification"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"query"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"ClassificationQueryInput"}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"data"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"ClassificationInsertInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"upsertOneClassification"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"query"},"value":{"kind":"Variable","name":{"kind":"Name","value":"query"}}},{"kind":"Argument","name":{"kind":"Name","value":"data"},"value":{"kind":"Variable","name":{"kind":"Name","value":"data"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"_id"}},{"kind":"Field","name":{"kind":"Name","value":"incidents"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"incident_id"}}]}},{"kind":"Field","name":{"kind":"Name","value":"reports"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"report_number"}}]}},{"kind":"Field","name":{"kind":"Name","value":"notes"}},{"kind":"Field","name":{"kind":"Name","value":"namespace"}},{"kind":"Field","name":{"kind":"Name","value":"attributes"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"short_name"}},{"kind":"Field","name":{"kind":"Name","value":"value_json"}}]}},{"kind":"Field","name":{"kind":"Name","value":"publish"}}]}}]}}]} as unknown as DocumentNode; +export const FindClassificationsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"FindClassifications"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"filter"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"ClassificationFilterType"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"classifications"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"filter"},"value":{"kind":"Variable","name":{"kind":"Name","value":"filter"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"_id"}},{"kind":"Field","name":{"kind":"Name","value":"incidents"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"incident_id"}}]}},{"kind":"Field","name":{"kind":"Name","value":"reports"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"report_number"}}]}},{"kind":"Field","name":{"kind":"Name","value":"notes"}},{"kind":"Field","name":{"kind":"Name","value":"namespace"}},{"kind":"Field","name":{"kind":"Name","value":"attributes"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"short_name"}},{"kind":"Field","name":{"kind":"Name","value":"value_json"}}]}},{"kind":"Field","name":{"kind":"Name","value":"publish"}}]}}]}}]} as unknown as DocumentNode; +export const UpsertClassificationDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"UpsertClassification"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"filter"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"ClassificationFilterType"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"update"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"ClassificationInsertType"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"upsertOneClassification"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"filter"},"value":{"kind":"Variable","name":{"kind":"Name","value":"filter"}}},{"kind":"Argument","name":{"kind":"Name","value":"update"},"value":{"kind":"Variable","name":{"kind":"Name","value":"update"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"_id"}},{"kind":"Field","name":{"kind":"Name","value":"incidents"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"incident_id"}}]}},{"kind":"Field","name":{"kind":"Name","value":"reports"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"report_number"}}]}},{"kind":"Field","name":{"kind":"Name","value":"notes"}},{"kind":"Field","name":{"kind":"Name","value":"namespace"}},{"kind":"Field","name":{"kind":"Name","value":"attributes"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"short_name"}},{"kind":"Field","name":{"kind":"Name","value":"value_json"}}]}},{"kind":"Field","name":{"kind":"Name","value":"publish"}}]}}]}}]} as unknown as DocumentNode; export const InsertDuplicateDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"InsertDuplicate"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"duplicate"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"DuplicateInsertInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"insertOneDuplicate"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"data"},"value":{"kind":"Variable","name":{"kind":"Name","value":"duplicate"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"duplicate_incident_number"}},{"kind":"Field","name":{"kind":"Name","value":"true_incident_number"}}]}}]}}]} as unknown as DocumentNode; export const UpsertEntityDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"UpsertEntity"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"filter"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"EntityFilterType"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"update"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"EntityInsertType"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"upsertOneEntity"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"filter"},"value":{"kind":"Variable","name":{"kind":"Name","value":"filter"}}},{"kind":"Argument","name":{"kind":"Name","value":"update"},"value":{"kind":"Variable","name":{"kind":"Name","value":"update"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"entity_id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}}]}}]}}]} as unknown as DocumentNode; export const FindEntitiesDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"FindEntities"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"entities"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"entity_id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}}]}}]}}]} as unknown as DocumentNode; diff --git a/site/gatsby-site/server/local.ts b/site/gatsby-site/server/local.ts index 7583e06d6e..45b0a8369c 100644 --- a/site/gatsby-site/server/local.ts +++ b/site/gatsby-site/server/local.ts @@ -39,6 +39,17 @@ import { permissions as submissionsPermissions } from './fields/submissions'; +import { + queryFields as classificationsQueryFields, + mutationFields as classificationsMutationFields, + permissions as classificationsPermissions +} from './fields/classifications'; + +import { + queryFields as taxaQueryFields, + permissions as taxaPermissions +} from './fields/taxa'; + export const getSchema = () => { @@ -55,6 +66,8 @@ export const getSchema = () => { ...entitiesQueryFields, ...usersQueryFields, ...submissionsQueryFields, + ...classificationsQueryFields, + ...taxaQueryFields, } }); @@ -67,6 +80,7 @@ export const getSchema = () => { ...entitiesMutationFields, ...usersMutationFields, ...submissionsMutationFields, + ...classificationsMutationFields, } }); @@ -100,6 +114,8 @@ export const getSchema = () => { ...entitiesPermissions.Query, ...usersPermissions.Query, ...submissionsPermissions.Query, + ...classificationsPermissions.Query, + ...taxaPermissions.Query, }, Mutation: { "*": deny, @@ -109,6 +125,7 @@ export const getSchema = () => { ...entitiesPermissions.Mutation, ...usersPermissions.Mutation, ...submissionsPermissions.Mutation, + ...classificationsPermissions.Mutation, }, }, { diff --git a/site/gatsby-site/server/remote.ts b/site/gatsby-site/server/remote.ts index 6e08c941e4..583834ba85 100644 --- a/site/gatsby-site/server/remote.ts +++ b/site/gatsby-site/server/remote.ts @@ -24,30 +24,50 @@ const userExecutor = buildHTTPExecutor({ }, }); -// TODO: uncomment items marked with * after classifications and subscription collections are migrated +// TODO: uncomment items after the subscription collection is migrated const ignoreTypes = [ 'Quickadd', 'QuickaddQueryInput', - - // 'Report', * - // 'ReportQueryInput', * + 'QuickaddUpdateInput', + 'QuickaddInsertInput', + + 'Classification', + 'ClassificationUpdateInput', + 'ClassificationInsertInput', + 'ClassificationQueryInput', + 'ClassificationIncidentsRelationInput', + 'ClassificationReportsRelationInput', + + 'Report', + 'ReportQueryInput', + 'ReportUpdateInput', + 'ReportInsertInput', 'ReportUserRelationInput', + 'CreateVariantInput', - // 'Incident', * - // 'IncidentQueryInput', * + // 'Incident', + 'IncidentQueryInput', + 'IncidentUpdateInput', + 'IncidentInsertInput', 'IncidentEditorsRelationInput', 'IncidentReportsRelationInput', 'LinkReportsToIncidentsInput', - // 'Entity', * - // 'EntityQueryInput', * + // 'Entity', + 'EntityQueryInput', + 'EntityUpdateInput', + 'EntityInsertInput', - // 'User', * - // 'UserQueryInput', * + // 'User', + 'UserQueryInput', + 'UserUpdateInput', + 'UserInsertInput', 'Submission', 'SubmissionQueryInput', + 'SubmissionUpdateInput', + 'SubmissionInsertInput', 'SubmissionDeployersRelationInput', 'SubmissionDevelopersRelationInput', 'SubmissionHarmed_partiesRelationInput', @@ -59,12 +79,20 @@ const ignoreTypes = [ 'UpdateOneReportTranslationInput', 'CreateVariantInputVariant', + + 'Taxa', + 'TaxaQueryInput', + 'TaxaUpdateInput', + 'TaxaInsertInput', ]; const ignoredQueries = [ 'quickadd', 'quickadds', + 'classification', + 'classifications', + 'report', 'reports', @@ -79,6 +107,9 @@ const ignoredQueries = [ 'submission', 'submissions', + + 'taxa', + 'taxas', ]; const ignoredMutations = [ @@ -91,6 +122,15 @@ const ignoredMutations = [ 'upsertOneQuickadd', 'upsertManyQuickadds', + 'deleteOneClassification', + 'deleteManyClassifications', + 'insertOneClassification', + 'insertManyClassifications', + 'updateOneClassification', + 'updateManyClassifications', + 'upsertOneClassification', + 'upsertManyClassifications', + 'deleteOneReport', 'deleteManyReports', 'insertOneReport', @@ -99,7 +139,7 @@ const ignoredMutations = [ 'updateManyReports', 'upsertOneReport', 'upsertManyReports', - + 'createVariant', 'linkReportsToIncidents', @@ -142,6 +182,15 @@ const ignoredMutations = [ 'promoteSubmissionToReport', 'updateOneReportTranslation', + + 'deleteOneTaxa', + 'deleteManyTaxas', + 'insertOneTaxa', + 'insertManyTaxas', + 'updateOneTaxa', + 'updateManyTaxas', + 'upsertOneTaxa', + 'upsertManyTaxas', ] /** diff --git a/site/gatsby-site/server/schema.ts b/site/gatsby-site/server/schema.ts index ffd42f9463..63f7c6d658 100644 --- a/site/gatsby-site/server/schema.ts +++ b/site/gatsby-site/server/schema.ts @@ -13,25 +13,10 @@ const gatewaySchema = stitchSchemas({ subschemas: [localSchema, remoteSchema], typeMergingOptions: { validationScopes: { - 'Report.translations': { - validationLevel: ValidationLevel.Off, // TODO: delete this once schema is completely migrated - }, - 'Report.translations.input': { - validationLevel: ValidationLevel.Off, // ditto - }, - ['Incident.reports']: { - validationLevel: ValidationLevel.Off, // ditto - }, - ['CreateVariantInput.incidentId']: { - validationLevel: ValidationLevel.Off // ditto - }, - ['CreateVariantInput.variant']: { + ['Incident.editors']: { validationLevel: ValidationLevel.Off, }, - 'Incident.editors': { - validationLevel: ValidationLevel.Off, // ditto - }, - }, + } } }); diff --git a/site/gatsby-site/server/tests/fixtures/classifications.ts b/site/gatsby-site/server/tests/fixtures/classifications.ts new file mode 100644 index 0000000000..9cc04afb05 --- /dev/null +++ b/site/gatsby-site/server/tests/fixtures/classifications.ts @@ -0,0 +1,402 @@ +import { ObjectId } from "bson"; +import { Fixture } from "../utils"; +import { Classification, ClassificationInsertType } from "../../generated/graphql"; + +type DBClassification = Omit + & { reports: number[] } + & { incidents: number[] } + +const subscriber = { + _id: new ObjectId('60a7c5b7b4f5b8a6d8f9c7e6'), + first_name: 'Subscriber', + last_name: 'One', + roles: ['subscriber'], + userId: 'subscriber1', +} + +const editor1 = { + _id: new ObjectId('60a7c5b7b4f5b8a6d8f9c7e4'), + first_name: 'John', + last_name: 'Doe', + roles: ['incident_editor'], + userId: 'editor1', +}; + +const anonymous = { + _id: new ObjectId('60a7c5b7b4f5b8a6d8f9c7e5'), + first_name: 'Anon', + last_name: 'Anon', + roles: [], + userId: 'anon', +}; + +const report1 = { + _id: new ObjectId('60a7c5b7b4f5b8a6d8f9c7e4'), + authors: ["Author 1", "Author 2"], + cloudinary_id: "sample_cloudinary_id", + date_downloaded: "2021-09-14T00:00:00.000Z", + date_modified: "2021-09-14T00:00:00.000Z", + date_published: "2021-09-14T00:00:00.000Z", + date_submitted: "2021-09-14T00:00:00.000Z", + description: "Sample description", + editor_notes: "Sample editor notes", + embedding: { + from_text_hash: "sample_hash", + vector: [0.1, 0.2, 0.3] + }, + epoch_date_downloaded: 1631577600, + epoch_date_modified: 1631577600, + epoch_date_published: 1631577600, + epoch_date_submitted: 1631577600, + flag: false, + image_url: "http://example.com/image.png", + inputs_outputs: ["input1", "output1"], + is_incident_report: true, + language: "en", + plain_text: "Sample plain text", + report_number: 1, + source_domain: "example.com", + submitters: ["Submitter 1", "Submitter 2"], + tags: ["tag1", "tag2"], + text: "Sample text", + title: "Sample title", + url: "http://example.com", + user: "editor1", + quiet: false, +} + +const incident1 = { + _id: new ObjectId("60a7c5b7b4f5b8a6d8f9c7e0"), + incident_id: 1, + date: "2023-01-14T00:00:00.000Z", + "Alleged deployer of AI system": [], + "Alleged developer of AI system": [], + "Alleged harmed or nearly harmed parties": [], + description: "Test description 1", + title: "Test Incident 1", + editors: [ + "editor1" + ], + nlp_similar_incidents: [ + { + incident_id: 2, + similarity: 0.9 + }, + { + incident_id: 3, + similarity: 0.85 + } + ], + editor_similar_incidents: [], + editor_dissimilar_incidents: [], + flagged_dissimilar_incidents: [], + embedding: { + vector: [ + 0.1, + 0.2, + ], + from_reports: [ + 105, + 104, + ], + }, + tsne: { + x: -0.1, + y: -0.2 + }, + reports: [1], +}; + +const classification1: DBClassification = { + _id: new ObjectId("63f3d58c26ab981f33b3f9c7"), + attributes: [ + { + short_name: "Harm Distribution Basis", + value_json: "[]" + }, + { + short_name: "Sector of Deployment", + value_json: "[]" + }, + ], + publish: false, + reports: [1], + incidents: [1], + namespace: "CSETv1", + notes: "", +} + +const classification2: DBClassification = { + _id: new ObjectId("63f3d58c26ab981f33b3f9c8"), + attributes: [ + { + short_name: "Harm Distribution Basis", + value_json: "[]" + }, + { + short_name: "Sector of Deployment", + value_json: "[]" + }, + ], + publish: false, + reports: [], + incidents: [1], + namespace: "CSETv2", + notes: "", +} + +const taxa1 = { + _id: new ObjectId("64c27aef6bd9217f29557f14"), + namespace: "CSETv1", + weight: 70, + complete_entities: true, + description: "# The CSET Taxonomy 1", + dummy_fields: [ + { + field_number: "1", + short_name: "Test" + }, + ], + field_list: [ + { + field_number: "1.1", + short_name: "Harm Distribution Basis", + long_name: "If harms were potentially unevenly distributed among people, on what basis?", + short_description: "Indicates how the harms were potentially distributed.", + long_description: "Multiple can occur.\n\nGenetic information refers to information about a person’s genetic tests or the genetic tests of their relatives. Genetic information can predict the manifestation of a disease or disorder.", + display_type: "multi", + mongo_type: "string", + default: "unclear", + placeholder: "", + permitted_values: [ + "sexual orientation or gender identity", + "veteran status", + "unclear", + "other" + ], + weight: 60, + instant_facet: false, + required: false, + public: true + }, + { + field_number: "1.2", + short_name: "Sector of Deployment", + long_name: "In which sector was the AI system deployed?", + short_description: "Indicates the sector in which the AI system was deployed.", + long_description: "Multiple can occur.\n\nGenetic information refers to information about a person’s genetic tests or the genetic tests of their relatives. Genetic information can predict the manifestation of a disease or disorder.", + display_type: "multi", + mongo_type: "string", + default: "unclear", + placeholder: "", + permitted_values: [ + "healthcare", + "finance", + "education", + "other" + ], + weight: 40, + instant_facet: false, + required: false, + public: true + }, + ], + created_at: new Date("1722269934190") +} + +const taxa2 = { + _id: new ObjectId("64c27aef6bd9217f29557f15"), + namespace: "CSETv2", + weight: 70, + complete_entities: true, + description: "# The CSET Taxonomy 2", + dummy_fields: [ + { + field_number: "1", + short_name: "Test" + }, + ], + field_list: [ + { + field_number: "1.1", + short_name: "Harm Distribution Basis", + long_name: "If harms were potentially unevenly distributed among people, on what basis?", + short_description: "Indicates how the harms were potentially distributed.", + long_description: "Multiple can occur.\n\nGenetic information refers to information about a person’s genetic tests or the genetic tests of their relatives. Genetic information can predict the manifestation of a disease or disorder.", + display_type: "multi", + mongo_type: "string", + default: "unclear", + placeholder: "", + permitted_values: [ + "sexual orientation or gender identity", + "veteran status", + "unclear", + "other" + ], + weight: 60, + instant_facet: false, + required: false, + public: true + }, + { + field_number: "1.2", + short_name: "Sector of Deployment", + long_name: "In which sector was the AI system deployed?", + short_description: "Indicates the sector in which the AI system was deployed.", + long_description: "Multiple can occur.\n\nGenetic information refers to information about a person’s genetic tests or the genetic tests of their relatives. Genetic information can predict the manifestation of a disease or disorder.", + display_type: "multi", + mongo_type: "string", + default: "unclear", + placeholder: "", + permitted_values: [ + "healthcare", + "finance", + "education", + "other" + ], + weight: 40, + instant_facet: false, + required: false, + public: true + }, + ], + created_at: new Date("1722269934190") +} + + +const fixture: Fixture = { + name: 'classifications', + query: ` + _id + attributes { + short_name + value_json + } + namespace + notes + publish + incidents { + incident_id + } + reports { + report_number + } + `, + seeds: { + customData: { + users: [ + subscriber, + editor1, + anonymous, + ], + }, + aiidprod: { + taxa: [ + taxa1, + taxa2, + ], + classifications: [ + classification1, + classification2, + ], + incidents: [ + incident1, + ], + reports: [ + report1, + ], + }, + }, + testSingular: { + allowed: [anonymous, subscriber], + denied: [], + filter: { reports: { IN: [1] } }, + result: { + _id: "63f3d58c26ab981f33b3f9c7", + namespace: "CSETv1", + reports: [{ report_number: 1 }], + incidents: [{ incident_id: 1 }], + }, + }, + testPluralFilter: { + allowed: [anonymous, subscriber], + denied: [], + filter: { + _id: { EQ: "63f3d58c26ab981f33b3f9c7" }, + }, + result: [ + { + _id: "63f3d58c26ab981f33b3f9c7", + namespace: "CSETv1", + reports: [{ report_number: 1 }], + incidents: [{ incident_id: 1 }], + } + ] + }, + testPluralSort: { + allowed: [anonymous, subscriber], + denied: [], + sort: { namespace: "ASC" }, + result: [ + { namespace: "CSETv1", _id: "63f3d58c26ab981f33b3f9c7" }, + { namespace: "CSETv2", _id: "63f3d58c26ab981f33b3f9c8" }, + ], + }, + testPluralPagination: { + allowed: [subscriber], + denied: [], + pagination: { limit: 1, skip: 1 }, + sort: { namespace: "ASC" }, + result: [ + { namespace: "CSETv2", _id: "63f3d58c26ab981f33b3f9c8" }, + ] + }, + testUpdateOne: null, + testUpdateMany: null, + testInsertOne: null, + testInsertMany: null, + testDeleteOne: null, + testDeleteMany: null, + testUpsertOne: { + shouldInsert: { + allowed: [editor1], + denied: [anonymous, subscriber], + filter: { incidents: { EQ: 1 }, namespace: { EQ: "CSETv3" } }, + update: { + namespace: "CSETv3", + reports: { link: [1] }, + incidents: { link: [1] }, + publish: true, + notes: "Inserted classification", + }, + result: { + _id: expect.any(String), + namespace: "CSETv3", + reports: [{ report_number: 1 }], + incidents: [{ incident_id: 1 }], + }, + + }, + shouldUpdate: { + allowed: [editor1], + denied: [anonymous, subscriber], + filter: { incidents: { EQ: 1 }, namespace: { EQ: "CSETv2" } }, + update: { + // targets classification2 + namespace: "CSETv2", + reports: { link: [] }, + incidents: { link: [1] }, + publish: true, + notes: "Updated classification", + }, + result: { + _id: "63f3d58c26ab981f33b3f9c8", + namespace: "CSETv2", + reports: [], + incidents: [{ incident_id: 1 }], + }, + } + }, +} + +export default fixture; \ No newline at end of file diff --git a/site/gatsby-site/server/tests/mutation-fields.spec.ts b/site/gatsby-site/server/tests/mutation-fields.spec.ts index ac94fc84d5..ec4f1371ce 100644 --- a/site/gatsby-site/server/tests/mutation-fields.spec.ts +++ b/site/gatsby-site/server/tests/mutation-fields.spec.ts @@ -11,6 +11,7 @@ import entitiesFixture from './fixtures/entities'; import usersFixture from './fixtures/users'; import incidentsFixture from './fixtures/incidents'; import submissionsFixture from './fixtures/submissions'; +import classificationsFixture from './fixtures/classifications'; const fixtures = [ quickaddsFixture, @@ -19,6 +20,7 @@ const fixtures = [ incidentsFixture, usersFixture, submissionsFixture, + classificationsFixture, ] fixtures.forEach((collection) => { diff --git a/site/gatsby-site/server/tests/query-fields.spec.ts b/site/gatsby-site/server/tests/query-fields.spec.ts index 25d75ce508..65bd5abc41 100644 --- a/site/gatsby-site/server/tests/query-fields.spec.ts +++ b/site/gatsby-site/server/tests/query-fields.spec.ts @@ -9,6 +9,7 @@ import entitiesFixture from './fixtures/entities'; import incidentsFixture from './fixtures/incidents'; import usersFixture from './fixtures/users'; import submissionsFixture from './fixtures/submissions'; +import classificationsFixture from './fixtures/classifications'; import * as context from '../context'; @@ -19,6 +20,7 @@ const fixtures = [ incidentsFixture, usersFixture, submissionsFixture, + classificationsFixture, ] fixtures.forEach((collection) => { diff --git a/site/gatsby-site/server/types/classification.ts b/site/gatsby-site/server/types/classification.ts new file mode 100644 index 0000000000..03e52fef32 --- /dev/null +++ b/site/gatsby-site/server/types/classification.ts @@ -0,0 +1,36 @@ +import { GraphQLObjectType, GraphQLString, GraphQLBoolean, GraphQLList, GraphQLInt, GraphQLNonNull, GraphQLID } from 'graphql'; +import { ObjectIdScalar } from '../scalars'; +import { IncidentType } from './incidents'; +import { getListRelationshipConfig } from '../utils'; +import { ReportType } from './report'; + +const AttributeType = new GraphQLObjectType({ + name: 'Attribute', + fields: { + short_name: { type: GraphQLString }, + value_json: { type: GraphQLString } + } +}); + +export const ClassificationType = new GraphQLObjectType({ + name: 'Classification', + fields: { + _id: { type: ObjectIdScalar }, + attributes: { type: new GraphQLList(AttributeType) }, + incidents: getListRelationshipConfig(IncidentType, GraphQLInt, 'incidents', 'incident_id', 'incidents', 'aiidprod'), + namespace: { type: new GraphQLNonNull(GraphQLString) }, + notes: { type: GraphQLString }, + publish: { type: GraphQLBoolean }, + reports: getListRelationshipConfig(ReportType, GraphQLInt, 'reports', 'report_number', 'reports', 'aiidprod') + } +}); + + +// dependencies property gets ignored by newest graphql package so we have to add it manually after the type is created +// ideally should be fixed in the graphql-to-mongodb package + +//@ts-ignore +ClassificationType.getFields().reports.dependencies = ['reports']; + +//@ts-ignore +ClassificationType.getFields().incidents.dependencies = ['incidents']; \ No newline at end of file diff --git a/site/gatsby-site/server/types/taxa.ts b/site/gatsby-site/server/types/taxa.ts new file mode 100644 index 0000000000..91493b2a40 --- /dev/null +++ b/site/gatsby-site/server/types/taxa.ts @@ -0,0 +1,116 @@ +import { + GraphQLObjectType, + GraphQLString, + GraphQLBoolean, + GraphQLList, + GraphQLInt, +} from 'graphql'; +import { ObjectIdScalar } from '../scalars'; + +const CompleteFromType = new GraphQLObjectType({ + name: 'CompleteFrom', + fields: { + all: { type: new GraphQLList(GraphQLString) }, + current: { type: new GraphQLList(GraphQLString) }, + }, +}); + +const ItemFieldsType = new GraphQLObjectType({ + name: 'ItemFields', + fields: { + complete_from: { type: CompleteFromType }, + default: { type: GraphQLString }, + display_type: { type: GraphQLString }, + field_number: { type: GraphQLString }, + instant_facet: { type: GraphQLBoolean }, + long_description: { type: GraphQLString }, + long_name: { type: GraphQLString }, + mongo_type: { type: GraphQLString }, + permitted_values: { type: new GraphQLList(GraphQLString) }, + placeholder: { type: GraphQLString }, + public: { type: GraphQLBoolean }, + required: { type: GraphQLBoolean }, + short_description: { type: GraphQLString }, + short_name: { type: GraphQLString }, + weight: { type: GraphQLInt }, + }, +}); + +const SubfieldCompleteFromType = new GraphQLObjectType({ + name: 'SubfieldCompleteFrom', + fields: { + all: { type: new GraphQLList(GraphQLString) }, + current: { type: new GraphQLList(GraphQLString) }, + entities: { type: GraphQLBoolean } + } +}); + +const SubfieldType = new GraphQLObjectType({ + name: 'Subfield', + fields: { + field_number: { type: GraphQLString }, + short_name: { type: GraphQLString }, + long_name: { type: GraphQLString }, + short_description: { type: GraphQLString }, + long_description: { type: GraphQLString }, + display_type: { type: GraphQLString }, + mongo_type: { type: GraphQLString }, + default: { type: GraphQLString }, + placeholder: { type: GraphQLString }, + permitted_values: { type: new GraphQLList(GraphQLString) }, + weight: { type: GraphQLInt }, + instant_facet: { type: GraphQLBoolean }, + required: { type: GraphQLBoolean }, + public: { type: GraphQLBoolean }, + complete_from: { type: SubfieldCompleteFromType }, + hide_search: { type: GraphQLBoolean }, + } +}); + +const FieldListType = new GraphQLObjectType({ + name: 'FieldList', + fields: { + complete_from: { type: CompleteFromType }, + default: { type: GraphQLString }, + display_type: { type: GraphQLString }, + field_number: { type: GraphQLString }, + instant_facet: { type: GraphQLBoolean }, + item_fields: { type: ItemFieldsType }, + long_description: { type: GraphQLString }, + long_name: { type: GraphQLString }, + mongo_type: { type: GraphQLString }, + permitted_values: { type: new GraphQLList(GraphQLString) }, + placeholder: { type: GraphQLString }, + public: { type: GraphQLBoolean }, + hide_search: { type: GraphQLBoolean }, + required: { type: GraphQLBoolean }, + short_description: { type: GraphQLString }, + short_name: { type: GraphQLString }, + weight: { type: GraphQLInt }, + notes: { type: GraphQLString }, + subfields: { type: new GraphQLList(SubfieldType) }, + }, +}); + +const DummyFieldsType = new GraphQLObjectType({ + name: 'DummyFields', + fields: { + field_number: { type: GraphQLString }, + short_name: { type: GraphQLString }, + }, +}); + +const TaxaType = new GraphQLObjectType({ + name: 'Taxa', + fields: { + _id: { type: ObjectIdScalar }, + complete_entities: { type: GraphQLBoolean }, + description: { type: GraphQLString }, + dummy_fields: { type: new GraphQLList(DummyFieldsType) }, + field_list: { type: new GraphQLList(FieldListType) }, + namespace: { type: GraphQLString }, + weight: { type: GraphQLInt }, + }, +}); + +export { TaxaType }; \ No newline at end of file diff --git a/site/gatsby-site/server/utils.ts b/site/gatsby-site/server/utils.ts index a9111e80b4..285991624e 100644 --- a/site/gatsby-site/server/utils.ts +++ b/site/gatsby-site/server/utils.ts @@ -487,7 +487,7 @@ class LinkValidationError extends Error { * * @async */ -async function parseRelationshipFields(Type: GraphQLObjectType, updateArg: Record, updateObj: UpdateObj, context: Context): Promise | Error> { +async function parseRelationshipFields(Type: GraphQLObjectType, updateArg: Record, updateObj: UpdateObj, context: Context) { const parsedUpdate: any = {}; @@ -500,7 +500,6 @@ async function parseRelationshipFields(Type: GraphQLObjectType, updateArg: Recor const name = key.slice(0, -5); const field = fields[name]; - await (field.extensions!.relationship as any).linkValidation(updateArg, context); parsedUpdate[name] = value; diff --git a/site/gatsby-site/src/components/cite/RemoveDuplicateModal.js b/site/gatsby-site/src/components/cite/RemoveDuplicateModal.js index f44b51522d..7d58dcfee6 100644 --- a/site/gatsby-site/src/components/cite/RemoveDuplicateModal.js +++ b/site/gatsby-site/src/components/cite/RemoveDuplicateModal.js @@ -17,13 +17,13 @@ export default function RemoveDuplicateModal({ incident, show, onClose }) { const [insertDuplicate] = useMutation(INSERT_DUPLICATE); - const [updateClassification] = useMutation(UPSERT_CLASSIFICATION); + const [upsertClassification] = useMutation(UPSERT_CLASSIFICATION); const [updateSubscription] = useMutation(UPSERT_SUBSCRIPTION); const { data: classificationsData, loading: classificationsLoading } = useQuery( FIND_CLASSIFICATION, - { variables: { query: { incidents: { incident_id: incident.incident_id } } } } + { variables: { filter: { incidents: { incident_id: { EQ: incident.incident_id } } } } } ); const { data: subscriptionsData, loading: subscriptionsLoading } = useQuery( @@ -103,10 +103,10 @@ export default function RemoveDuplicateModal({ incident, show, onClose }) { try { for (const classification of classificationsData.classifications) { - await updateClassification({ + await upsertClassification({ variables: { - query: { _id: classification._id }, - data: { + filter: { _id: { EQ: classification._id } }, + update: { ...classification, incidents: { link: classification.incidents diff --git a/site/gatsby-site/src/components/classifications/CsetTable.js b/site/gatsby-site/src/components/classifications/CsetTable.js index 5a222cebe8..0b9948ffca 100644 --- a/site/gatsby-site/src/components/classifications/CsetTable.js +++ b/site/gatsby-site/src/components/classifications/CsetTable.js @@ -338,7 +338,7 @@ function TableWrap({ data, setData, className, ...props }) { } export default function CsetTable({ data, taxa, incident_id, ...props }) { - const [updateClassification] = useMutation(UPSERT_CLASSIFICATION); + const [upsertClassification] = useMutation(UPSERT_CLASSIFICATION); const addToast = useToastContext(); @@ -415,15 +415,13 @@ export default function CsetTable({ data, taxa, incident_id, ...props }) { reports: { link: [] }, }; - await updateClassification({ + await upsertClassification({ variables: { - query: { - incidents: { - incident_id: parseInt(incident_id), - }, - namespace: 'CSETv1', + filter: { + incidents: { EQ: parseInt(incident_id) }, + namespace: { EQ: 'CSETv1' }, }, - data, + update: data, }, }); diff --git a/site/gatsby-site/src/components/taxa/ClassificationsEditor.js b/site/gatsby-site/src/components/taxa/ClassificationsEditor.js index 64a6350f91..bce61f279d 100644 --- a/site/gatsby-site/src/components/taxa/ClassificationsEditor.js +++ b/site/gatsby-site/src/components/taxa/ClassificationsEditor.js @@ -34,12 +34,12 @@ export default function TaxonomiesEditor({ setCanEditTaxonomies(taxa.nodes.some((t) => canEditTaxonomy(t.namespace))); }, [user]); - const incidentsQuery = incidentId ? { incidents: { incident_id: incidentId } } : {}; + const incidentsQuery = incidentId ? { incidents: { EQ: incidentId } } : {}; - const reportsQuery = reportNumber ? { reports: { report_number: reportNumber } } : {}; + const reportsQuery = reportNumber ? { reports: { EQ: reportNumber } } : {}; const { data } = useQuery(FIND_CLASSIFICATION, { - variables: { query: { ...incidentsQuery, ...reportsQuery } }, + variables: { filter: { ...incidentsQuery, ...reportsQuery } }, }); const [taxonomies, setTaxonomies] = useState([]); @@ -109,7 +109,8 @@ export default function TaxonomiesEditor({ taxonomiesList.some((t) => t.namespace === query.edit_taxonomy) ) { setScrolledToTaxonomy(query.edit_taxonomy); - taxonomiesList.find((t) => t.namespace === query.edit_taxonomy).ref.current.scrollIntoView(); + // TODO: this has a race condition where the ref is not yet set + taxonomiesList.find((t) => t.namespace === query.edit_taxonomy).ref.current?.scrollIntoView(); } }, [taxonomiesList]); diff --git a/site/gatsby-site/src/components/taxa/TaxonomyForm.js b/site/gatsby-site/src/components/taxa/TaxonomyForm.js index 6a406e560d..33ea6b3f9e 100644 --- a/site/gatsby-site/src/components/taxa/TaxonomyForm.js +++ b/site/gatsby-site/src/components/taxa/TaxonomyForm.js @@ -1,8 +1,6 @@ import React, { useState, useEffect, forwardRef, useImperativeHandle, useRef } from 'react'; import { Form, Formik } from 'formik'; import { useMutation, useQuery, useApolloClient } from '@apollo/client'; -import gql from 'graphql-tag'; - import { FIND_CLASSIFICATION, UPSERT_CLASSIFICATION } from '../../graphql/classifications'; import Loader from 'components/ui/Loader'; import useToastContext, { SEVERITY } from 'hooks/useToast'; @@ -14,6 +12,7 @@ import TextInputGroup from 'components/forms/TextInputGroup'; import Card from 'elements/Card'; import SubmitButton from 'components/ui/SubmitButton'; import { uniq } from 'lodash'; +import { FIND_ENTITIES } from '../../graphql/entities'; const TaxonomyForm = forwardRef(function TaxonomyForm( { taxonomy, incidentId, reportNumber, onSubmit, active }, @@ -45,18 +44,20 @@ const TaxonomyForm = forwardRef(function TaxonomyForm( }, })); - const incidentsQuery = incidentId ? { incidents: { incident_id: incidentId } } : {}; + const incidentsQuery = incidentId ? { incidents: { EQ: incidentId } } : {}; - const reportsQuery = reportNumber ? { reports: { report_number: reportNumber } } : {}; + const reportsQuery = reportNumber ? { reports: { EQ: reportNumber } } : {}; const { data: classificationsData } = useQuery(FIND_CLASSIFICATION, { - variables: { query: { ...incidentsQuery, ...reportsQuery }, namespace }, + variables: { filter: { ...incidentsQuery, ...reportsQuery }, namespace: { EQ: namespace } }, skip: !active, }); //TODO: why does this fetch all classifications? 🤔 const { data: allClassificationsData } = useQuery(FIND_CLASSIFICATION, { - variables: { query: { namespace: taxonomy.namespace } }, + variables: { + filter: { namespace: { EQ: taxonomy.namespace } }, + }, skip: !active, }); @@ -71,13 +72,7 @@ const TaxonomyForm = forwardRef(function TaxonomyForm( if (taxonomy.complete_entities) { setEntitiesData( await client.query({ - query: gql` - query FindEntities { - entities { - name - } - } - `, + query: FIND_ENTITIES, }) ); } @@ -169,7 +164,6 @@ const TaxonomyForm = forwardRef(function TaxonomyForm( ); const data = { - __typename: undefined, notes: values.notes, publish: values.publish, attributes: attributes.map((a) => a), @@ -201,12 +195,12 @@ const TaxonomyForm = forwardRef(function TaxonomyForm( await upsertClassification({ variables: { - query: { + filter: { ...incidentsQuery, ...reportsQuery, - namespace, + namespace: { EQ: namespace }, }, - data, + update: data, }, }); } catch (e) { diff --git a/site/gatsby-site/src/contexts/userContext/UserContextProvider.js b/site/gatsby-site/src/contexts/userContext/UserContextProvider.js index 42d3bccb0c..9766aec582 100644 --- a/site/gatsby-site/src/contexts/userContext/UserContextProvider.js +++ b/site/gatsby-site/src/contexts/userContext/UserContextProvider.js @@ -201,6 +201,13 @@ export const UserContextProvider = ({ children }) => { user, isLoggedIn: user && user.isLoggedIn, isRole(role) { + // This is to allow mocking custom data for testing purposes, (only affects client side features) + if (typeof window !== 'undefined' && window.localStorage.getItem('__CUSTOM_DATA_MOCK')) { + const customData = JSON.parse(window.localStorage.getItem('__CUSTOM_DATA_MOCK')); + + return customData.roles.includes('admin') || customData.roles.includes(role); + } + return ( user && user.isLoggedIn && diff --git a/site/gatsby-site/src/graphql/classifications.js b/site/gatsby-site/src/graphql/classifications.js index 019438714b..555dd877e3 100644 --- a/site/gatsby-site/src/graphql/classifications.js +++ b/site/gatsby-site/src/graphql/classifications.js @@ -1,8 +1,8 @@ import { gql } from '../../server/generated/gql'; export const FIND_CLASSIFICATION = gql(` - query FindClassifications($query: ClassificationQueryInput) { - classifications(query: $query) { + query FindClassifications($filter: ClassificationFilterType) { + classifications(filter: $filter) { _id incidents { incident_id @@ -23,10 +23,10 @@ export const FIND_CLASSIFICATION = gql(` export const UPSERT_CLASSIFICATION = gql(` mutation UpsertClassification( - $query: ClassificationQueryInput - $data: ClassificationInsertInput! + $filter: ClassificationFilterType! + $update: ClassificationInsertType! ) { - upsertOneClassification(query: $query, data: $data) { + upsertOneClassification(filter: $filter, update: $update) { _id incidents { incident_id diff --git a/site/gatsby-site/src/pages/apps/classifications.js b/site/gatsby-site/src/pages/apps/classifications.js index 905ad42e01..132f58f642 100644 --- a/site/gatsby-site/src/pages/apps/classifications.js +++ b/site/gatsby-site/src/pages/apps/classifications.js @@ -188,10 +188,10 @@ export default function ClassificationsDbView(props) { setupTaxonomiesSelect(); }, [isAdmin]); - const fetchClassificationData = async (query) => { + const fetchClassificationData = async (filter) => { const classificationsData = await client.query({ query: FIND_CLASSIFICATION, - variables: { query }, + variables: { filter }, }); // For now we convert the list of attributes into a classifications object @@ -439,7 +439,7 @@ export default function ClassificationsDbView(props) { ]); let rowQuery = { - namespace: currentTaxonomy, + namespace: { EQ: currentTaxonomy }, }; const classificationData = await fetchClassificationData(rowQuery); diff --git a/site/gatsby-site/src/pages/apps/csettool/{mongodbAiidprodIncidents.incident_id}.js b/site/gatsby-site/src/pages/apps/csettool/{mongodbAiidprodIncidents.incident_id}.js index 69d4633866..be88b04c1a 100644 --- a/site/gatsby-site/src/pages/apps/csettool/{mongodbAiidprodIncidents.incident_id}.js +++ b/site/gatsby-site/src/pages/apps/csettool/{mongodbAiidprodIncidents.incident_id}.js @@ -16,7 +16,7 @@ const ToolPage = (props) => { const { data, loading } = useQuery(FIND_CLASSIFICATION, { variables: { - query: { incidents_in: { incident_id: parseInt(incident_id) }, namespace_in: allNamespaces }, + filter: { incidents: { IN: parseInt(incident_id) }, namespace: { IN: allNamespaces } }, }, }); diff --git a/site/gatsby-site/src/pages/incidents/history.js b/site/gatsby-site/src/pages/incidents/history.js index ef706655d0..5467fcbb87 100644 --- a/site/gatsby-site/src/pages/incidents/history.js +++ b/site/gatsby-site/src/pages/incidents/history.js @@ -79,7 +79,7 @@ function IncidentHistoryPage(props) { const { data: classificationsData, loading: loadingIncidentClassifications } = useQuery( FIND_CLASSIFICATION, { - variables: { query: { incidents: { incident_id: incidentId } } }, + variables: { filter: { incidents: { EQ: incidentId } } }, } ); diff --git a/site/gatsby-site/src/templates/citeDynamicTemplate.js b/site/gatsby-site/src/templates/citeDynamicTemplate.js index 11cf2a6234..36752bef22 100644 --- a/site/gatsby-site/src/templates/citeDynamicTemplate.js +++ b/site/gatsby-site/src/templates/citeDynamicTemplate.js @@ -54,7 +54,7 @@ function CiteDynamicTemplate({ }); const { data: classificationsData } = useQuery(FIND_CLASSIFICATION, { - variables: { query: { incidents: { incident_id }, publish: true } }, + variables: { filter: { incidents: { EQ: incident_id }, publish: { EQ: true } } }, }); useEffect(() => { diff --git a/site/gatsby-site/tsconfig.json b/site/gatsby-site/tsconfig.json index e66b1100ad..7f8bb75c84 100644 --- a/site/gatsby-site/tsconfig.json +++ b/site/gatsby-site/tsconfig.json @@ -97,5 +97,8 @@ // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */ "skipLibCheck": true, /* Skip type checking all .d.ts files. */ "allowJs": true - } + }, + "exclude": [ + "public" + ], } \ No newline at end of file From d5eba4f54c59a8407be681d8ad5e758a9419a0d8 Mon Sep 17 00:00:00 2001 From: Cesar Varela Date: Tue, 27 Aug 2024 11:35:46 -0300 Subject: [PATCH 2/6] Use more shards to reduce chances of test server crashing (#3042) * Use more shards to reduce chances of test server crashing * increase shards count --- .github/workflows/test-playwright-full.yml | 6 +++--- .github/workflows/test-playwright.yml | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/test-playwright-full.yml b/.github/workflows/test-playwright-full.yml index 5ed3a02b59..b1aa091a12 100644 --- a/.github/workflows/test-playwright-full.yml +++ b/.github/workflows/test-playwright-full.yml @@ -23,13 +23,13 @@ jobs: tests: name: Run Playwright tests environment: ${{ inputs.environment }} - timeout-minutes: 60 + timeout-minutes: 30 runs-on: ubuntu-latest strategy: fail-fast: false matrix: - shardIndex: [1, 2, 3, 4] - shardTotal: [4] + shardIndex: [1, 2, 3, 4, 5, 6] + shardTotal: [6] steps: - name: Checkout uses: actions/checkout@v4 diff --git a/.github/workflows/test-playwright.yml b/.github/workflows/test-playwright.yml index 91017d53cf..dc69205a54 100644 --- a/.github/workflows/test-playwright.yml +++ b/.github/workflows/test-playwright.yml @@ -23,13 +23,13 @@ jobs: tests: name: Run Playwright tests environment: ${{ inputs.environment }} - timeout-minutes: 60 + timeout-minutes: 30 runs-on: ubuntu-latest strategy: fail-fast: false matrix: - shardIndex: [1, 2, 3, 4] - shardTotal: [4] + shardIndex: [1, 2, 3, 4, 5, 6] + shardTotal: [6] steps: - name: Checkout uses: actions/checkout@v4 From 99602b1d0554eeffdfbf1ae652111f032dbeab81 Mon Sep 17 00:00:00 2001 From: Kevin Date: Wed, 28 Aug 2024 15:25:14 -0300 Subject: [PATCH 3/6] remove warning for removed option `sizeByPixelDensity` (#3016) --- site/gatsby-site/gatsby-config.js | 1 - 1 file changed, 1 deletion(-) diff --git a/site/gatsby-site/gatsby-config.js b/site/gatsby-site/gatsby-config.js index 2b9c669233..d3ac502d00 100755 --- a/site/gatsby-site/gatsby-config.js +++ b/site/gatsby-site/gatsby-config.js @@ -32,7 +32,6 @@ const plugins = [ resolve: 'gatsby-remark-images', options: { maxWidth: 1035, - sizeByPixelDensity: true, }, }, { From cfeed12a3b637b5aa127e52e5949546b98b18658 Mon Sep 17 00:00:00 2001 From: Cesar Varela Date: Thu, 29 Aug 2024 16:49:29 -0300 Subject: [PATCH 4/6] Migrate entityEdit to playwright (#3047) * Migrate entityEdit to playwright * Fix date_modified time check * Delete entityEdit.cy.js * Add missing waitForRequest * Fix entities seed * Fix entityEdit test * Add date_modified to entities and move spec to e2e-full --------- Co-authored-by: Clara Youdale --- .../cypress/e2e/integration/entityEdit.cy.js | 122 ------------------ .../cypress/fixtures/entities/entity.json | 10 -- .../fixtures/entities/updateOneEntity.json | 7 - .../playwright/e2e-full/entityEdit.spec.ts | 106 +++++++++++++++ .../playwright/seeds/aiidprod/entities.ts | 18 ++- site/gatsby-site/server/generated/gql.ts | 4 +- site/gatsby-site/server/generated/graphql.ts | 30 ++--- site/gatsby-site/server/types/entity.ts | 1 + site/gatsby-site/src/graphql/entities.js | 3 +- site/gatsby-site/src/pages/entities/edit.js | 3 +- 10 files changed, 139 insertions(+), 165 deletions(-) delete mode 100644 site/gatsby-site/cypress/e2e/integration/entityEdit.cy.js delete mode 100644 site/gatsby-site/cypress/fixtures/entities/entity.json delete mode 100644 site/gatsby-site/cypress/fixtures/entities/updateOneEntity.json create mode 100644 site/gatsby-site/playwright/e2e-full/entityEdit.spec.ts diff --git a/site/gatsby-site/cypress/e2e/integration/entityEdit.cy.js b/site/gatsby-site/cypress/e2e/integration/entityEdit.cy.js deleted file mode 100644 index 08ae036394..0000000000 --- a/site/gatsby-site/cypress/e2e/integration/entityEdit.cy.js +++ /dev/null @@ -1,122 +0,0 @@ -import { conditionalIt } from '../../support/utils'; -import entity from '../../fixtures/entities/entity.json'; -import updateOneEntity from '../../fixtures/entities/updateOneEntity.json'; - -describe('Edit Entity', () => { - const entity_id = 'google'; - - const url = `/entities/edit?entity_id=${entity_id}`; - - conditionalIt( - !Cypress.env('isEmptyEnvironment') && Cypress.env('e2eUsername') && Cypress.env('e2ePassword'), - 'Should successfully edit Entity fields', - () => { - cy.login(Cypress.env('e2eUsername'), Cypress.env('e2ePassword')); - - cy.visit(url); - - cy.conditionalIntercept( - '**/graphql', - (req) => req.body.operationName == 'FindEntity', - 'FindEntity', - entity - ); - - cy.wait(['@FindEntity']); - - const values = { - name: 'Google new', - }; - - Object.keys(values).forEach((key) => { - cy.get(`[name=${key}]`).clear().type(values[key]); - }); - - cy.conditionalIntercept( - '**/graphql', - (req) => req.body.operationName == 'UpdateEntity', - 'UpdateEntity', - updateOneEntity - ); - - const now = new Date(); - - cy.clock(now); - - cy.contains('button', 'Save').click(); - - const updatedEntity = { - name: values.name, - date_modified: now.toISOString(), - }; - - cy.wait('@UpdateEntity').then((xhr) => { - expect(xhr.request.body.operationName).to.eq('UpdateEntity'); - expect(xhr.request.body.variables.query.entity_id).to.eq(entity_id); - expect(xhr.request.body.variables.set).to.deep.eq(updatedEntity); - }); - - cy.get('.tw-toast').contains('Entity updated successfully.').should('exist'); - } - ); - - conditionalIt( - !Cypress.env('isEmptyEnvironment') && Cypress.env('e2eUsername') && Cypress.env('e2ePassword'), - 'Should display an error message when editing Entity fails', - () => { - cy.login(Cypress.env('e2eUsername'), Cypress.env('e2ePassword')); - - cy.visit(url); - - cy.conditionalIntercept( - '**/graphql', - (req) => req.body.operationName == 'FindEntity', - 'FindEntity', - entity - ); - - cy.wait(['@FindEntity']); - - const values = { - name: 'Google new', - }; - - Object.keys(values).forEach((key) => { - cy.get(`[name=${key}]`).clear().type(values[key]); - }); - - cy.conditionalIntercept( - '**/graphql', - (req) => req.body.operationName == 'UpdateEntity', - 'UpdateEntity', - { - data: null, - errors: [ - { - message: 'Dummy error message', - }, - ], - } - ); - - const now = new Date(); - - cy.clock(now); - - cy.contains('button', 'Save').click(); - - const updatedEntity = { - name: values.name, - date_modified: now.toISOString(), - }; - - cy.wait('@UpdateEntity').then((xhr) => { - expect(xhr.request.body.operationName).to.eq('UpdateEntity'); - expect(xhr.request.body.variables.query.entity_id).to.eq(entity_id); - expect(xhr.request.body.variables.set).to.deep.eq(updatedEntity); - }); - - cy.get('.tw-toast').contains('Error updating Entity.').should('exist'); - } - ); -}); diff --git a/site/gatsby-site/cypress/fixtures/entities/entity.json b/site/gatsby-site/cypress/fixtures/entities/entity.json deleted file mode 100644 index bf937d338e..0000000000 --- a/site/gatsby-site/cypress/fixtures/entities/entity.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "data": { - "entity": { - "entity_id": "google", - "name": "Google", - "created_at": "2024-03-01T21:52:39.877Z", - "date_modified": "2024-03-21T21:52:39.877Z" - } - } -} diff --git a/site/gatsby-site/cypress/fixtures/entities/updateOneEntity.json b/site/gatsby-site/cypress/fixtures/entities/updateOneEntity.json deleted file mode 100644 index be791e188b..0000000000 --- a/site/gatsby-site/cypress/fixtures/entities/updateOneEntity.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "data": { - "updateOneEntity": { - "entity_id": "google" - } - } -} diff --git a/site/gatsby-site/playwright/e2e-full/entityEdit.spec.ts b/site/gatsby-site/playwright/e2e-full/entityEdit.spec.ts new file mode 100644 index 0000000000..251f71aa8f --- /dev/null +++ b/site/gatsby-site/playwright/e2e-full/entityEdit.spec.ts @@ -0,0 +1,106 @@ +import { conditionalIntercept, waitForRequest, test, query } from '../utils'; +import entities from '../seeds/aiidprod/entities'; +import { expect } from '@playwright/test'; +import { init } from '../memory-mongo'; +import gql from 'graphql-tag'; + +test.describe('Edit Entity', () => { + const entity_id = 'entity1'; + const url = `/entities/edit?entity_id=${entity_id}`; + + function isDateApproximatelyEqual(expectedDate, actualDate, toleranceInSeconds = 5) { + const expectedTime = new Date(expectedDate).getTime(); + const actualTime = new Date(actualDate).getTime(); + return Math.abs(expectedTime - actualTime) <= toleranceInSeconds * 1000; + } + + test('Should successfully edit Entity fields', async ({ page, login, skipOnEmptyEnvironment }) => { + + const userId = await login(process.env.E2E_ADMIN_USERNAME, process.env.E2E_ADMIN_PASSWORD); + + await init({ customData: { users: [{ userId, first_name: 'Test', last_name: 'User', roles: ['admin'] }] }, }, { drop: true }); + + await page.goto(url); + + const values = { + name: 'Google new', + }; + + for (const key in values) { + await page.locator(`[name=${key}]`).fill(values[key]); + } + + await page.getByText("Save", { exact: true }).click(); + + const now = new Date(); + + await page.addInitScript(() => { + Date.now = () => now.getTime(); + }); + + await expect(page.locator('.tw-toast')).toContainText('Entity updated successfully.'); + + const { data } = await query({ + query: gql`{ + entity(filter: { entity_id: { EQ: "${entity_id}" } }) { + entity_id + name + created_at + date_modified + } + }`, + }); + + expect(data.entity).toMatchObject({ + entity_id, + created_at: entities[0].created_at, + name: values.name, + }); + + expect(isDateApproximatelyEqual(data.entity.date_modified, now.toISOString())).toBe(true); + + await expect(page.locator('.tw-toast')).toContainText('Entity updated successfully.'); + }); + + + test('Should display an error message when editing Entity fails', + async ({ page, login, skipOnEmptyEnvironment }) => { + const userId = await login(process.env.E2E_ADMIN_USERNAME, process.env.E2E_ADMIN_PASSWORD); + + await init({ customData: { users: [{ userId, first_name: 'Test', last_name: 'User', roles: ['admin'] }] }, }, { drop: true }); + + await page.goto(url); + + const values = { + name: 'Google new', + }; + + for (const key in values) { + await page.locator(`[name=${key}]`).fill(values[key]); + } + + await conditionalIntercept( + page, + '**/graphql', + (req) => req.postDataJSON().operationName === 'UpdateEntity', + { + data: null, + errors: [ + { + message: 'Dummy error message', + }, + ], + }, + 'UpdateEntity' + ); + + await page.getByText("Save", { exact: true }).click(); + + const updateEntityRequest = await waitForRequest('UpdateEntity'); + expect(updateEntityRequest.postDataJSON().variables.filter.entity_id.EQ).toBe(entity_id); + expect(updateEntityRequest.postDataJSON().variables.update.set.name).toBe(values.name); + + await expect(page.locator('.tw-toast')).toContainText('Error updating Entity.'); + } + ); +}); diff --git a/site/gatsby-site/playwright/seeds/aiidprod/entities.ts b/site/gatsby-site/playwright/seeds/aiidprod/entities.ts index bef894b364..c63ddbffef 100644 --- a/site/gatsby-site/playwright/seeds/aiidprod/entities.ts +++ b/site/gatsby-site/playwright/seeds/aiidprod/entities.ts @@ -6,36 +6,42 @@ const entities: DBEntity[] = [ { entity_id: 'entity1', name: 'Entity 1', - created_at: new Date(1609459200000).toString(), + created_at: new Date(1609459200000).toISOString(), + date_modified: new Date(1609459200000).toISOString(), }, { entity_id: 'entity2', name: 'Entity 2', - created_at: new Date(1609459200000).toString(), + created_at: new Date(1609459200000).toISOString(), + date_modified: new Date(1609459200000).toISOString(), }, { entity_id: 'entity3', name: 'Entity 3', - created_at: new Date(1609459200000).toString(), + created_at: new Date(1609459200000).toISOString(), + date_modified: new Date(1609459200000).toISOString(), }, { entity_id: 'starbucks', name: 'Starbucks', - created_at: new Date(1609459200000).toString(), + created_at: new Date(1609459200000).toISOString(), + date_modified: new Date(1609459200000).toISOString(), }, { entity_id: 'kronos', name: 'Kronos', - created_at: new Date(1609459200000).toString(), + created_at: new Date(1609459200000).toISOString(), + date_modified: new Date(1609459200000).toISOString(), }, { entity_id: 'starbucks-employees', name: 'Starbucks Employees', - created_at: new Date(1609459200000).toString(), + created_at: new Date(1609459200000).toISOString(), + date_modified: new Date(1609459200000).toISOString(), }, ] diff --git a/site/gatsby-site/server/generated/gql.ts b/site/gatsby-site/server/generated/gql.ts index ff87243211..0002fb7b8b 100644 --- a/site/gatsby-site/server/generated/gql.ts +++ b/site/gatsby-site/server/generated/gql.ts @@ -18,7 +18,7 @@ const documents = { "\n mutation InsertDuplicate($duplicate: DuplicateInsertInput!) {\n insertOneDuplicate(data: $duplicate) {\n duplicate_incident_number\n true_incident_number\n }\n }\n": types.InsertDuplicateDocument, "\n mutation UpsertEntity($filter: EntityFilterType!, $update: EntityInsertType!) {\n upsertOneEntity(filter: $filter, update: $update) {\n entity_id\n name\n }\n }\n": types.UpsertEntityDocument, "\n query FindEntities {\n entities {\n entity_id\n name\n }\n }\n": types.FindEntitiesDocument, - "\n query FindEntity($filter: EntityFilterType) {\n entity(filter: $filter) {\n entity_id\n name\n created_at\n }\n }\n": types.FindEntityDocument, + "\n query FindEntity($filter: EntityFilterType) {\n entity(filter: $filter) {\n entity_id\n name\n created_at\n date_modified\n }\n }\n": types.FindEntityDocument, "\n mutation UpdateEntity($filter: EntityFilterType!, $update: EntityUpdateType!) {\n updateOneEntity(filter: $filter, update: $update) {\n entity_id\n }\n }\n": types.UpdateEntityDocument, "\n query FindIncident($filter: IncidentFilterType) {\n incident(filter: $filter) {\n incident_id\n title\n description\n editors {\n userId\n first_name\n last_name\n }\n date\n AllegedDeployerOfAISystem {\n entity_id\n name\n }\n AllegedDeveloperOfAISystem {\n entity_id\n name\n }\n AllegedHarmedOrNearlyHarmedParties {\n entity_id\n name\n }\n nlp_similar_incidents {\n incident_id\n similarity\n }\n editor_similar_incidents\n editor_dissimilar_incidents\n flagged_dissimilar_incidents\n reports {\n report_number\n }\n embedding {\n from_reports\n vector\n }\n editor_notes\n }\n }\n": types.FindIncidentDocument, "\n query FindIncidentsTable($filter: IncidentFilterType) {\n incidents(filter: $filter) {\n incident_id\n title\n description\n editors {\n userId\n first_name\n last_name\n }\n date\n AllegedDeployerOfAISystem {\n entity_id\n name\n }\n AllegedDeveloperOfAISystem {\n entity_id\n name\n }\n AllegedHarmedOrNearlyHarmedParties {\n entity_id\n name\n }\n reports {\n report_number\n }\n }\n }\n": types.FindIncidentsTableDocument, @@ -108,7 +108,7 @@ export function gql(source: "\n query FindEntities {\n entities {\n ent /** * The gql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. */ -export function gql(source: "\n query FindEntity($filter: EntityFilterType) {\n entity(filter: $filter) {\n entity_id\n name\n created_at\n }\n }\n"): (typeof documents)["\n query FindEntity($filter: EntityFilterType) {\n entity(filter: $filter) {\n entity_id\n name\n created_at\n }\n }\n"]; +export function gql(source: "\n query FindEntity($filter: EntityFilterType) {\n entity(filter: $filter) {\n entity_id\n name\n created_at\n date_modified\n }\n }\n"): (typeof documents)["\n query FindEntity($filter: EntityFilterType) {\n entity(filter: $filter) {\n entity_id\n name\n created_at\n date_modified\n }\n }\n"]; /** * The gql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. */ diff --git a/site/gatsby-site/server/generated/graphql.ts b/site/gatsby-site/server/generated/graphql.ts index e665091a4c..a283badbcb 100644 --- a/site/gatsby-site/server/generated/graphql.ts +++ b/site/gatsby-site/server/generated/graphql.ts @@ -943,8 +943,10 @@ export type CreateVariantInputVariant = { export type CreateVariantPayload = { __typename?: 'CreateVariantPayload'; - incident_id?: Maybe; - report_number?: Maybe; + /** The unique identifier for the incident. */ + incident_id: Scalars['Int']['output']; + /** The unique report number associated with the incident. */ + report_number: Scalars['Int']['output']; }; /** Filter type for DateTime scalar */ @@ -1133,6 +1135,7 @@ export type EntityFilterType = { OR?: InputMaybe>>; _id?: InputMaybe; created_at?: InputMaybe; + date_modified?: InputMaybe; entity_id?: InputMaybe; name?: InputMaybe; }; @@ -1140,6 +1143,7 @@ export type EntityFilterType = { export type EntityInsertType = { _id?: InputMaybe; created_at?: InputMaybe; + date_modified?: InputMaybe; entity_id: Scalars['String']['input']; name: Scalars['String']['input']; }; @@ -1147,6 +1151,7 @@ export type EntityInsertType = { export type EntitySetType = { _id?: InputMaybe; created_at?: InputMaybe; + date_modified?: InputMaybe; entity_id?: InputMaybe; name?: InputMaybe; }; @@ -1167,6 +1172,7 @@ export enum EntitySortByInput { export type EntitySortType = { _id?: InputMaybe; created_at?: InputMaybe; + date_modified?: InputMaybe; entity_id?: InputMaybe; name?: InputMaybe; }; @@ -3891,12 +3897,6 @@ export type ReportSortType = { url?: InputMaybe; }; -export type ReportTranslation = { - __typename?: 'ReportTranslation'; - text?: Maybe; - title?: Maybe; -}; - export type ReportTranslations = { __typename?: 'ReportTranslations'; text?: Maybe; @@ -5038,9 +5038,9 @@ export type User = { export type UserAdminDatum = { __typename?: 'UserAdminDatum'; - creationDate?: Maybe; - disabled?: Maybe; - email?: Maybe; + creationDate: Scalars['DateTime']['output']; + disabled: Scalars['Boolean']['output']; + email: Scalars['String']['output']; lastAuthenticationDate?: Maybe; }; @@ -5125,7 +5125,7 @@ export type FindEntityQueryVariables = Exact<{ }>; -export type FindEntityQuery = { __typename?: 'Query', entity?: { __typename?: 'Entity', entity_id: string, name: string, created_at?: any | null } | null }; +export type FindEntityQuery = { __typename?: 'Query', entity?: { __typename?: 'Entity', entity_id: string, name: string, created_at?: any | null, date_modified?: any | null } | null }; export type UpdateEntityMutationVariables = Exact<{ filter: EntityFilterType; @@ -5415,7 +5415,7 @@ export type FindUserQueryVariables = Exact<{ }>; -export type FindUserQuery = { __typename?: 'Query', user?: { __typename?: 'User', roles: Array, userId: string, first_name?: string | null, last_name?: string | null, adminData?: { __typename?: 'UserAdminDatum', email?: string | null, disabled?: boolean | null, creationDate?: any | null, lastAuthenticationDate?: any | null } | null } | null }; +export type FindUserQuery = { __typename?: 'Query', user?: { __typename?: 'User', roles: Array, userId: string, first_name?: string | null, last_name?: string | null, adminData?: { __typename?: 'UserAdminDatum', email: string, disabled: boolean, creationDate: any, lastAuthenticationDate?: any | null } | null } | null }; export type FindUsersByRoleQueryVariables = Exact<{ role?: InputMaybe | Scalars['String']['input']>; @@ -5465,7 +5465,7 @@ export type CreateVariantMutationVariables = Exact<{ }>; -export type CreateVariantMutation = { __typename?: 'Mutation', createVariant?: { __typename?: 'CreateVariantPayload', incident_id?: number | null, report_number?: number | null } | null }; +export type CreateVariantMutation = { __typename?: 'Mutation', createVariant?: { __typename?: 'CreateVariantPayload', incident_id: number, report_number: number } | null }; export type UpdateVariantMutationVariables = Exact<{ filter: ReportFilterType; @@ -5488,7 +5488,7 @@ export const UpsertClassificationDocument = {"kind":"Document","definitions":[{" export const InsertDuplicateDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"InsertDuplicate"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"duplicate"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"DuplicateInsertInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"insertOneDuplicate"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"data"},"value":{"kind":"Variable","name":{"kind":"Name","value":"duplicate"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"duplicate_incident_number"}},{"kind":"Field","name":{"kind":"Name","value":"true_incident_number"}}]}}]}}]} as unknown as DocumentNode; export const UpsertEntityDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"UpsertEntity"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"filter"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"EntityFilterType"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"update"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"EntityInsertType"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"upsertOneEntity"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"filter"},"value":{"kind":"Variable","name":{"kind":"Name","value":"filter"}}},{"kind":"Argument","name":{"kind":"Name","value":"update"},"value":{"kind":"Variable","name":{"kind":"Name","value":"update"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"entity_id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}}]}}]}}]} as unknown as DocumentNode; export const FindEntitiesDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"FindEntities"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"entities"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"entity_id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}}]}}]}}]} as unknown as DocumentNode; -export const FindEntityDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"FindEntity"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"filter"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"EntityFilterType"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"entity"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"filter"},"value":{"kind":"Variable","name":{"kind":"Name","value":"filter"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"entity_id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"created_at"}}]}}]}}]} as unknown as DocumentNode; +export const FindEntityDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"FindEntity"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"filter"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"EntityFilterType"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"entity"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"filter"},"value":{"kind":"Variable","name":{"kind":"Name","value":"filter"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"entity_id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"created_at"}},{"kind":"Field","name":{"kind":"Name","value":"date_modified"}}]}}]}}]} as unknown as DocumentNode; export const UpdateEntityDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"UpdateEntity"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"filter"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"EntityFilterType"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"update"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"EntityUpdateType"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"updateOneEntity"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"filter"},"value":{"kind":"Variable","name":{"kind":"Name","value":"filter"}}},{"kind":"Argument","name":{"kind":"Name","value":"update"},"value":{"kind":"Variable","name":{"kind":"Name","value":"update"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"entity_id"}}]}}]}}]} as unknown as DocumentNode; export const FindIncidentDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"FindIncident"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"filter"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"IncidentFilterType"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"incident"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"filter"},"value":{"kind":"Variable","name":{"kind":"Name","value":"filter"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"incident_id"}},{"kind":"Field","name":{"kind":"Name","value":"title"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"editors"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"userId"}},{"kind":"Field","name":{"kind":"Name","value":"first_name"}},{"kind":"Field","name":{"kind":"Name","value":"last_name"}}]}},{"kind":"Field","name":{"kind":"Name","value":"date"}},{"kind":"Field","name":{"kind":"Name","value":"AllegedDeployerOfAISystem"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"entity_id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}}]}},{"kind":"Field","name":{"kind":"Name","value":"AllegedDeveloperOfAISystem"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"entity_id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}}]}},{"kind":"Field","name":{"kind":"Name","value":"AllegedHarmedOrNearlyHarmedParties"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"entity_id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}}]}},{"kind":"Field","name":{"kind":"Name","value":"nlp_similar_incidents"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"incident_id"}},{"kind":"Field","name":{"kind":"Name","value":"similarity"}}]}},{"kind":"Field","name":{"kind":"Name","value":"editor_similar_incidents"}},{"kind":"Field","name":{"kind":"Name","value":"editor_dissimilar_incidents"}},{"kind":"Field","name":{"kind":"Name","value":"flagged_dissimilar_incidents"}},{"kind":"Field","name":{"kind":"Name","value":"reports"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"report_number"}}]}},{"kind":"Field","name":{"kind":"Name","value":"embedding"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"from_reports"}},{"kind":"Field","name":{"kind":"Name","value":"vector"}}]}},{"kind":"Field","name":{"kind":"Name","value":"editor_notes"}}]}}]}}]} as unknown as DocumentNode; export const FindIncidentsTableDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"FindIncidentsTable"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"filter"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"IncidentFilterType"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"incidents"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"filter"},"value":{"kind":"Variable","name":{"kind":"Name","value":"filter"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"incident_id"}},{"kind":"Field","name":{"kind":"Name","value":"title"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"editors"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"userId"}},{"kind":"Field","name":{"kind":"Name","value":"first_name"}},{"kind":"Field","name":{"kind":"Name","value":"last_name"}}]}},{"kind":"Field","name":{"kind":"Name","value":"date"}},{"kind":"Field","name":{"kind":"Name","value":"AllegedDeployerOfAISystem"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"entity_id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}}]}},{"kind":"Field","name":{"kind":"Name","value":"AllegedDeveloperOfAISystem"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"entity_id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}}]}},{"kind":"Field","name":{"kind":"Name","value":"AllegedHarmedOrNearlyHarmedParties"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"entity_id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}}]}},{"kind":"Field","name":{"kind":"Name","value":"reports"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"report_number"}}]}}]}}]}}]} as unknown as DocumentNode; diff --git a/site/gatsby-site/server/types/entity.ts b/site/gatsby-site/server/types/entity.ts index 53e9561843..a7b78adca2 100644 --- a/site/gatsby-site/server/types/entity.ts +++ b/site/gatsby-site/server/types/entity.ts @@ -9,5 +9,6 @@ export const EntityType = new GraphQLObjectType({ entity_id: { type: new GraphQLNonNull(GraphQLString) }, name: { type: new GraphQLNonNull(GraphQLString) }, created_at: { type: GraphQLDateTime }, + date_modified: { type: GraphQLDateTime }, }, }); diff --git a/site/gatsby-site/src/graphql/entities.js b/site/gatsby-site/src/graphql/entities.js index 11bb7c9a91..e4402279cf 100644 --- a/site/gatsby-site/src/graphql/entities.js +++ b/site/gatsby-site/src/graphql/entities.js @@ -24,12 +24,11 @@ export const FIND_ENTITY = gql(` entity_id name created_at + date_modified } } `); -// TODO: temporarily remove date_modified - export const UPDATE_ENTITY = gql(` mutation UpdateEntity($filter: EntityFilterType!, $update: EntityUpdateType!) { updateOneEntity(filter: $filter, update: $update) { diff --git a/site/gatsby-site/src/pages/entities/edit.js b/site/gatsby-site/src/pages/entities/edit.js index 968edee23f..231b11e90b 100644 --- a/site/gatsby-site/src/pages/entities/edit.js +++ b/site/gatsby-site/src/pages/entities/edit.js @@ -55,7 +55,7 @@ function EditEntityPage(props) { update: { set: { name: values.name, - date_modified: new Date(), + date_modified: new Date().toISOString(), }, }, }, @@ -102,6 +102,7 @@ function EditEntityPage(props) { initialValues={{ ...entity, }} + enableReinitialize > {({ values, From 5917ec763bfc73eb63c5173229def1145d0d0fab Mon Sep 17 00:00:00 2001 From: Pablo Costa Date: Mon, 2 Sep 2024 15:19:22 -0300 Subject: [PATCH 5/6] Fix news digest page queries (#3051) --- site/gatsby-site/src/pages/apps/newsdigest.js | 28 +++++++++++++++---- 1 file changed, 22 insertions(+), 6 deletions(-) diff --git a/site/gatsby-site/src/pages/apps/newsdigest.js b/site/gatsby-site/src/pages/apps/newsdigest.js index bc8995ab67..9fa0c6f40d 100644 --- a/site/gatsby-site/src/pages/apps/newsdigest.js +++ b/site/gatsby-site/src/pages/apps/newsdigest.js @@ -61,25 +61,41 @@ export default function NewsSearchPage() { const { data: submissionsData } = useQuery( gql` - query ExistingSubmissions($query: SubmissionQueryInput!) { - submissions(query: $query, limit: 9999) { + query ExistingSubmissions($filter: SubmissionFilterType!) { + submissions(filter: $filter) { url } } `, - { variables: { query: { url_in: newsArticleUrls } } } + { + variables: { + filter: { + url: { + IN: newsArticleUrls, + }, + }, + }, + } ); const { data: reportsData } = useQuery( gql` - query ExistingReports($query: ReportQueryInput!) { - reports(query: $query, limit: 9999) { + query ExistingReports($filter: ReportFilterType!) { + reports(filter: $filter) { report_number url } } `, - { variables: { query: { url_in: newsArticleUrls } } } + { + variables: { + filter: { + url: { + IN: newsArticleUrls, + }, + }, + }, + } ); const [updateCandidate] = useMutation(gql` From 64e1275bdc87ec546196c2b23274bea0371739ad Mon Sep 17 00:00:00 2001 From: Cesar Varela Date: Tue, 3 Sep 2024 16:38:10 -0300 Subject: [PATCH 6/6] Fix/update tests (#3027) * Add taxa seeds * Reapply "Add classifications collection to api" This reverts commit 5d3aed2494bafe674db2c73daa9b759f555babee. * Fix upsert mutations * Fix classification editor tests * Update components * Move to correct folder * Fix mutations handling of relationships * Update client side query variables * Undo debug comments * Update generated code * temporary silence warning * Update playwright login util to allow mocking local storage session * migrate classifications test * Update test * Update tests to use new login fixture * Fix tests * Skip data dependant test * Prevent login util from dropping users database * WIP * Fix custom data mocking * Delete old mutation * delete old test * Update user queries * Make logs less verbose * Update tests * Migrate test * Remove slow clause * Migrate submitted app tests * Fix subscriptions with users linking * Undo changes * migrate test * Have leftover cypress tests point to local api * Migrate checklists form tests * Remove test.slow calls * Add one more retry * updates * fix imports * Update tests * Improve util * Migrate test to full e2e * skip tests * use new login userId * reduce flake * Fix test * update test * add one more retry * role self is now it's own rule --- .github/workflows/test-playwright-full.yml | 14 +- .../cypress/e2e/incidentVariants.cy.js | 473 ---- .../e2e/integration/apps/checklistsForm.cy.js | 275 -- .../integration/apps/checklistsIndex.cy.js | 277 -- .../e2e/integration/apps/submitted.cy.js | 2509 ----------------- .../functions/linkReportsToIncidents.cy.js | 277 -- .../functions/promoteSubmissionToReport.cy.js | 887 ------ site/gatsby-site/cypress/support/utils.js | 4 +- site/gatsby-site/package.json | 2 +- site/gatsby-site/playwright.config.ts | 2 +- .../e2e-full/apps/checklistForm.spec.ts | 346 +++ .../e2e-full/apps/checklistIndex.spec.ts | 251 ++ .../e2e-full/apps/classifications.spec.ts | 2 +- .../e2e-full/apps/submitted.spec.ts | 579 ++++ .../playwright/e2e-full/cite.spec.ts | 10 - .../playwright/e2e-full/citeEdit.spec.ts | 8 +- .../e2e-full/classificationsEditor.spec.ts | 32 +- .../e2e-full/incidentVariants.spec.ts | 211 ++ .../playwright/e2e-full/incidents/new.spec.ts | 4 +- .../e2e-full/socialShareButtons.spec.ts | 105 + .../playwright/e2e-full/submit.spec.ts | 1685 +++-------- .../playwright/e2e/discover.spec.ts | 3 +- .../playwright/e2e/incidents/history.spec.ts | 29 +- .../playwright/e2e/integration/cset.spec.ts | 2 +- .../playwright/e2e/integrity.spec.ts | 6 +- .../playwright/e2e/reportHistory.spec.ts | 24 +- .../playwright/e2e/socialShareButtons.spec.ts | 85 - .../checklists/riskSortingChecklist.json | 137 + .../fixtures/checklists/riskSortingRisks.json | 415 +++ .../gatsby-site/playwright/meta/utils.spec.ts | 17 + .../seeds/aiidprod/classifications.ts | 57 +- .../playwright/seeds/aiidprod/reports.ts | 4 +- .../playwright/seeds/aiidprod/submissions.ts | 18 +- .../playwright/seeds/customData/users.ts | 2 +- site/gatsby-site/playwright/utils.ts | 8 +- site/gatsby-site/server/fields/users.ts | 6 +- site/gatsby-site/server/remote.ts | 4 +- site/gatsby-site/server/rules.ts | 31 +- .../components/checklists/CheckListForm.js | 2 +- .../src/components/submissions/schemas.js | 2 +- site/gatsby-site/src/utils/checklists.js | 1 + site/realm/functions/config.json | 4 - .../functions/promoteSubmissionToReport.js | 194 -- .../mutation_promoteSubmissionToReport.json | 40 - 44 files changed, 2608 insertions(+), 6436 deletions(-) delete mode 100644 site/gatsby-site/cypress/e2e/incidentVariants.cy.js delete mode 100644 site/gatsby-site/cypress/e2e/integration/apps/checklistsForm.cy.js delete mode 100644 site/gatsby-site/cypress/e2e/integration/apps/checklistsIndex.cy.js delete mode 100644 site/gatsby-site/cypress/e2e/integration/apps/submitted.cy.js delete mode 100644 site/gatsby-site/cypress/e2e/unit/functions/linkReportsToIncidents.cy.js delete mode 100644 site/gatsby-site/cypress/e2e/unit/functions/promoteSubmissionToReport.cy.js create mode 100644 site/gatsby-site/playwright/e2e-full/apps/checklistForm.spec.ts create mode 100644 site/gatsby-site/playwright/e2e-full/apps/checklistIndex.spec.ts create mode 100644 site/gatsby-site/playwright/e2e-full/apps/submitted.spec.ts create mode 100644 site/gatsby-site/playwright/e2e-full/incidentVariants.spec.ts create mode 100644 site/gatsby-site/playwright/e2e-full/socialShareButtons.spec.ts delete mode 100644 site/gatsby-site/playwright/e2e/socialShareButtons.spec.ts create mode 100644 site/gatsby-site/playwright/fixtures/checklists/riskSortingChecklist.json create mode 100644 site/gatsby-site/playwright/fixtures/checklists/riskSortingRisks.json create mode 100644 site/gatsby-site/playwright/meta/utils.spec.ts delete mode 100644 site/realm/functions/promoteSubmissionToReport.js delete mode 100644 site/realm/graphql/custom_resolvers/mutation_promoteSubmissionToReport.json diff --git a/.github/workflows/test-playwright-full.yml b/.github/workflows/test-playwright-full.yml index b1aa091a12..557b1c4feb 100644 --- a/.github/workflows/test-playwright-full.yml +++ b/.github/workflows/test-playwright-full.yml @@ -28,8 +28,8 @@ jobs: strategy: fail-fast: false matrix: - shardIndex: [1, 2, 3, 4, 5, 6] - shardTotal: [6] + shardIndex: [1, 2, 3, 4, 5, 6, 7, 8] + shardTotal: [8] steps: - name: Checkout uses: actions/checkout@v4 @@ -127,12 +127,20 @@ jobs: SHARD_TOTAL: ${{ matrix.shardTotal }} TEST_FOLDER: playwright/e2e-full/ + - name: Upload Playwright traces + if: failure() + uses: actions/upload-artifact@v4 + with: + name: playwright-traces-${{ matrix.shardIndex }} + path: site/gatsby-site/test-results/**/*.zip + retention-days: 7 + - uses: actions/upload-artifact@v4 if: ${{ !cancelled() }} with: name: blob-report-full-${{ matrix.shardIndex }} path: site/gatsby-site/blob-report/ - retention-days: 1 + retention-days: 7 merge-reports: # Merge reports after playwright-tests, even if some shards have failed diff --git a/site/gatsby-site/cypress/e2e/incidentVariants.cy.js b/site/gatsby-site/cypress/e2e/incidentVariants.cy.js deleted file mode 100644 index 1f867e3e6c..0000000000 --- a/site/gatsby-site/cypress/e2e/incidentVariants.cy.js +++ /dev/null @@ -1,473 +0,0 @@ -import { maybeIt } from '../support/utils'; -import variantsIncident from '../fixtures/variants/variantsIncident.json'; -import { - getVariantStatus, - getVariantStatusText, - isCompleteReport, - VARIANT_STATUS, -} from '../../src/utils/variants'; -import { getUnixTime } from 'date-fns'; -const { gql } = require('@apollo/client'); - -const incidentId = 464; - -const getVariants = (callback) => { - cy.query({ - query: gql` - query { - incidents(query: { incident_id: ${incidentId} }, limit: 1) { - reports { - report_number - title - date_published - tags - url - source_domain - submitters - text - inputs_outputs - } - } - } - `, - }).then(({ data: { incidents } }) => { - const incident = incidents[0]; - - const variants = incident.reports - .filter((r) => !isCompleteReport(r)) - .sort((a, b) => a.report_number - b.report_number); - - callback(variants); - }); -}; - -const new_date_published = '2000-01-01'; - -const new_text = - 'New text example with more than 80 characters. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.'; - -const new_inputs_outputs_1 = 'New Input text'; - -const new_inputs_outputs_2 = 'New Output text'; - -const new_submitter = 'New Submitter'; - -describe('Variants pages', () => { - const url = `/cite/${incidentId}`; - - before('before', function () { - // Skip all tests if the environment is empty since /cite/{incident_id} page is not available - Cypress.env('isEmptyEnvironment') && this.skip(); - }); - - it('Successfully loads', () => { - cy.visit(url); - - cy.disableSmoothScroll(); - }); - - it('Should display Variant list', () => { - cy.visit(url); - - cy.contains('h1', 'Variants').should('exist').scrollIntoView(); - - getVariants((variants) => { - cy.get('[data-cy=variant-card]').should('have.length', variants.length); - - for (let index = 0; index < variants.length; index++) { - const variant = variants[index]; - - cy.get('[data-cy=variant-card]') - .eq(index) - .within(() => { - cy.get('[data-cy=variant-status-badge]').contains( - getVariantStatusText(getVariantStatus(variant)) - ); - cy.get('[data-cy=variant-text]').contains(variant.text); - cy.get('[data-cy=variant-inputs-outputs]').eq(0).contains('New Input text'); - cy.get('[data-cy=variant-inputs-outputs]') - .eq(1) - .contains('Test output text with markdown'); - }); - } - }); - }); - - it.skip('Should add a new Variant - Unauthenticated user', () => { - cy.conditionalIntercept( - '**/graphql', - (req) => - req.body.operationName == 'CreateVariant' && - req.body.variables.input.incidentId === incidentId && - req.body.variables.input.variant.date_published === new_date_published && - req.body.variables.input.variant.submitters[0] === new_submitter && - req.body.variables.input.variant.text === new_text && - req.body.variables.input.variant.inputs_outputs[0] === new_inputs_outputs_1 && - req.body.variables.input.variant.inputs_outputs[1] === new_inputs_outputs_2, - 'createVariant', - { - data: { - createVariant: { - __typename: 'CreateVariantPayload', - incident_id: incidentId, - report_number: 2313, - }, - }, - } - ); - - cy.visit(url); - - cy.waitForStableDOM(); - - cy.contains('h1', 'Variants').should('exist').scrollIntoView(); - - cy.get('[data-cy=variant-form]').should('not.exist'); - - cy.get('[data-cy=add-variant-btn]').scrollIntoView().click(); - - cy.waitForStableDOM(); - - cy.get('[data-cy=variant-form]').should('exist'); - - cy.get('[data-cy="variant-form-date-published"]').type(new_date_published); - cy.get('[data-cy="variant-form-submitters"]').type(new_submitter); - cy.get('[data-cy="variant-form-text"]').clear().type(new_text); - cy.get('[data-cy="variant-form-inputs-outputs"]:eq(0)').clear().type(new_inputs_outputs_1); - cy.get('[data-cy="add-text-row-btn"]').click(); - cy.get('[data-cy="variant-form-inputs-outputs"]:eq(1)').clear().type(new_inputs_outputs_2); - - cy.waitForStableDOM(); - - cy.get('[data-cy=add-variant-submit-btn]').click(); - - cy.waitForStableDOM(); - - cy.wait('@createVariant'); - - cy.waitForStableDOM(); - - cy.get('[data-cy=success-message]').contains( - "Your variant has been added to the review queue and will appear on this page within 12 hours. Please continue submitting when you encounter more variants. Most of the time we won't review it in the same day, but it will appear within a day as unreviewed." - ); - - cy.get('[data-cy="toast"]') - .contains( - 'Your variant has been added to the review queue and will appear on this page within 12 hours.' - ) - .should('exist'); - }); - - it("Shouldn't edit a Variant - Unauthenticated user", () => { - cy.visit(url); - - cy.contains('h1', 'Variants').should('exist').scrollIntoView(); - - cy.get('[data-cy=edit-variant-btn]').should('not.exist'); - }); - - maybeIt('Should Approve Variant - Incident Editor user', () => { - cy.login(Cypress.env('e2eUsername'), Cypress.env('e2ePassword')); - - cy.conditionalIntercept( - '**/graphql', - (req) => req.body.operationName == 'FindVariant', - 'findVariant', - { - data: { - report: variantsIncident.data.incident.reports[0], - }, - } - ); - - cy.visit(url); - - getVariants((variants) => { - const variant = variants[0]; - - const now = new Date(); - - cy.conditionalIntercept( - '**/graphql', - (req) => - req.body.operationName == 'UpdateVariant' && - req.body.variables.query.report_number === variant.report_number && - req.body.variables.set.date_published === new Date(new_date_published).toISOString() && - req.body.variables.set.submitters[0] === variant.submitters[0] && - req.body.variables.set.submitters[1] === variant.submitters[1] && - req.body.variables.set.text === new_text && - req.body.variables.set.inputs_outputs[0] === new_inputs_outputs_1 && - req.body.variables.set.inputs_outputs[1] === new_inputs_outputs_2 && - req.body.variables.set.tags.includes(VARIANT_STATUS.approved) && - req.body.variables.set.date_modified == now.toISOString() && - req.body.variables.set.epoch_date_modified == getUnixTime(now), - 'updateVariant', - { - data: { - updateOneReport: { ...variant, tags: [VARIANT_STATUS.approved] }, - }, - } - ); - - cy.get('[data-cy=variant-card]').should('have.length', variants.length); - - if (variants.length > 0) { - cy.get('[data-cy=variant-card]') - .eq(0) - .within(() => { - cy.get('[data-cy=edit-variant-btn]').click(); - }); - - cy.waitForStableDOM(); - - cy.get('[data-cy=edit-variant-modal]').should('be.visible').as('modal'); - - cy.get('[data-cy="variant-form-date-published"]').type(new_date_published); - cy.get('[data-cy="variant-form-submitters"]').type(new_submitter); - cy.get('[data-cy="variant-form-text"]').clear().type(new_text); - cy.get('[data-cy="variant-form-inputs-outputs"]:eq(0)').clear().type(new_inputs_outputs_1); - cy.get('[data-cy="variant-form-inputs-outputs"]:eq(1)').clear().type(new_inputs_outputs_2); - - cy.clock(now); - - cy.get('[data-cy=approve-variant-btn]').click(); - - cy.wait('@updateVariant'); - - cy.get('[data-cy="toast"]') - .contains('Variant successfully updated. Your edits will be live within 24 hours.') - .should('exist'); - - cy.get('[data-cy=edit-variant-modal]').should('not.exist'); - } - }); - }); - - maybeIt('Should Reject Variant - Incident Editor user', () => { - cy.login(Cypress.env('e2eUsername'), Cypress.env('e2ePassword')); - - cy.conditionalIntercept( - '**/graphql', - (req) => req.body.operationName == 'FindVariant', - 'findVariant', - { - data: { - report: variantsIncident.data.incident.reports[1], - }, - } - ); - - cy.visit(url); - - getVariants((variants) => { - const variant = variants[0]; - - const now = new Date(); - - cy.conditionalIntercept( - '**/graphql', - (req) => - req.body.operationName == 'UpdateVariant' && - req.body.variables.query.report_number === variant.report_number && - req.body.variables.set.date_published === new Date(new_date_published).toISOString() && - req.body.variables.set.submitters[0] === variant.submitters[0] && - req.body.variables.set.submitters[1] === new_submitter && - req.body.variables.set.text === new_text && - req.body.variables.set.inputs_outputs[0] === new_inputs_outputs_1 && - req.body.variables.set.inputs_outputs[1] === new_inputs_outputs_2 && - req.body.variables.set.tags.includes(VARIANT_STATUS.rejected) && - req.body.variables.set.date_modified == now.toISOString() && - req.body.variables.set.epoch_date_modified == getUnixTime(now), - 'updateVariant', - { - data: { - updateOneReport: { ...variant, tags: [VARIANT_STATUS.rejected] }, - }, - } - ); - - cy.get('[data-cy=variant-card]').should('have.length', variants.length); - - if (variants.length > 0) { - cy.get('[data-cy=variant-card]') - .eq(0) - .within(() => { - cy.get('[data-cy=edit-variant-btn]').click(); - }); - - cy.waitForStableDOM(); - - cy.get('[data-cy=edit-variant-modal]').should('be.visible').as('modal'); - - cy.get('[data-cy="variant-form-date-published"]').type(new_date_published); - cy.get('[data-cy="variant-form-submitters"]').type(new_submitter); - cy.get('[data-cy="variant-form-text"]').clear().type(new_text); - cy.get('[data-cy="variant-form-inputs-outputs"]:eq(0)').clear().type(new_inputs_outputs_1); - cy.get('[data-cy="variant-form-inputs-outputs"]:eq(1)').clear().type(new_inputs_outputs_2); - - cy.clock(now); - - cy.get('[data-cy=reject-variant-btn]').click(); - - cy.wait('@updateVariant'); - - cy.get('[data-cy="toast"]') - .contains('Variant successfully updated. Your edits will be live within 24 hours.') - .should('exist'); - - cy.get('[data-cy=edit-variant-modal]').should('not.exist'); - } - }); - }); - - maybeIt('Should Save Variant - Incident Editor user', () => { - cy.login(Cypress.env('e2eUsername'), Cypress.env('e2ePassword')); - - cy.conditionalIntercept( - '**/graphql', - (req) => req.body.operationName == 'FindVariant', - 'findVariant', - { - data: { - report: variantsIncident.data.incident.reports[0], - }, - } - ); - - cy.visit(url); - - getVariants((variants) => { - const variant = variants[0]; - - const now = new Date(); - - cy.conditionalIntercept( - '**/graphql', - (req) => - req.body.operationName == 'UpdateVariant' && - req.body.variables.query.report_number === variant.report_number && - req.body.variables.set.date_published === new Date(new_date_published).toISOString() && - req.body.variables.set.submitters[0] === variant.submitters[0] && - req.body.variables.set.submitters[1] === new_submitter && - req.body.variables.set.text === new_text && - req.body.variables.set.inputs_outputs[0] === new_inputs_outputs_1 && - req.body.variables.set.inputs_outputs[1] === variant.inputs_outputs[1] && - req.body.variables.set.tags == undefined && - req.body.variables.set.date_modified == now.toISOString() && - req.body.variables.set.epoch_date_modified == getUnixTime(now), - 'updateVariant', - { - data: { - updateOneReport: variant, - }, - } - ); - - cy.get('[data-cy=variant-card]').should('have.length', variants.length); - - if (variants.length > 0) { - cy.get('[data-cy=variant-card]') - .eq(0) - .within(() => { - cy.get('[data-cy=edit-variant-btn]').click(); - }); - - cy.waitForStableDOM(); - - cy.get('[data-cy=edit-variant-modal]').should('be.visible').as('modal'); - - cy.get('[data-cy="variant-form-date-published"]').type(new_date_published); - cy.get('[data-cy="variant-form-submitters"]').type(new_submitter); - cy.get('[data-cy="variant-form-text"]').clear().type(new_text); - cy.get('[data-cy="variant-form-inputs-outputs"]:eq(0)').clear().type(new_inputs_outputs_1); - - cy.clock(now); - - cy.get('[data-cy=save-variant-btn]').click(); - - cy.wait('@updateVariant'); - - cy.get('[data-cy="toast"]') - .contains('Variant successfully updated. Your edits will be live within 24 hours.') - .should('exist'); - - cy.get('[data-cy=edit-variant-modal]').should('not.exist'); - } - }); - }); - - maybeIt('Should Delete Variant - Incident Editor user', () => { - cy.login(Cypress.env('e2eUsername'), Cypress.env('e2ePassword')); - - getVariants((variants) => { - const variant = variants[0]; - - cy.conditionalIntercept( - '**/graphql', - (req) => req.body.operationName == 'FindVariant', - 'findVariant', - { - data: { - report: variant, - }, - } - ); - - cy.conditionalIntercept( - '**/graphql', - (req) => - req.body.operationName == 'DeleteOneVariant' && - req.body.variables.query.report_number === variant.report_number, - 'deleteOneVariant', - { - data: { - deleteOneReport: { - __typename: 'Report', - report_number: variant.report_number, - }, - }, - } - ); - - cy.conditionalIntercept( - '**/graphql', - (req) => - req.body.operationName == 'LinkReportsToIncidents' && - req.body.variables.input.incident_ids.length === 0 && - req.body.variables.input.report_numbers.includes(variant.report_number), - 'linkReportsToIncidents', - { - data: { - linkReportsToIncidents: [], - }, - } - ); - - cy.visit(url); - - cy.get('[data-cy=variant-card]').should('have.length', variants.length); - - if (variants.length > 0) { - cy.get('[data-cy=variant-card]') - .eq(0) - .within(() => { - cy.get('[data-cy=edit-variant-btn]').click(); - }); - - cy.get('[data-cy=edit-variant-modal]').should('be.visible').as('modal'); - - cy.get('[data-cy=delete-variant-btn]').click(); - - cy.wait('@deleteOneVariant'); - - cy.wait('@linkReportsToIncidents'); - - cy.get('[data-cy="toast"]') - .contains('Variant successfully deleted. Your changes will be live within 24 hours.') - .should('exist'); - - cy.get('[data-cy=edit-variant-modal]').should('not.exist'); - } - }); - }); -}); diff --git a/site/gatsby-site/cypress/e2e/integration/apps/checklistsForm.cy.js b/site/gatsby-site/cypress/e2e/integration/apps/checklistsForm.cy.js deleted file mode 100644 index a22b997cae..0000000000 --- a/site/gatsby-site/cypress/e2e/integration/apps/checklistsForm.cy.js +++ /dev/null @@ -1,275 +0,0 @@ -import { maybeIt } from '../../../support/utils'; -import riskSortingRisks from '../../../fixtures/checklists/riskSortingRisks.json'; -import riskSortingChecklist from '../../../fixtures/checklists/riskSortingChecklist.json'; -const { gql } = require('@apollo/client'); - -describe('Checklists App Form', () => { - const url = '/apps/checklists?id=testChecklist'; - - const defaultChecklist = { - __typename: 'Checklist', - about: '', - id: 'testChecklist', - name: 'Test Checklist', - owner_id: 'a-fake-user-id-that-does-not-exist', - risks: [], - tags_goals: [], - tags_methods: [], - tags_other: [], - }; - - const usersQuery = { - query: gql` - { - users(limit: 9999) { - userId - roles - adminData { - email - } - } - } - `, - timeout: 120000, // mongodb admin api is extremely slow - }; - - const withLogin = (callback) => { - cy.login(Cypress.env('e2eUsername'), Cypress.env('e2ePassword')); - - cy.query(usersQuery).then(({ data: { users } }) => { - const user = users.find((user) => user.adminData.email == Cypress.env('e2eUsername')); - - callback({ user }); - }); - }; - - const interceptFindChecklist = (checklist) => { - cy.conditionalIntercept( - '**/graphql', - (req) => req.body.operationName == 'findChecklist', - 'findChecklist', - { data: { checklist } } - ); - }; - - const interceptUpsertChecklist = (checklist) => { - cy.conditionalIntercept( - '**/graphql', - (req) => req.body.operationName == 'upsertChecklist', - 'upsertChecklist', - { data: { checklist } } - ); - }; - - const interceptFindRisks = (risks) => { - cy.conditionalIntercept('**/graphql', (req) => req.body.query.includes('GMF'), 'findRisks', { - data: { risks }, - }); - }; - - it.skip('Should have read-only access for non-logged-in users', () => { - interceptFindChecklist(defaultChecklist); - - cy.visit(url); - - cy.wait(['@findChecklist']); - - cy.waitForStableDOM(); - - cy.get('[data-cy="checklist-form"] textarea:not([disabled])').should('not.exist'); - - cy.get('[data-cy="checklist-form"] input:not([disabled]):not([readonly])').should('not.exist'); - }); - - maybeIt('Should have read-only access for logged-in non-owners', () => { - cy.login(Cypress.env('e2eUsername'), Cypress.env('e2ePassword')); - - interceptFindChecklist(defaultChecklist); - - cy.visit(url); - - cy.wait(['@findChecklist']); - - cy.waitForStableDOM(); - - cy.get('[data-cy="checklist-form"] textarea:not([disabled])').should('not.exist'); - - cy.get('[data-cy="checklist-form"] input:not([disabled]):not([readonly])').should('not.exist'); - }); - - maybeIt('Should allow editing for owner', () => { - withLogin(({ user }) => { - interceptFindChecklist({ ...defaultChecklist, owner_id: user.userId }); - interceptUpsertChecklist({ - ...defaultChecklist, - owner_id: user.userId, - about: "It's a system that does something probably.", - }); - - cy.visit(url); - - cy.wait(['@findChecklist']); - - cy.waitForStableDOM(); - - cy.get('[data-cy="about"]').type("It's a system that does something probably."); - - cy.wait(['@upsertChecklist']); - }); - }); - - maybeIt('Should trigger GraphQL upsert query on adding tag', () => { - withLogin(({ user }) => { - interceptFindChecklist({ ...defaultChecklist, owner_id: user.userId }); - interceptUpsertChecklist({}); - - cy.visit(url); - - cy.get('#tags_goals_input').type('Code Generation'); - cy.get('#tags_goals').contains('Code Generation').click(); - - cy.wait(['@upsertChecklist']).then((xhr) => { - expect(xhr.request.body.variables.checklist).to.deep.nested.include({ - tags_goals: ['GMF:Known AI Goal:Code Generation'], - }); - }); - }); - }); - - maybeIt('Should trigger GraphQL update on removing tag', () => { - withLogin(({ user }) => { - interceptFindChecklist({ - ...defaultChecklist, - owner_id: user.userId, - tags_goals: ['GMF:Known AI Goal:Code Generation'], - }); - interceptUpsertChecklist({}); - - cy.visit(url); - - cy.get('[option="GMF:Known AI Goal:Code Generation"] .close').click(); - - cy.wait(['@upsertChecklist']).then((xhr) => { - expect(xhr.request.body.variables.checklist).to.deep.nested.include({ - tags_goals: [], - }); - }); - - cy.visit(url); - }); - }); - - maybeIt('Should trigger UI update on adding and removing tag', () => { - withLogin(({ user }) => { - interceptFindChecklist({ - ...defaultChecklist, - owner_id: user.userId, - }); - interceptUpsertChecklist({}); - - cy.visit(url); - - cy.get('#tags_methods_input').type('Transformer'); - cy.get('#tags_methods').contains('Transformer').click(); - - cy.waitForStableDOM(); - - cy.get('details').should('exist'); - - cy.get('.rbt-close').click(); - - cy.waitForStableDOM(); - - cy.get('details').should('not.exist'); - }); - }); - - it('Should change sort order of risk items', () => { - cy.viewport(1920, 1080); - - withLogin(({ user }) => { - interceptFindChecklist({ - ...riskSortingChecklist.data.checklist, - owner_id: user.userId, - }); - - interceptFindRisks(riskSortingRisks.data.risks); - - cy.visit(url); - - cy.wait(['@findChecklist']); - - cy.wait(['@findRisks']); - - cy.waitForStableDOM(); - - cy.contains('Mitigated').click(); - - cy.get('details:nth(1)').contains('Distributional Bias').should('exist'); - - cy.contains('Minor').click(); - - cy.get('details:nth(1)').contains('Dataset Imbalance').should('exist'); - }); - }); - - it('Should remove a manually-created risk', () => { - withLogin(({ user }) => { - interceptFindChecklist({ - ...defaultChecklist, - owner_id: user.userId, - risks: [ - { - __typename: 'ChecklistRisk', - generated: false, - id: '5bb31fa6-2d32-4a01-b0a0-fa3fb4ec4b7d', - likelihood: '', - precedents: [], - risk_notes: '', - risk_status: 'Mitigated', - severity: '', - tags: ['GMF:Known AI Goal:Content Search'], - title: 'Manual Test Risk', - touched: false, - }, - ], - }); - interceptUpsertChecklist({ ...defaultChecklist, owner_id: user.userId }); - - cy.visit(url); - - cy.contains('Manual Test Risk').get('svg > title').contains('Delete Risk').parent().click(); - - cy.wait('@upsertChecklist'); - - cy.contains('Manual Test Risk').should('not.exist'); - }); - }); - - it('Should persist open state on editing query', () => { - withLogin(({ user }) => { - interceptFindChecklist({ - ...riskSortingChecklist.data.checklist, - owner_id: user.userId, - }); - - interceptFindRisks(riskSortingRisks.data.risks); - - cy.visit(url); - - cy.wait(['@findChecklist']); - - cy.wait(['@findRisks']); - - cy.waitForStableDOM(); - - cy.contains('Distributional Artifacts').click(); - - cy.get('[data-cy="risk_query-container"] .rbt-input-main') - .first() - .type('CSETv0:Annotator:1{enter}'); - - cy.get('[data-cy="risk_query-container"]').parents('details').should('have.attr', 'open'); - }); - }); -}); diff --git a/site/gatsby-site/cypress/e2e/integration/apps/checklistsIndex.cy.js b/site/gatsby-site/cypress/e2e/integration/apps/checklistsIndex.cy.js deleted file mode 100644 index 9ee2f71660..0000000000 --- a/site/gatsby-site/cypress/e2e/integration/apps/checklistsIndex.cy.js +++ /dev/null @@ -1,277 +0,0 @@ -import { maybeIt } from '../../../support/utils'; - -const { gql } = require('@apollo/client'); - -describe('Checklists App Index', () => { - const url = '/apps/checklists'; - - const newChecklistButtonQuery = '#new-checklist-button'; - - const testError = { - 0: { - message: 'Test error', - locations: [{ line: 1, column: 1 }], - }, - }; - - const usersQuery = { - query: gql` - { - users(limit: 9999) { - userId - roles - adminData { - email - } - } - } - `, - timeout: 120000, // mongodb admin api is extremely slow - }; - - it('Should sort checklists', () => { - cy.query(usersQuery).then(({ data: { users } }) => { - const user = users.find((user) => user.adminData.email == Cypress.env('e2eUsername')); - - cy.conditionalIntercept( - '**/graphql', - (req) => req.body.operationName == 'findChecklists', - 'findChecklists', - { - data: { - checklists: [ - { - about: '', - id: 'fakeChecklist1', - name: 'My Checklist', - owner_id: user.userId, - risks: [], - tags_goals: ['GMF:Known AI Goal:Translation'], - tags_methods: [], - tags_other: [], - date_created: '2024-01-01T00:26:02.959+00:00', - date_updated: '2024-01-05T00:26:02.959+00:00', - }, - { - about: '', - id: 'fakeChecklist2', - name: 'Another checklist', - owner_id: user.userId, - risks: [], - tags_goals: [], - tags_methods: [], - tags_other: [], - date_created: '2024-01-03T00:26:02.959+00:00', - date_updated: '2024-01-03T00:26:02.959+00:00', - }, - ], - }, - } - ); - - cy.login(Cypress.env('e2eUsername'), Cypress.env('e2ePassword')); - - cy.visit(url); - - cy.get('#sort-by').select('newest-first'); - - cy.get('[data-cy="checklist-card"]').first().contains('Another checklist'); - - cy.get('#sort-by').select('last-updated'); - - cy.get('[data-cy="checklist-card"]').first().contains('My Checklist'); - - cy.get('#sort-by').select('alphabetical'); - - cy.get('[data-cy="checklist-card"]').first().contains('Another checklist'); - - cy.get('#sort-by').select('oldest-first'); - - cy.get('[data-cy="checklist-card"]').first().contains('My Checklist'); - }); - }); - - it('Should not display New Checklist button as non-logged-in user', () => { - cy.visit(url); - - cy.get(newChecklistButtonQuery).should('not.exist'); - }); - - maybeIt('Should display New Checklist button as logged-in user', () => { - cy.login(Cypress.env('e2eUsername'), Cypress.env('e2ePassword')); - - cy.visit(url); - - cy.get(newChecklistButtonQuery).should('exist'); - }); - - /* We're now showing only the user's owned checklists, - * so we can't test that the delete button doesn't show up on unowned ones. - * Eventually, we'll probably have public checklists show up here too, - * so this can be skipped with a TODO to activate it - * once there are unowned checklists displayed. - */ - it.skip('Should show delete buttons only for owned checklists', () => { - cy.login(Cypress.env('e2eUsername'), Cypress.env('e2ePassword')); - - cy.query(usersQuery).then(({ data: { users } }) => { - const user = users.find((user) => user.adminData.email == Cypress.env('e2eUsername')); - - cy.conditionalIntercept( - '**/graphql', - (req) => req.body.operationName == 'findChecklists', - 'findChecklists', - { - data: { - checklists: [ - { - about: '', - id: 'fakeChecklist1', - name: 'My Checklist', - owner_id: user.userId, - risks: [], - tags_goals: [], - tags_methods: [], - tags_other: [], - }, - { - about: '', - id: 'fakeChecklist2', - name: "Somebody Else's Checklist", - owner_id: 'aFakeUserId', - risks: [], - tags_goals: [], - tags_methods: [], - tags_other: [], - }, - ], - }, - } - ); - - cy.visit(url); - - cy.wait(['@findChecklists']); - - cy.waitForStableDOM(); - - cy.get('[data-cy="checklist-card"]:first-child button').contains('Delete').should('exist'); - - cy.get('[data-cy="checklist-card"]:last-child button').contains('Delete').should('not.exist'); - }); - }); - - it('Should show toast on error fetching checklists', () => { - cy.conditionalIntercept( - '**/graphql', - (req) => req.body.operationName == 'findChecklists', - 'findChecklists', - { errors: [testError] } - ); - - cy.visit(url); - - cy.get('[data-cy="toast"]').contains('Could not fetch checklists').should('exist'); - }); - - it('Should show toast on error fetching risks', () => { - cy.query(usersQuery).then(({ data: { users } }) => { - const user = users.find((user) => user.adminData.email == Cypress.env('e2eUsername')); - - cy.conditionalIntercept( - '**/graphql', - (req) => req.body.operationName == 'findChecklists', - 'findChecklists', - { - data: { - checklists: [ - { - about: '', - id: 'fakeChecklist1', - name: 'My Checklist', - owner_id: user.userId, - risks: [], - tags_goals: ['GMF:Known AI Goal:Translation'], - tags_methods: [], - tags_other: [], - }, - { - about: '', - id: 'fakeChecklist2', - name: "Somebody Else's Checklist", - owner_id: 'aFakeUserId', - risks: [], - tags_goals: [], - tags_methods: [], - tags_other: [], - }, - ], - }, - } - ); - - cy.conditionalIntercept('**/graphql', (req) => req.body.query.includes('GMF'), 'risks', { - errors: [testError], - }); - - cy.login(Cypress.env('e2eUsername'), Cypress.env('e2ePassword')); - - cy.visit(url); - - cy.get('[data-cy="toast"]').contains('Failure searching for risks').should('exist'); - }); - }); - - maybeIt('Should show toast on error creating checklist', () => { - cy.query(usersQuery).then(({ data: { users } }) => { - const user = users.find((user) => user.adminData.email == Cypress.env('e2eUsername')); - - cy.conditionalIntercept( - '**/graphql', - (req) => req.body.operationName == 'insertChecklist', - 'insertChecklist', - { errors: [testError] } - ); - - cy.conditionalIntercept( - '**/graphql', - (req) => req.body.operationName == 'findChecklists', - 'findChecklists', - { - data: { - checklists: [ - { - about: '', - id: 'fakeChecklist1', - name: 'My Checklist', - owner_id: user.userId, - risks: [], - tags_goals: [], - tags_methods: [], - tags_other: [], - }, - { - about: '', - id: 'fakeChecklist2', - name: "Somebody Else's Checklist", - owner_id: 'aFakeUserId', - risks: [], - tags_goals: [], - tags_methods: [], - tags_other: [], - }, - ], - }, - } - ); - - cy.login(Cypress.env('e2eUsername'), Cypress.env('e2ePassword')); - - cy.visit(url); - - cy.get(newChecklistButtonQuery).click(); - - cy.get('[data-cy="toast"]').contains('Could not create checklist.').should('exist'); - }); - }); -}); diff --git a/site/gatsby-site/cypress/e2e/integration/apps/submitted.cy.js b/site/gatsby-site/cypress/e2e/integration/apps/submitted.cy.js deleted file mode 100644 index c2a4298696..0000000000 --- a/site/gatsby-site/cypress/e2e/integration/apps/submitted.cy.js +++ /dev/null @@ -1,2509 +0,0 @@ -import { maybeIt } from '../../../support/utils'; -import submittedReports from '../../../fixtures/submissions/submitted.json'; -import quickAdds from '../../../fixtures/submissions/quickadds.json'; -import parseNews from '../../../fixtures/api/parseNews.json'; -import { isArray } from 'lodash'; -import { arrayToList } from '../../../../src/utils/typography'; -import { SUBSCRIPTION_TYPE } from '../../../../src/utils/subscriptions'; -import { format, getUnixTime } from 'date-fns'; -const { gql } = require('@apollo/client'); - -describe('Submitted reports', () => { - const url = '/apps/submitted'; - - let user; - - before('before', () => { - cy.query({ - query: gql` - { - user(query: { first_name: "Test", last_name: "User" }) { - userId - first_name - last_name - roles - adminData { - email - } - } - } - `, - }).then(({ data: { user: userData } }) => { - user = userData; - }); - }); - - it('Loads submissions', () => { - const submissions = submittedReports.data.submissions.slice(0, 10); - - cy.conditionalIntercept( - '**/graphql', - (req) => req.body.operationName == 'FindSubmissions', - 'FindSubmission', - { - data: { - submissions, - }, - } - ); - - cy.conditionalIntercept( - '**/graphql', - (req) => req.body.operationName == 'AllQuickAdd', - 'AllQuickAdd', - { - data: { - quickadds: [quickAdds], - }, - } - ); - - cy.visit(url); - - cy.wait('@FindSubmission'); - - cy.wait('@AllQuickAdd'); - - cy.get('[data-cy="submissions"] [data-cy="row"]').should('have.length', submissions.length); - - submissions.forEach((report, index) => { - cy.get('[data-cy="submissions"] [data-cy="row"]') - .eq(index) - .then((element) => { - cy.wrap(element) - .find('[data-cy="cell"] [data-cy="review-submission"]') - .should('not.exist'); - }); - - cy.get('[data-cy="submissions"] [data-cy="row"]') - .eq(index) - .then((element) => { - const keys = ['title', 'submitters', 'incident_date', 'editor', 'status']; - - cy.wrap(element) - .find('[data-cy="cell"]') - .each((cell, cellIndex) => { - if (report[keys[cellIndex]]) { - let value = report[keys[cellIndex]]; - - if (isArray(value)) { - value = arrayToList(value); - } - cy.wrap(cell).should('contain', value); - } - }); - }); - }); - }); - - maybeIt('Promotes a submission to a new report and links it to a new incident', () => { - cy.login(Cypress.env('e2eUsername'), Cypress.env('e2ePassword')); - - if (user.adminData.email == Cypress.env('e2eUsername')) { - expect(user.roles.some((role) => ['admin', 'incident_editor'].includes(role))).to.be.true; - } - - const submission = submittedReports.data.submissions.find( - (r) => r._id === '5f9c3ebfd4896d392493f03c' - ); - - cy.conditionalIntercept( - '**/graphql', - (req) => req.body.operationName == 'FindSubmission', - 'FindSubmission', - { - data: { - submission: submission, - }, - } - ); - - cy.conditionalIntercept( - '**/graphql', - (req) => req.body.operationName == 'AllQuickAdd', - 'AllQuickAdd', - { - data: { - quickadds: [quickAdds], - }, - } - ); - - cy.conditionalIntercept( - '**/graphql', - (req) => req.body.operationName == 'FindEntities', - 'FindEntities', - { - data: { - entities: [ - { __typename: 'Entity', entity_id: 'Adults', name: 'adults' }, - { __typename: 'Entity', entity_id: 'Google', name: 'google' }, - ], - }, - } - ); - - cy.conditionalIntercept( - '**/graphql', - (req) => req.body.operationName == 'FindIncidentsTitles', - 'FindIncidentsTitles', - { - data: { - incidents: [ - { - __typename: 'Incident', - incident_id: 1, - title: 'Test title', - date: '2016-03-13', - }, - ], - }, - } - ); - - cy.conditionalIntercept( - '**/graphql', - (req) => req.body.operationName == 'ProbablyRelatedReports', - 'ProbablyRelatedReports', - { - data: { - reports: [ - { - report_number: 1628, - title: - 'Pasco’s sheriff uses grades and abuse histories to label schoolchildren potential criminals', - url: 'https://projects.tampabay.com/projects/2020/investigations/police-pasco-sheriff-targeted/school-data/', - }, - ], - }, - } - ); - - cy.conditionalIntercept( - '**/graphql', - (req) => req.body.operationName == 'ProbablyRelatedIncidents', - 'ProbablyRelatedIncidents', - { - data: { - incidents: [ - { - incident_id: 195, - title: - 'Predictive Policing Program by Florida Sheriff’s Office Allegedly Violated Residents’ Rights and Targeted Children of Vulnerable Groups', - reports: [ - { - report_number: 1843, - title: 'The man behind the machine', - url: 'https://projects.tampabay.com/projects/2020/investigations/police-pasco-sheriff-targeted/chris-nocco/', - __typename: 'Report', - }, - ], - }, - ], - }, - } - ); - - cy.visit(url + `?editSubmission=${submission._id}`); - - cy.wait('@AllQuickAdd'); - - cy.waitForStableDOM(); - - cy.conditionalIntercept( - '**/graphql', - (req) => req.body.operationName == 'PromoteSubmission', - 'promoteSubmission', - { - data: { - promoteSubmissionToReport: { - incident_ids: [182], - report_number: 1565, - }, - }, - } - ); - - cy.conditionalIntercept( - '**/graphql', - (req) => - req.body.operationName == 'UpsertSubscription' && - req.body.variables?.query?.type === SUBSCRIPTION_TYPE.incident, - 'UpsertSubscription', - { - data: { - upsertOneSubscription: { - _id: 'dummyIncidentId', - }, - }, - } - ); - - cy.get('select[data-cy="promote-select"]').as('dropdown'); - - cy.get('@dropdown').select('Incident'); - - cy.get('[data-cy="promote-button"]').click(); - - cy.wait('@promoteSubmission') - .its('request.body.variables.input') - .then((input) => { - expect(input.incident_ids).to.deep.eq([]); - expect(input.submission_id).to.eq('5f9c3ebfd4896d392493f03c'); - expect(input.is_incident_report).to.eq(true); - }); - - cy.wait('@UpsertSubscription') - .its('request.body.variables') - .then((variables) => { - expect(variables.query.type).to.eq(SUBSCRIPTION_TYPE.incident); - expect(variables.query.incident_id.incident_id).to.eq(182); - expect(variables.query.userId.userId).to.eq(user.userId); - - expect(variables.subscription.type).to.eq(SUBSCRIPTION_TYPE.incident); - expect(variables.subscription.incident_id.link).to.eq(182); - expect(variables.subscription.userId.link).to.eq(user.userId); - }); - - cy.contains( - '[data-cy="toast"]', - 'Successfully promoted submission to Incident 182 and Report 1565' - ).should('exist'); - }); - - maybeIt('Promotes a submission to a new report and links it to an existing incident', () => { - cy.login(Cypress.env('e2eUsername'), Cypress.env('e2ePassword')); - - if (user.adminData.email == Cypress.env('e2eUsername')) { - expect(user.roles.some((role) => ['admin', 'incident_editor'].includes(role))).to.be.true; - } - - const submission = submittedReports.data.submissions.find( - (r) => r.incident_ids.length == 1 && r.incident_ids.includes(10) - ); - - cy.conditionalIntercept( - '**/graphql', - (req) => req.body.operationName == 'FindSubmission', - 'FindSubmission', - { - data: { - submission: submission, - }, - } - ); - - cy.conditionalIntercept( - '**/graphql', - (req) => req.body.operationName == 'AllQuickAdd', - 'AllQuickAdd', - { - data: { - quickadds: [quickAdds], - }, - } - ); - - cy.conditionalIntercept( - '**/graphql', - (req) => req.body.operationName == 'FindEntities', - 'FindEntities', - { - data: { - entities: [ - { __typename: 'Entity', entity_id: 'Adults', name: 'adults' }, - { __typename: 'Entity', entity_id: 'Google', name: 'google' }, - ], - }, - } - ); - - cy.conditionalIntercept( - '**/graphql', - (req) => req.body.operationName == 'FindIncidentsTitles', - 'FindIncidentsTitles', - { - data: { - incidents: [ - { - __typename: 'Incident', - incident_id: 10, - title: 'Test title', - date: '2016-03-13', - }, - ], - }, - } - ); - - cy.conditionalIntercept( - '**/graphql', - (req) => req.body.operationName == 'ProbablyRelatedReports', - 'ProbablyRelatedReports', - { - data: { - reports: [ - { - report_number: 1628, - title: - 'Pasco’s sheriff uses grades and abuse histories to label schoolchildren potential criminals', - url: 'https://projects.tampabay.com/projects/2020/investigations/police-pasco-sheriff-targeted/school-data/', - }, - ], - }, - } - ); - - cy.conditionalIntercept( - '**/graphql', - (req) => req.body.operationName == 'ProbablyRelatedIncidents', - 'ProbablyRelatedIncidents', - { - data: { - incidents: [ - { - incident_id: 195, - title: - 'Predictive Policing Program by Florida Sheriff’s Office Allegedly Violated Residents’ Rights and Targeted Children of Vulnerable Groups', - reports: [ - { - report_number: 1843, - title: 'The man behind the machine', - url: 'https://projects.tampabay.com/projects/2020/investigations/police-pasco-sheriff-targeted/chris-nocco/', - __typename: 'Report', - }, - ], - }, - ], - }, - } - ); - - cy.visit(url + `?editSubmission=${submission._id}}`); - - cy.wait('@AllQuickAdd'); - - cy.waitForStableDOM(); - - cy.conditionalIntercept( - '**/graphql', - (req) => req.body.operationName == 'PromoteSubmission', - 'promoteSubmission', - { - data: { - promoteSubmissionToReport: { - incident_ids: [10], - report_number: 1566, - }, - }, - } - ); - - cy.conditionalIntercept( - '**/graphql', - (req) => req.body.operationName == 'UpsertSubscription', - 'UpsertSubscription', - { - data: { - upsertOneSubscription: { - _id: 'dummyIncidentId', - }, - }, - } - ); - - cy.get('[data-cy="promote-to-report-button"]').click(); - - cy.wait('@promoteSubmission') - .its('request.body.variables.input') - .then((input) => { - expect(input.incident_ids).to.deep.eq([10]); - expect(input.submission_id).to.eq('6123bf345e740c1a81850e89'); - expect(input.is_incident_report).to.eq(true); - }); - - cy.wait('@UpsertSubscription') - .its('request.body.variables') - .then((variables) => { - expect(variables.query.type).to.eq(SUBSCRIPTION_TYPE.incident); - expect(variables.query.incident_id.incident_id).to.eq(10); - expect(variables.query.userId.userId).to.eq(user.userId); - - expect(variables.subscription.type).to.eq(SUBSCRIPTION_TYPE.incident); - expect(variables.subscription.incident_id.link).to.eq(10); - expect(variables.subscription.userId.link).to.eq(user.userId); - }); - - cy.contains( - '[data-cy="toast"]', - 'Successfully promoted submission to Incident 10 and Report 1566' - ).should('exist'); - }); - - maybeIt('Promotes a submission to a new report and links it to multiple incidents', () => { - cy.login(Cypress.env('e2eUsername'), Cypress.env('e2ePassword')); - - if (user.adminData.email == Cypress.env('e2eUsername')) { - expect(user.roles.some((role) => ['admin', 'incident_editor'].includes(role))).to.be.true; - } - - const submission = submittedReports.data.submissions.find( - (r) => r._id == '444461606b4bb5e39601234' - ); - - cy.conditionalIntercept( - '**/graphql', - (req) => req.body.operationName == 'FindSubmission', - 'FindSubmission', - { - data: { - submission: submission, - }, - } - ); - - cy.conditionalIntercept( - '**/graphql', - (req) => req.body.operationName == 'AllQuickAdd', - 'AllQuickAdd', - { - data: { - quickadds: [quickAdds], - }, - } - ); - - cy.conditionalIntercept( - '**/graphql', - (req) => req.body.operationName == 'FindEntities', - 'FindEntities', - { - data: { - entities: [ - { __typename: 'Entity', entity_id: 'Adults', name: 'adults' }, - { __typename: 'Entity', entity_id: 'Google', name: 'google' }, - ], - }, - } - ); - - cy.conditionalIntercept( - '**/graphql', - (req) => req.body.operationName == 'FindIncidentsTitles', - 'FindIncidentsTitles', - { - data: { - incidents: [ - { - __typename: 'Incident', - incident_id: 52, - title: 'Test title 52', - date: '2016-03-13', - }, - { - __typename: 'Incident', - incident_id: 53, - title: 'Test title 53', - date: '2016-03-13', - }, - ], - }, - } - ); - - cy.conditionalIntercept( - '**/graphql', - (req) => req.body.operationName == 'ProbablyRelatedReports', - 'ProbablyRelatedReports', - { - data: { - reports: [ - { - report_number: 1628, - title: - 'Pasco’s sheriff uses grades and abuse histories to label schoolchildren potential criminals', - url: 'https://projects.tampabay.com/projects/2020/investigations/police-pasco-sheriff-targeted/school-data/', - }, - ], - }, - } - ); - - cy.conditionalIntercept( - '**/graphql', - (req) => req.body.operationName == 'ProbablyRelatedIncidents', - 'ProbablyRelatedIncidents', - { - data: { - incidents: [ - { - incident_id: 195, - title: - 'Predictive Policing Program by Florida Sheriff’s Office Allegedly Violated Residents’ Rights and Targeted Children of Vulnerable Groups', - reports: [ - { - report_number: 1843, - title: 'The man behind the machine', - url: 'https://projects.tampabay.com/projects/2020/investigations/police-pasco-sheriff-targeted/chris-nocco/', - __typename: 'Report', - }, - ], - }, - ], - }, - } - ); - - cy.visit(url + `?editSubmission=${submission._id}}`); - - cy.wait('@AllQuickAdd'); - - cy.conditionalIntercept( - '**/graphql', - (req) => req.body.operationName == 'PromoteSubmission', - 'promoteSubmission', - { - data: { - promoteSubmissionToReport: { - incident_ids: [52, 53], - report_number: 1566, - }, - }, - } - ); - - cy.conditionalIntercept( - '**/graphql', - (req) => - req.body.operationName == 'UpsertSubscription' && - req.body.variables.query.incident_id.incident_id == 52, - 'UpsertSubscription1', - { - data: { - upsertOneSubscription: { - _id: 'dummyIncidentId', - }, - }, - } - ); - - cy.conditionalIntercept( - '**/graphql', - (req) => - req.body.operationName == 'UpsertSubscription' && - req.body.variables.query.incident_id.incident_id == 53, - 'UpsertSubscription2', - { - data: { - upsertOneSubscription: { - _id: 'dummyIncidentId', - }, - }, - } - ); - - cy.get('[data-cy="promote-to-report-button"]').click(); - - cy.wait('@promoteSubmission') - .its('request.body.variables.input') - .then((input) => { - expect(input.incident_ids).to.deep.eq([52, 53]); - expect(input.submission_id).to.eq('444461606b4bb5e39601234'); - expect(input.is_incident_report).to.eq(true); - }); - - cy.wait('@UpsertSubscription1') - .its('request.body.variables') - .then((variables) => { - expect(variables.query.type).to.eq(SUBSCRIPTION_TYPE.incident); - expect(variables.query.incident_id.incident_id).to.eq(52); - expect(variables.query.userId.userId).to.eq(user.userId); - - expect(variables.subscription.type).to.eq(SUBSCRIPTION_TYPE.incident); - expect(variables.subscription.incident_id.link).to.eq(52); - expect(variables.subscription.userId.link).to.eq(user.userId); - }); - - cy.wait('@UpsertSubscription2') - .its('request.body.variables') - .then((variables) => { - expect(variables.query.type).to.eq(SUBSCRIPTION_TYPE.incident); - expect(variables.query.incident_id.incident_id).to.eq(53); - expect(variables.query.userId.userId).to.eq(user.userId); - - expect(variables.subscription.type).to.eq(SUBSCRIPTION_TYPE.incident); - expect(variables.subscription.incident_id.link).to.eq(53); - expect(variables.subscription.userId.link).to.eq(user.userId); - }); - - cy.contains( - '[data-cy="toast"]', - 'Successfully promoted submission to Incident 52 and Report 1566' - ).should('exist'); - - cy.contains( - '[data-cy="toast"]', - 'Successfully promoted submission to Incident 53 and Report 1566' - ).should('exist'); - }); - - maybeIt('Promotes a submission to a new issue', () => { - cy.login(Cypress.env('e2eUsername'), Cypress.env('e2ePassword')); - - if (user.adminData.email == Cypress.env('e2eUsername')) { - expect(user.roles.some((role) => ['admin', 'incident_editor'].includes(role))).to.be.true; - } - - const submission = submittedReports.data.submissions.find( - (r) => r._id === '62d561606b4bb5e39605555' - ); - - cy.conditionalIntercept( - '**/graphql', - (req) => req.body.operationName == 'FindSubmission', - 'FindSubmission', - { - data: { - submission: submission, - }, - } - ); - - cy.conditionalIntercept( - '**/graphql', - (req) => req.body.operationName == 'AllQuickAdd', - 'AllQuickAdd', - { - data: { - quickadds: [quickAdds], - }, - } - ); - - cy.conditionalIntercept( - '**/graphql', - (req) => req.body.operationName == 'FindEntities', - 'FindEntities', - { - data: { - entities: [ - { __typename: 'Entity', entity_id: 'Adults', name: 'adults' }, - { __typename: 'Entity', entity_id: 'Google', name: 'google' }, - ], - }, - } - ); - - cy.conditionalIntercept( - '**/graphql', - (req) => req.body.operationName == 'FindIncidentsTitles', - 'FindIncidentsTitles', - { - data: { - incidents: [ - { - __typename: 'Incident', - incident_id: 10, - title: 'Test title 52', - date: '2016-03-13', - }, - ], - }, - } - ); - - cy.conditionalIntercept( - '**/graphql', - (req) => req.body.operationName == 'ProbablyRelatedReports', - 'ProbablyRelatedReports', - { - data: { - reports: [ - { - report_number: 1628, - title: - 'Pasco’s sheriff uses grades and abuse histories to label schoolchildren potential criminals', - url: 'https://projects.tampabay.com/projects/2020/investigations/police-pasco-sheriff-targeted/school-data/', - }, - ], - }, - } - ); - - cy.conditionalIntercept( - '**/graphql', - (req) => req.body.operationName == 'ProbablyRelatedIncidents', - 'ProbablyRelatedIncidents', - { - data: { - incidents: [ - { - incident_id: 195, - title: - 'Predictive Policing Program by Florida Sheriff’s Office Allegedly Violated Residents’ Rights and Targeted Children of Vulnerable Groups', - reports: [ - { - report_number: 1843, - title: 'The man behind the machine', - url: 'https://projects.tampabay.com/projects/2020/investigations/police-pasco-sheriff-targeted/chris-nocco/', - __typename: 'Report', - }, - ], - }, - ], - }, - } - ); - - cy.visit(url + `?editSubmission=${submission._id}`); - - cy.wait('@AllQuickAdd'); - - cy.waitForStableDOM(); - - cy.conditionalIntercept( - '**/graphql', - (req) => req.body.operationName == 'PromoteSubmission', - 'promoteSubmission', - { - data: { - promoteSubmissionToReport: { - incident_ids: [10], - report_number: 1566, - }, - }, - } - ); - - cy.get('select[data-cy="promote-select"]').as('dropdown'); - - cy.get('@dropdown').select('Issue'); - - cy.get('[data-cy="promote-button"]').click(); - - cy.wait('@promoteSubmission') - .its('request.body.variables.input') - .then((input) => { - expect(input.incident_ids).to.deep.eq([]); - expect(input.submission_id).to.eq('62d561606b4bb5e39605555'); - expect(input.is_incident_report).to.eq(false); - }); - - cy.contains('[data-cy="toast"]', 'Successfully promoted submission to Issue 1566').should( - 'exist' - ); - }); - - maybeIt('Rejects a submission', () => { - cy.login(Cypress.env('e2eUsername'), Cypress.env('e2ePassword')); - - if (user.adminData.email == Cypress.env('e2eUsername')) { - expect(user.roles.some((role) => ['admin', 'incident_editor'].includes(role))).to.be.true; - } - - const submission = submittedReports.data.submissions.find( - (r) => r.incident_ids.length == 1 && r.incident_ids.includes(10) - ); - - cy.conditionalIntercept( - '**/graphql', - (req) => req.body.operationName == 'FindSubmission', - 'FindSubmission', - { - data: { - submission: submission, - }, - } - ); - - cy.conditionalIntercept( - '**/graphql', - (req) => req.body.operationName == 'AllQuickAdd', - 'AllQuickAdd', - { - data: { - quickadds: [quickAdds], - }, - } - ); - - cy.conditionalIntercept( - '**/graphql', - (req) => req.body.operationName == 'FindEntities', - 'FindEntities', - { - data: { - entities: [ - { __typename: 'Entity', entity_id: 'Adults', name: 'adults' }, - { __typename: 'Entity', entity_id: 'Google', name: 'google' }, - ], - }, - } - ); - - cy.conditionalIntercept( - '**/graphql', - (req) => req.body.operationName == 'FindIncidentsTitles', - 'FindIncidentsTitles', - { - data: { - incidents: [ - { - __typename: 'Incident', - incident_id: 10, - title: 'Test title 52', - date: '2016-03-13', - }, - ], - }, - } - ); - - cy.conditionalIntercept( - '**/graphql', - (req) => req.body.operationName == 'ProbablyRelatedReports', - 'ProbablyRelatedReports', - { - data: { - reports: [ - { - report_number: 1628, - title: - 'Pasco’s sheriff uses grades and abuse histories to label schoolchildren potential criminals', - url: 'https://projects.tampabay.com/projects/2020/investigations/police-pasco-sheriff-targeted/school-data/', - }, - ], - }, - } - ); - - cy.conditionalIntercept( - '**/graphql', - (req) => req.body.operationName == 'ProbablyRelatedIncidents', - 'ProbablyRelatedIncidents', - { - data: { - incidents: [ - { - incident_id: 195, - title: - 'Predictive Policing Program by Florida Sheriff’s Office Allegedly Violated Residents’ Rights and Targeted Children of Vulnerable Groups', - reports: [ - { - report_number: 1843, - title: 'The man behind the machine', - url: 'https://projects.tampabay.com/projects/2020/investigations/police-pasco-sheriff-targeted/chris-nocco/', - __typename: 'Report', - }, - ], - }, - ], - }, - } - ); - - cy.visit(url + `?editSubmission=${submission._id}`); - - cy.wait('@AllQuickAdd'); - - cy.waitForStableDOM(); - - cy.conditionalIntercept( - '**/graphql', - (req) => req.body.operationName == 'DeleteSubmission', - 'DeleteSubmission', - { - data: { - deleteOneSubmission: { __typename: 'Submission', _id: '6123bf345e740c1a81850e89' }, - }, - } - ); - - cy.get('[data-cy="reject-button"]').click(); - - cy.wait('@DeleteSubmission').then((xhr) => { - expect(xhr.request.body.variables._id).to.eq('6123bf345e740c1a81850e89'); - }); - }); - - maybeIt('Edits a submission - update just a text', () => { - cy.login(Cypress.env('e2eUsername'), Cypress.env('e2ePassword')); - - if (user.adminData.email == Cypress.env('e2eUsername')) { - expect(user.roles.some((role) => ['admin', 'incident_editor'].includes(role))).to.be.true; - } - - cy.conditionalIntercept( - '**/graphql', - (req) => req.body.operationName == 'FindSubmissions', - 'FindSubmissions', - submittedReports - ); - - cy.conditionalIntercept( - '**/graphql', - (req) => req.body.operationName == 'FindSubmission', - 'FindSubmission', - { - data: { - submission: submittedReports.data.submissions[0], - }, - } - ); - - cy.conditionalIntercept( - '**/graphql', - (req) => req.body.operationName == 'FindEntities', - 'FindEntities', - { - data: { - entities: [ - { __typename: 'Entity', entity_id: 'Adults', name: 'adults' }, - { __typename: 'Entity', entity_id: 'Google', name: 'google' }, - ], - }, - } - ); - - cy.conditionalIntercept( - '**/graphql', - (req) => req.body.operationName == 'AllQuickAdd', - 'AllQuickAdd', - { - data: { - quickadds: [ - { - _id: '6604507c78fd656005852a22', - date_submitted: '2024-03-27', - url: 'https://www.wired.com/story/robert-f-kennedy-jr-chatbot-microsoft-openai-disappeared/', - source_domain: 'wired.com', - __typename: 'Quickadd', - }, - ], - }, - } - ); - - cy.conditionalIntercept( - '**/graphql', - (req) => req.body.operationName == 'ProbablyRelatedIncidents', - 'ProbablyRelatedIncidents', - { - data: { - incidents: [ - { - incident_id: 195, - title: - 'Predictive Policing Program by Florida Sheriff’s Office Allegedly Violated Residents’ Rights and Targeted Children of Vulnerable Groups', - reports: [ - { - report_number: 1843, - title: 'The man behind the machine', - url: 'https://projects.tampabay.com/projects/2020/investigations/police-pasco-sheriff-targeted/chris-nocco/', - __typename: 'Report', - }, - ], - }, - ], - }, - } - ); - - cy.conditionalIntercept( - '**/graphql', - (req) => req.body.operationName == 'FindIncidentsTitles', - 'FindIncidentsTitles', - { - data: { - incidents: [ - { - __typename: 'Incident', - incident_id: 1, - title: 'Test title', - date: '2016-03-13', - }, - ], - }, - } - ); - - cy.conditionalIntercept( - '**/graphql', - (req) => req.body.operationName == 'ProbablyRelatedReports', - 'ProbablyRelatedReports', - { - data: { - reports: [ - { - report_number: 1628, - title: - 'Pasco’s sheriff uses grades and abuse histories to label schoolchildren potential criminals', - url: 'https://projects.tampabay.com/projects/2020/investigations/police-pasco-sheriff-targeted/school-data/', - }, - ], - }, - } - ); - - cy.visit(url + `?editSubmission=${submittedReports.data.submissions[0]._id}`); - - cy.setEditorText( - '## Another one\n\n**More markdown**\n\nAnother paragraph with more text to reach the minimum character count!' - ); - - cy.conditionalIntercept( - '**/graphql', - (req) => req.body.operationName === 'UpdateSubmission', - 'UpdateSubmission', - { - data: { - updateOneSubmission: { - ...submittedReports.data.submissions[0], - text: '## Another one\n\n**More markdown**\n\nAnother paragraph with more text to reach the minimum character count!', - plain_text: - 'Another one\n\nMore markdown\n\nAnother paragraph with more text to reach the minimum character count!\n', - }, - }, - } - ); - - cy.conditionalIntercept( - '**/graphql', - (req) => - req.body.operationName == 'UpsertEntity' && req.body.variables.entity.entity_id == 'adults', - 'UpsertAdults', - { - data: { - upsertOneEntity: { __typename: 'Entity', entity_id: 'adults', name: 'Adults' }, - }, - } - ); - - cy.conditionalIntercept( - '**/graphql', - (req) => - req.body.operationName == 'UpsertEntity' && req.body.variables.entity.entity_id == 'google', - 'UpsertGoogle', - { - data: { - upsertOneEntity: { __typename: 'Entity', entity_id: 'google', name: 'Google' }, - }, - } - ); - const now = new Date(); - - cy.clock(now); - - cy.wait('@UpsertGoogle').its('request.body.variables.entity.entity_id').should('eq', 'google'); - - cy.wait('@UpsertAdults').its('request.body.variables.entity.entity_id').should('eq', 'adults'); - - cy.wait('@UpdateSubmission').then((xhr) => { - expect(xhr.request.body.variables.query).to.deep.nested.include({ - _id: submittedReports.data.submissions[0]._id, - }); - expect(xhr.request.body.variables.set).to.deep.eq({ - authors: ['Nedi Bedi and Kathleen McGrory'], - cloudinary_id: 'reports/s3.amazonaws.com/ledejs/resized/s2020-pasco-ilp/600/nocco5.jpg', - date_downloaded: '2020-10-30', - date_modified: format(now, 'yyyy-MM-dd'), - date_published: '2017-05-03', - date_submitted: '2020-10-30', - epoch_date_modified: getUnixTime(now), - description: - 'By NEIL BEDI and KATHLEEN McGRORY\nTimes staff writers\nNov. 19, 2020\nThe Pasco Sheriff’s Office keeps a secret list of kids it thinks could “fall into a life of crime” based on factors like wheth', - image_url: 'https://s3.amazonaws.com/ledejs/resized/s2020-pasco-ilp/600/nocco5.jpg', - incident_title: 'Submisssion 1 title', - incident_date: '2015-09-01', - incident_ids: [], - language: 'en', - source_domain: 'projects.tampabay.com', - submitters: ['Kate Perkins'], - text: '## Another one\n\n**More markdown**\n\nAnother paragraph with more text to reach the minimum character count!', - plain_text: - 'Another one\n\nMore markdown\n\nAnother paragraph with more text to reach the minimum character count!\n', - title: 'Submisssion 1 title', - url: 'https://projects.tampabay.com/projects/2020/investigations/police-pasco-sheriff-targeted/school-data/', - editor_notes: '', - developers: { link: ['google'] }, - deployers: { link: ['google'] }, - harmed_parties: { link: ['adults'] }, - nlp_similar_incidents: [], - editor_dissimilar_incidents: [], - editor_similar_incidents: [], - tags: [], - incident_editors: { link: [] }, - user: { - userId: '62cd9520a69a2cdf17fb47db', - }, - }); - }); - }); - - maybeIt('Edits a submission - uses fetch info', () => { - cy.login(Cypress.env('e2eUsername'), Cypress.env('e2ePassword')); - - if (user.adminData.email == Cypress.env('e2eUsername')) { - expect(user.roles.some((role) => ['admin', 'incident_editor'].includes(role))).to.be.true; - } - - cy.conditionalIntercept( - '**/graphql', - (req) => req.body.operationName == 'FindSubmission', - 'FindSubmission', - { - data: { - submission: submittedReports.data.submissions[0], - }, - } - ); - - cy.conditionalIntercept( - '**/graphql', - (req) => req.body.operationName == 'ProbablyRelatedIncidents', - 'ProbablyRelatedIncidents', - { - data: { - incidents: [ - { - incident_id: 195, - title: - 'Predictive Policing Program by Florida Sheriff’s Office Allegedly Violated Residents’ Rights and Targeted Children of Vulnerable Groups', - reports: [ - { - report_number: 1843, - title: 'The man behind the machine', - url: 'https://projects.tampabay.com/projects/2020/investigations/police-pasco-sheriff-targeted/chris-nocco/', - __typename: 'Report', - }, - ], - }, - ], - }, - } - ); - - cy.conditionalIntercept( - '**/graphql', - (req) => req.body.operationName == 'FindIncidentsTitles', - 'FindIncidentsTitles', - { - data: { - incidents: [ - { - __typename: 'Incident', - incident_id: 1, - title: 'Test title', - date: '2016-03-13', - }, - ], - }, - } - ); - - cy.conditionalIntercept( - '**/graphql', - (req) => req.body.operationName == 'ProbablyRelatedReports', - 'ProbablyRelatedReports', - { - data: { - reports: [ - { - report_number: 1628, - title: - 'Pasco’s sheriff uses grades and abuse histories to label schoolchildren potential criminals', - url: 'https://projects.tampabay.com/projects/2020/investigations/police-pasco-sheriff-targeted/school-data/', - }, - ], - }, - } - ); - - cy.intercept('GET', '/api/parseNews**', parseNews).as('parseNews'); - - cy.visit(url + `?editSubmission=${submittedReports.data.submissions[0]._id}`); - - cy.conditionalIntercept( - '**/graphql', - (req) => req.body.operationName == 'FindEntities', - 'FindEntities', - { - data: { - entities: [ - { __typename: 'Entity', entity_id: 'Adults', name: 'adults' }, - { __typename: 'Entity', entity_id: 'Google', name: 'google' }, - { __typename: 'Entity', entity_id: 'Tesla', name: 'tesla' }, - ], - }, - } - ); - - cy.get('input[name="url"]').click(); - - cy.clickOutside(); - - cy.get('[data-cy="fetch-info"]').click(); - - cy.wait('@parseNews'); - - cy.get('input[label="Title"]').should( - 'have.attr', - 'value', - 'YouTube to crack down on inappropriate content masked as kids’ cartoons' - ); - - cy.get('input[name="harmed_parties"]').type('Tes'); - - cy.get('#harmed_parties-tags .dropdown-item') - .contains(/^Tesla$/) - .click(); - - cy.get('input[label="Image Address"]').should( - 'have.attr', - 'value', - 'https://cdn.arstechnica.net/wp-content/uploads/2017/11/Screen-Shot-2017-11-10-at-9.25.47-AM-760x380.png' - ); - cy.get('input[label="Date Published"]').should('have.attr', 'value', '2017-11-10'); - cy.get('input[label="Date Downloaded"]').should('have.attr', 'value', '2022-05-26'); - }); - - maybeIt( - 'Does not allow promotion of submission to Incident if schema is invalid (missing Description).', - () => { - cy.login(Cypress.env('e2eUsername'), Cypress.env('e2ePassword')); - - if (user.adminData.email == Cypress.env('e2eUsername')) { - expect(user.roles.some((role) => ['admin', 'incident_editor'].includes(role))).to.be.true; - } - - const submission = submittedReports.data.submissions.find( - (r) => r._id === '62d561606b4bb5e396034444' - ); - - cy.conditionalIntercept( - '**/graphql', - (req) => req.body.operationName == 'FindSubmission', - 'FindSubmission', - { - data: { - submission: submission, - }, - } - ); - - cy.conditionalIntercept( - '**/graphql', - (req) => req.body.operationName == 'AllQuickAdd', - 'AllQuickAdd', - { - data: { - quickadds: [quickAdds], - }, - } - ); - - cy.conditionalIntercept( - '**/graphql', - (req) => req.body.operationName == 'ProbablyRelatedIncidents', - 'ProbablyRelatedIncidents', - { - data: { - incidents: [ - { - incident_id: 195, - title: - 'Predictive Policing Program by Florida Sheriff’s Office Allegedly Violated Residents’ Rights and Targeted Children of Vulnerable Groups', - reports: [ - { - report_number: 1843, - title: 'The man behind the machine', - url: 'https://projects.tampabay.com/projects/2020/investigations/police-pasco-sheriff-targeted/chris-nocco/', - __typename: 'Report', - }, - ], - }, - ], - }, - } - ); - - cy.conditionalIntercept( - '**/graphql', - (req) => req.body.operationName == 'FindIncidentsTitles', - 'FindIncidentsTitles', - { - data: { - incidents: [ - { - __typename: 'Incident', - incident_id: 1, - title: 'Test title', - date: '2016-03-13', - }, - ], - }, - } - ); - - cy.conditionalIntercept( - '**/graphql', - (req) => req.body.operationName == 'ProbablyRelatedReports', - 'ProbablyRelatedReports', - { - data: { - reports: [ - { - report_number: 1628, - title: - 'Pasco’s sheriff uses grades and abuse histories to label schoolchildren potential criminals', - url: 'https://projects.tampabay.com/projects/2020/investigations/police-pasco-sheriff-targeted/school-data/', - }, - ], - }, - } - ); - - cy.visit(url + `?editSubmission=${submission._id}`); - - cy.wait('@AllQuickAdd'); - - cy.conditionalIntercept( - '**/graphql', - (req) => req.body.operationName === 'PromoteSubmission', - 'promotionInvoked', - {} - ); - - cy.get('select[data-cy="promote-select"]').as('dropdown'); - - cy.get('@dropdown').select('Incident'); - - cy.get('[data-cy="promote-button"]').click(); - - cy.contains('[data-cy="toast"]', 'Description is required').should('exist'); - } - ); - - maybeIt( - 'Does not allow promotion of submission to Issue if schema is invalid (missing Title).', - () => { - cy.login(Cypress.env('e2eUsername'), Cypress.env('e2ePassword')); - - if (user.adminData.email == Cypress.env('e2eUsername')) { - expect(user.roles.some((role) => ['admin', 'incident_editor'].includes(role))).to.be.true; - } - - const submission = submittedReports.data.submissions.find( - (r) => r._id === '123461606b4bb5e39601234' - ); - - cy.conditionalIntercept( - '**/graphql', - (req) => req.body.operationName == 'FindSubmission', - 'FindSubmission', - { - data: { - submission: submission, - }, - } - ); - - cy.conditionalIntercept( - '**/graphql', - (req) => req.body.operationName == 'AllQuickAdd', - 'AllQuickAdd', - { - data: { - quickadds: [quickAdds], - }, - } - ); - - cy.conditionalIntercept( - '**/graphql', - (req) => req.body.operationName == 'ProbablyRelatedIncidents', - 'ProbablyRelatedIncidents', - { - data: { - incidents: [ - { - incident_id: 195, - title: - 'Predictive Policing Program by Florida Sheriff’s Office Allegedly Violated Residents’ Rights and Targeted Children of Vulnerable Groups', - reports: [ - { - report_number: 1843, - title: 'The man behind the machine', - url: 'https://projects.tampabay.com/projects/2020/investigations/police-pasco-sheriff-targeted/chris-nocco/', - __typename: 'Report', - }, - ], - }, - ], - }, - } - ); - - cy.conditionalIntercept( - '**/graphql', - (req) => req.body.operationName == 'FindIncidentsTitles', - 'FindIncidentsTitles', - { - data: { - incidents: [ - { - __typename: 'Incident', - incident_id: 12, - title: 'Test title', - date: '2016-03-13', - }, - ], - }, - } - ); - - cy.conditionalIntercept( - '**/graphql', - (req) => req.body.operationName == 'ProbablyRelatedReports', - 'ProbablyRelatedReports', - { - data: { - reports: [ - { - report_number: 1628, - title: - 'Pasco’s sheriff uses grades and abuse histories to label schoolchildren potential criminals', - url: 'https://projects.tampabay.com/projects/2020/investigations/police-pasco-sheriff-targeted/school-data/', - }, - ], - }, - } - ); - - cy.visit(url + `?editSubmission=${submission._id}`); - - cy.wait('@AllQuickAdd'); - - cy.conditionalIntercept( - '**/graphql', - (req) => req.body.operationName === 'PromoteSubmission', - 'promotionInvoked', - {} - ); - - cy.get('select[data-cy="promote-select"]').as('dropdown'); - - cy.get('@dropdown').select('Issue'); - - cy.get('[data-cy="promote-button"]').click(); - - cy.contains('[data-cy="toast"]', 'Title is required').should('exist'); - } - ); - - it.skip('Does not allow promotion of submission to Report if schema is invalid (missing Date).', () => { - cy.login(Cypress.env('e2eUsername'), Cypress.env('e2ePassword')); - - if (user.adminData.email == Cypress.env('e2eUsername')) { - expect(user.roles.some((role) => ['admin', 'incident_editor'].includes(role))).to.be.true; - } - - const submission = submittedReports.data.submissions.find( - (r) => r._id === '333561606b4bb5e39601234' - ); - - cy.conditionalIntercept( - '**/graphql', - (req) => req.body.operationName == 'FindSubmission', - 'FindSubmission', - { - data: { - submission: submission, - }, - } - ); - - cy.conditionalIntercept( - '**/graphql', - (req) => req.body.operationName == 'AllQuickAdd', - 'AllQuickAdd', - { - data: { - quickadds: [quickAdds], - }, - } - ); - - cy.conditionalIntercept( - '**/graphql', - (req) => req.body.operationName == 'ProbablyRelatedIncidents', - 'ProbablyRelatedIncidents', - { - data: { - incidents: [ - { - incident_id: 195, - title: - 'Predictive Policing Program by Florida Sheriff’s Office Allegedly Violated Residents’ Rights and Targeted Children of Vulnerable Groups', - reports: [ - { - report_number: 1843, - title: 'The man behind the machine', - url: 'https://projects.tampabay.com/projects/2020/investigations/police-pasco-sheriff-targeted/chris-nocco/', - __typename: 'Report', - }, - ], - }, - ], - }, - } - ); - - cy.conditionalIntercept( - '**/graphql', - (req) => req.body.operationName == 'FindIncidentsTitles', - 'FindIncidentsTitles', - { - data: { - incidents: [ - { - __typename: 'Incident', - incident_id: 12, - title: 'Test title', - date: '2016-03-13', - }, - ], - }, - } - ); - - cy.conditionalIntercept( - '**/graphql', - (req) => req.body.operationName == 'ProbablyRelatedReports', - 'ProbablyRelatedReports', - { - data: { - reports: [ - { - report_number: 1628, - title: - 'Pasco’s sheriff uses grades and abuse histories to label schoolchildren potential criminals', - url: 'https://projects.tampabay.com/projects/2020/investigations/police-pasco-sheriff-targeted/school-data/', - }, - ], - }, - } - ); - - cy.visit(url + `?editSubmission=${submission._id}`); - - cy.wait('@AllQuickAdd'); - - cy.conditionalIntercept( - '**/graphql', - (req) => req.body.operationName === 'PromoteSubmission', - 'promotionInvoked', - {} - ); - - cy.get('[data-cy="promote-to-report-button"]').contains('Add to incident 12').click(); - - cy.contains('[data-cy="toast"]', '*Date is not valid, must be `YYYY-MM-DD`').should('exist'); - }); - - it.skip('Should display an error message if data is missing', () => { - // With new submission list, we allow to save changes always - cy.login(Cypress.env('e2eUsername'), Cypress.env('e2ePassword')); - - if (user.adminData.email == Cypress.env('e2eUsername')) { - expect(user.roles.some((role) => ['admin', 'incident_editor'].includes(role))).to.be.true; - } - - const submission = submittedReports.data.submissions.find( - (r) => r._id === '62d561606b4bb5e39601234' - ); - - cy.conditionalIntercept( - '**/graphql', - (req) => req.body.operationName == 'FindSubmissions', - 'FindSubmissions', - { - data: { - submissions: [submission], - }, - } - ); - - cy.conditionalIntercept( - '**/graphql', - (req) => req.body.operationName == 'FindSubmission', - 'FindSubmission', - { - data: { - submission: submission, - }, - } - ); - - cy.conditionalIntercept( - '**/graphql', - (req) => req.body.operationName == 'FindSubmission', - 'FindSubmission', - { - data: { - submission, - }, - } - ); - - cy.visit(url + `?editSubmission=${submission._id}`); - - cy.get('[data-cy="submission-form"]') - .contains('Please review submission. Some data is missing.') - .should('exist'); - - cy.get('[data-cy="submission"]').contains('Changes not saved').should('exist'); - - cy.waitForStableDOM(); - - cy.get('input[name="title"]').type( - 'Lorem Ipsum is simply dummy text of the printing and typesetting industry' - ); - - cy.get('input[name=authors]').type('Author{enter}'); - - cy.setEditorText( - 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco' - ); - - cy.get('input[name=date_published]').type('2023-01-01'); - - cy.get('input[name=date_downloaded]').type('2023-01-01'); - - cy.get('[data-cy="submission-form"]') - .contains('Please review submission. Some data is missing.') - .should('not.exist'); - - cy.get('[data-cy="submission"]').contains('Changes not saved').should('not.exist'); - }); - - maybeIt('Should display submission image on edit page', () => { - cy.login(Cypress.env('e2eUsername'), Cypress.env('e2ePassword')); - - if (user.adminData.email == Cypress.env('e2eUsername')) { - expect(user.roles.some((role) => ['admin', 'incident_editor'].includes(role))).to.be.true; - } - - const submission = submittedReports.data.submissions.find( - (s) => s.cloudinary_id && s.cloudinary_id != 'reports/' && s.cloudinary_id != '' - ); - - cy.conditionalIntercept( - '**/graphql', - (req) => req.body.operationName == 'FindSubmissions', - 'FindSubmissions', - { - data: { - submissions: [submission], - }, - } - ); - - cy.conditionalIntercept( - '**/graphql', - (req) => req.body.operationName == 'FindSubmission', - 'FindSubmission', - { - data: { - submission, - }, - } - ); - - cy.conditionalIntercept( - '**/graphql', - (req) => req.body.operationName == 'AllQuickAdd', - 'AllQuickAdd', - { - data: { - quickadds: [quickAdds], - }, - } - ); - - cy.conditionalIntercept( - '**/graphql', - (req) => req.body.operationName == 'FindEntities', - 'FindEntities', - { - data: { - entities: [ - { __typename: 'Entity', entity_id: 'Adults', name: 'adults' }, - { __typename: 'Entity', entity_id: 'Google', name: 'google' }, - ], - }, - } - ); - - cy.conditionalIntercept( - '**/graphql', - (req) => req.body.operationName == 'ProbablyRelatedIncidents', - 'ProbablyRelatedIncidents', - { - data: { - incidents: [ - { - incident_id: 195, - title: - 'Predictive Policing Program by Florida Sheriff’s Office Allegedly Violated Residents’ Rights and Targeted Children of Vulnerable Groups', - reports: [ - { - report_number: 1843, - title: 'The man behind the machine', - url: 'https://projects.tampabay.com/projects/2020/investigations/police-pasco-sheriff-targeted/chris-nocco/', - __typename: 'Report', - }, - ], - }, - ], - }, - } - ); - - cy.conditionalIntercept( - '**/graphql', - (req) => req.body.operationName == 'FindIncidentsTitles', - 'FindIncidentsTitles', - { - data: { - incidents: [ - { - __typename: 'Incident', - incident_id: 1, - title: 'Test title', - date: '2016-03-13', - }, - ], - }, - } - ); - - cy.conditionalIntercept( - '**/graphql', - (req) => req.body.operationName == 'ProbablyRelatedReports', - 'ProbablyRelatedReports', - { - data: { - reports: [ - { - report_number: 1628, - title: - 'Pasco’s sheriff uses grades and abuse histories to label schoolchildren potential criminals', - url: 'https://projects.tampabay.com/projects/2020/investigations/police-pasco-sheriff-targeted/school-data/', - }, - ], - }, - } - ); - - cy.visit(url + `?editSubmission=${submission._id}`); - - cy.wait('@AllQuickAdd'); - - cy.waitForStableDOM(); - - cy.get('[data-cy="image-preview-figure"] img').should( - 'have.attr', - 'src', - 'https://res.cloudinary.com/pai/image/upload/f_auto/q_auto/v1/reports/s3.amazonaws.com/ledejs/resized/s2020-pasco-ilp/600/nocco5.jpg' - ); - }); - - it.skip('Should display fallback image on edit modal if submission doesnt have an image', () => { - cy.login(Cypress.env('e2eUsername'), Cypress.env('e2ePassword')); - - if (user.adminData.email == Cypress.env('e2eUsername')) { - expect(user.roles.some((role) => ['admin', 'incident_editor'].includes(role))).to.be.true; - } - - const submission = submittedReports.data.submissions.find((s) => s.cloudinary_id === null); - - cy.conditionalIntercept( - '**/graphql', - (req) => req.body.operationName == 'FindSubmissions', - 'FindSubmissions', - { - data: { - submissions: [submission], - }, - } - ); - - cy.conditionalIntercept( - '**/graphql', - (req) => req.body.operationName == 'FindSubmission', - 'FindSubmission', - { - data: { - submission, - }, - } - ); - - cy.conditionalIntercept( - '**/graphql', - (req) => req.body.operationName == 'AllQuickAdd', - 'AllQuickAdd', - { - data: { - quickadds: [quickAdds], - }, - } - ); - - cy.conditionalIntercept( - '**/graphql', - (req) => req.body.operationName == 'ProbablyRelatedIncidents', - 'ProbablyRelatedIncidents', - { - data: { - incidents: [ - { - incident_id: 195, - title: - 'Predictive Policing Program by Florida Sheriff’s Office Allegedly Violated Residents’ Rights and Targeted Children of Vulnerable Groups', - reports: [ - { - report_number: 1843, - title: 'The man behind the machine', - url: 'https://projects.tampabay.com/projects/2020/investigations/police-pasco-sheriff-targeted/chris-nocco/', - __typename: 'Report', - }, - ], - }, - ], - }, - } - ); - - cy.conditionalIntercept( - '**/graphql', - (req) => req.body.operationName == 'FindIncidentsTitles', - 'FindIncidentsTitles', - { - data: { - incidents: [ - { - __typename: 'Incident', - incident_id: 1, - title: 'Test title', - date: '2016-03-13', - }, - ], - }, - } - ); - - cy.conditionalIntercept( - '**/graphql', - (req) => req.body.operationName == 'ProbablyRelatedReports', - 'ProbablyRelatedReports', - { - data: { - reports: [ - { - report_number: 1628, - title: - 'Pasco’s sheriff uses grades and abuse histories to label schoolchildren potential criminals', - url: 'https://projects.tampabay.com/projects/2020/investigations/police-pasco-sheriff-targeted/school-data/', - }, - ], - }, - } - ); - - cy.visit(url + `?editSubmission=${submission._id}`); - - cy.wait('@AllQuickAdd'); - - cy.get('[data-cy="image-preview-figure"] canvas').should('exist'); - }); - - maybeIt('Should display an error message if Date Published is not in the past', () => { - cy.login(Cypress.env('e2eUsername'), Cypress.env('e2ePassword')); - - if (user.adminData.email == Cypress.env('e2eUsername')) { - expect(user.roles.some((role) => ['admin', 'incident_editor'].includes(role))).to.be.true; - } - - const submission = submittedReports.data.submissions.find( - (r) => r._id === '62d561606b4bb5e39601234' - ); - - cy.conditionalIntercept( - '**/graphql', - (req) => req.body.operationName == 'FindSubmissions', - 'FindSubmissions', - { - data: { - submissions: [submission], - }, - } - ); - - cy.conditionalIntercept( - '**/graphql', - (req) => req.body.operationName == 'FindSubmission', - 'FindSubmission', - { - data: { - submission, - }, - } - ); - - cy.conditionalIntercept( - '**/graphql', - (req) => req.body.operationName == 'ProbablyRelatedIncidents', - 'ProbablyRelatedIncidents', - { - data: { - incidents: [ - { - incident_id: 195, - title: - 'Predictive Policing Program by Florida Sheriff’s Office Allegedly Violated Residents’ Rights and Targeted Children of Vulnerable Groups', - reports: [ - { - report_number: 1843, - title: 'The man behind the machine', - url: 'https://projects.tampabay.com/projects/2020/investigations/police-pasco-sheriff-targeted/chris-nocco/', - __typename: 'Report', - }, - ], - }, - ], - }, - } - ); - - cy.conditionalIntercept( - '**/graphql', - (req) => req.body.operationName == 'FindIncidentsTitles', - 'FindIncidentsTitles', - { - data: { - incidents: [ - { - __typename: 'Incident', - incident_id: 1, - title: 'Test title', - date: '2016-03-13', - }, - ], - }, - } - ); - - cy.conditionalIntercept( - '**/graphql', - (req) => req.body.operationName == 'ProbablyRelatedReports', - 'ProbablyRelatedReports', - { - data: { - reports: [ - { - report_number: 1628, - title: - 'Pasco’s sheriff uses grades and abuse histories to label schoolchildren potential criminals', - url: 'https://projects.tampabay.com/projects/2020/investigations/police-pasco-sheriff-targeted/school-data/', - }, - ], - }, - } - ); - - cy.visit(url + `?editSubmission=${submission._id}`); - - cy.waitForStableDOM(); - - cy.get('input[name=date_published]').type('3000-01-01'); - - cy.get('[data-cy="submission-form"]').contains('*Date must be in the past').should('exist'); - }); - - maybeIt('Should display an error message if Date Downloaded is not in the past', () => { - cy.login(Cypress.env('e2eUsername'), Cypress.env('e2ePassword')); - - const submission = submittedReports.data.submissions.find( - (r) => r._id === '62d561606b4bb5e39601234' - ); - - cy.conditionalIntercept( - '**/graphql', - (req) => req.body.operationName == 'FindSubmissions', - 'FindSubmissions', - { - data: { - submissions: [submission], - }, - } - ); - - cy.conditionalIntercept( - '**/graphql', - (req) => req.body.operationName == 'FindSubmission', - 'FindSubmission', - { - data: { - submission, - }, - } - ); - - cy.conditionalIntercept( - '**/graphql', - (req) => req.body.operationName == 'ProbablyRelatedIncidents', - 'ProbablyRelatedIncidents', - { - data: { - incidents: [ - { - incident_id: 195, - title: - 'Predictive Policing Program by Florida Sheriff’s Office Allegedly Violated Residents’ Rights and Targeted Children of Vulnerable Groups', - reports: [ - { - report_number: 1843, - title: 'The man behind the machine', - url: 'https://projects.tampabay.com/projects/2020/investigations/police-pasco-sheriff-targeted/chris-nocco/', - __typename: 'Report', - }, - ], - }, - ], - }, - } - ); - - cy.conditionalIntercept( - '**/graphql', - (req) => req.body.operationName == 'FindIncidentsTitles', - 'FindIncidentsTitles', - { - data: { - incidents: [ - { - __typename: 'Incident', - incident_id: 1, - title: 'Test title', - date: '2016-03-13', - }, - ], - }, - } - ); - - cy.conditionalIntercept( - '**/graphql', - (req) => req.body.operationName == 'ProbablyRelatedReports', - 'ProbablyRelatedReports', - { - data: { - reports: [ - { - report_number: 1628, - title: - 'Pasco’s sheriff uses grades and abuse histories to label schoolchildren potential criminals', - url: 'https://projects.tampabay.com/projects/2020/investigations/police-pasco-sheriff-targeted/school-data/', - }, - ], - }, - } - ); - - cy.visit(url + `?editSubmission=${submission._id}`); - - cy.waitForStableDOM(); - - cy.get('input[name=date_downloaded]').type('3000-01-01'); - - cy.get('[data-cy="submission-form"]').contains('*Date must be in the past').should('exist'); - }); - - maybeIt( - 'Edits a submission - links to existing incident - Incident Data should be hidden', - () => { - cy.login(Cypress.env('e2eUsername'), Cypress.env('e2ePassword')); - - cy.conditionalIntercept( - '**/graphql', - (req) => req.body.operationName == 'FindSubmissions', - 'FindSubmissions', - submittedReports - ); - - cy.conditionalIntercept( - '**/graphql', - (req) => req.body.operationName == 'FindSubmission', - 'FindSubmission', - { - data: { - submission: submittedReports.data.submissions[0], - }, - } - ); - - cy.conditionalIntercept( - '**/graphql', - (req) => req.body.operationName == 'FindEntities', - 'FindEntities', - { - data: { - entities: [ - { __typename: 'Entity', entity_id: 'Adults', name: 'adults' }, - { __typename: 'Entity', entity_id: 'Google', name: 'google' }, - ], - }, - } - ); - - cy.conditionalIntercept( - '**/graphql', - (req) => req.body.operationName == 'FindIncidentsTitles', - 'FindIncidentsTitles', - { - data: { - incidents: [ - { - __typename: 'Incident', - incident_id: 1, - title: 'Test title', - date: '2016-03-13', - }, - ], - }, - } - ); - - cy.conditionalIntercept( - '**/graphql', - (req) => req.body.operationName === 'UpdateSubmission', - 'UpdateSubmission', - { - data: { - updateOneSubmission: { - ...submittedReports.data.submissions[0], - incident_ids: [1], - }, - }, - } - ); - - cy.conditionalIntercept( - '**/graphql', - (req) => - req.body.operationName == 'UpsertEntity' && - req.body.variables.entity.entity_id == 'adults', - 'UpsertAdults', - { - data: { - upsertOneEntity: { __typename: 'Entity', entity_id: 'adults', name: 'Adults' }, - }, - } - ); - - cy.conditionalIntercept( - '**/graphql', - (req) => - req.body.operationName == 'UpsertEntity' && - req.body.variables.entity.entity_id == 'google', - 'UpsertGoogle', - { - data: { - upsertOneEntity: { __typename: 'Entity', entity_id: 'google', name: 'Google' }, - }, - } - ); - - cy.visit(url + `?editSubmission=${submittedReports.data.submissions[0]._id}`); - - cy.waitForStableDOM(); - - cy.get(`input[name="incident_ids"]`).type('1'); - - cy.waitForStableDOM(); - - cy.get(`[role="option"]`).first().click(); - - cy.waitForStableDOM(); - - cy.get('[data-cy="incident-data-section"]').should('not.exist'); - - cy.wait('@UpdateSubmission').then((xhr) => { - expect(xhr.request.body.variables.query).to.deep.nested.include({ - _id: submittedReports.data.submissions[0]._id, - }); - - expect(xhr.request.body.variables.set).to.deep.nested.include({ - incident_ids: [1], - }); - }); - - cy.wait('@UpsertGoogle') - .its('request.body.variables.entity.entity_id') - .should('eq', 'google'); - - cy.wait('@UpsertAdults') - .its('request.body.variables.entity.entity_id') - .should('eq', 'adults'); - } - ); - - maybeIt('Claims a submission', () => { - cy.login(Cypress.env('e2eUsername'), Cypress.env('e2ePassword')); - - if (user.adminData.email == Cypress.env('e2eUsername')) { - expect(user.roles.some((role) => ['admin', 'incident_editor'].includes(role))).to.be.true; - } - - const submission = submittedReports.data.submissions.find( - (r) => r._id === '5f9c3ebfd4896d392493f03c' - ); - - cy.conditionalIntercept( - '**/graphql', - (req) => req.body.operationName == 'FindSubmissions', - 'FindSubmissions', - { - data: { - submissions: [submission], - }, - } - ); - - cy.conditionalIntercept( - '**/graphql', - (req) => req.body.operationName == 'AllQuickAdd', - 'AllQuickAdd', - { - data: { - quickadds: [quickAdds], - }, - } - ); - cy.conditionalIntercept( - '**/graphql', - (req) => req.body.operationName === 'UpdateSubmission', - 'UpdateSubmission', - { - data: { - updateOneSubmission: { - ...submission, - incident_editors: [ - { - userId: '619737436d52a1795887d3f9', - first_name: 'Your', - last_name: 'Name', - }, - ], - }, - }, - } - ); - - cy.visit(url); - - cy.wait('@FindSubmissions'); - - cy.wait('@AllQuickAdd'); - - cy.waitForStableDOM(); - - cy.get('[data-cy="claim-submission"]').click(); - - cy.waitForStableDOM(); - - cy.wait('@UpdateSubmission').then((xhr) => { - expect(xhr.request.body.variables.query).to.deep.nested.include({ - _id: submission._id, - }); - expect(xhr.request.body.variables.set).to.deep.eq({ - incident_editors: { link: [user.userId] }, - }); - }); - }); - - maybeIt('Unclaims a submission', () => { - cy.login(Cypress.env('e2eUsername'), Cypress.env('e2ePassword')); - - if (user.adminData.email == Cypress.env('e2eUsername')) { - expect(user.roles.some((role) => ['admin', 'incident_editor'].includes(role))).to.be.true; - } - - const submission = submittedReports.data.submissions.find( - (r) => r._id === '6123bf345e740c1a81850e89' - ); - - cy.conditionalIntercept( - '**/graphql', - (req) => req.body.operationName == 'FindSubmissions', - 'FindSubmissions', - { - data: { - submissions: [ - { - ...submission, - incident_editors: [ - { - userId: user.userId, - first_name: 'Test', - last_name: 'User', - }, - ], - }, - ], - }, - } - ); - - cy.conditionalIntercept( - '**/graphql', - (req) => req.body.operationName == 'AllQuickAdd', - 'AllQuickAdd', - { - data: { - quickadds: [quickAdds], - }, - } - ); - cy.conditionalIntercept( - '**/graphql', - (req) => req.body.operationName === 'UpdateSubmission', - 'UpdateSubmission', - { - data: { - updateOneSubmission: { - ...submission, - incident_editors: [], - }, - }, - } - ); - - cy.visit(url); - - cy.wait('@FindSubmissions'); - - cy.wait('@AllQuickAdd'); - - cy.waitForStableDOM(); - - cy.get('[data-cy="claim-submission"]').click(); - - cy.waitForStableDOM(); - - cy.wait('@UpdateSubmission').then((xhr) => { - expect(xhr.request.body.variables.query).to.deep.nested.include({ - _id: submission._id, - }); - expect(xhr.request.body.variables.set).to.deep.eq({ - incident_editors: { link: [] }, - }); - }); - }); - - maybeIt('Should maintain current page while claiming', () => { - cy.login(Cypress.env('e2eUsername'), Cypress.env('e2ePassword')); - - if (user.adminData.email == Cypress.env('e2eUsername')) { - expect(user.roles.some((role) => ['admin', 'incident_editor'].includes(role))).to.be.true; - } - - const submission = submittedReports.data.submissions.find( - (r) => r._id === '433346160eeeeqdd5e382bei234' - ); - - cy.conditionalIntercept( - '**/graphql', - (req) => req.body.operationName == 'FindSubmissions', - 'FindSubmissions', - submittedReports - ); - - cy.conditionalIntercept( - '**/graphql', - (req) => req.body.operationName == 'AllQuickAdd', - 'AllQuickAdd', - { - data: { - quickadds: [quickAdds], - }, - } - ); - cy.conditionalIntercept( - '**/graphql', - (req) => req.body.operationName === 'UpdateSubmission', - 'UpdateSubmission', - { - data: { - updateOneSubmission: { - ...submission, - incident_editors: [ - { - userId: '62cd9520a69a2cdf17fb47db', - first_name: 'Your', - last_name: 'Name', - }, - ], - }, - }, - } - ); - - cy.visit(url); - - cy.wait('@FindSubmissions'); - - cy.wait('@AllQuickAdd'); - - cy.waitForStableDOM(); - - cy.get('.pagination').contains('Next').click(); - - cy.waitForStableDOM(); - - cy.get('[data-cy="claim-submission"]').click(); - - cy.waitForStableDOM(); - - cy.wait('@UpdateSubmission').then((xhr) => { - expect(xhr.request.body.variables.query).to.deep.nested.include({ - _id: submission._id, - }); - expect(xhr.request.body.variables.set).to.deep.eq({ - incident_editors: { link: [user.userId] }, - }); - }); - cy.get(".pagination [aria-current='page'] button").contains('2').should('exist'); - }); - - maybeIt('Should display "No reports found" if no quick adds are found', () => { - cy.conditionalIntercept( - '**/graphql', - (req) => req.body.operationName == 'AllQuickAdd', - 'AllQuickAdd', - { - data: { - quickadds: [], - }, - } - ); - - cy.visit(url); - - cy.wait('@AllQuickAdd'); - - cy.get('[data-cy="no-results"]').should('contain', 'No reports found'); - }); -}); diff --git a/site/gatsby-site/cypress/e2e/unit/functions/linkReportsToIncidents.cy.js b/site/gatsby-site/cypress/e2e/unit/functions/linkReportsToIncidents.cy.js deleted file mode 100644 index 01ad9f40ff..0000000000 --- a/site/gatsby-site/cypress/e2e/unit/functions/linkReportsToIncidents.cy.js +++ /dev/null @@ -1,277 +0,0 @@ -const linkReportsToIncidents = require('../../../../../realm/functions/linkReportsToIncidents'); - -//should be on its own /cypress/unit folder or something - -const incident_1 = { - AllegedDeployerOfAISystem: [], - AllegedDeveloperOfAISystem: [], - AllegedHarmedOrNearlyHarmedParties: [], - date: '2018-11-16', - description: 'Description 1', - incident_id: 1, - nlp_similar_incidents: [], - reports: [1, 2], - title: 'Title 1', - embedding: { - vector: [1, 2, 3], - from_reports: [1, 2], - }, -}; - -const incident_2 = { - AllegedDeployerOfAISystem: [], - AllegedDeveloperOfAISystem: [], - AllegedHarmedOrNearlyHarmedParties: [], - date: '2018-11-16', - description: 'Description 2', - incident_id: 2, - nlp_similar_incidents: [], - reports: [3], - title: 'Title 2', - embedding: { - vector: [4, 5], - from_reports: [3], - }, -}; - -const report_1 = { - report_number: 1, - title: 'Report 1', - embedding: { - vector: [1, 2, 3, 4], - }, - url: 'https://url.com', - source_domain: 'domain.com', -}; - -const report_2 = { - report_number: 2, - title: 'Report 2', - embedding: { - vector: [6, 7, 8], - }, - url: 'https://url.com', - source_domain: 'domain.com', -}; - -const report_3 = { - report_number: 3, - title: 'Report 3', - embedding: { - vector: [10], - }, - url: 'https://url.com', - source_domain: 'domain.com', -}; - -describe('Functions', () => { - it('Should link a new report to two incidents', () => { - const incidentsCollection = { - find: cy - .stub() - .onFirstCall() - .returns({ - toArray: cy.stub().resolves([]), - }) - .onSecondCall() - .returns({ - toArray: cy.stub().resolves([incident_1, incident_2]), - }) - .onThirdCall() - .returns({ - toArray: cy.stub().resolves([]), - }), - updateMany: cy.stub().resolves({}), - updateOne: cy.stub().resolves({}), - }; - - const reportsCollection = { - find: cy - .stub() - .onFirstCall() - .returns({ - toArray: cy.stub().resolves([report_1, report_2]), - }) - .onSecondCall() - .returns({ - toArray: cy.stub().resolves([report_3]), - }), - updateOne: cy.stub().resolves(), - updateMany: cy.stub().resolves({}), - }; - - global.context = { - // @ts-ignore - services: { - get: cy.stub().returns({ - db: cy.stub().returns({ - collection: (() => { - const stub = cy.stub(); - - stub.withArgs('incidents').returns(incidentsCollection); - stub.withArgs('reports').returns(reportsCollection); - - return stub; - })(), - }), - }), - }, - functions: { - execute: cy.stub(), - }, - }; - - global.BSON = { Int32: (x) => x }; - - cy.wrap(linkReportsToIncidents({ incident_ids: [1, 2], report_numbers: [3] })).then(() => { - expect(incidentsCollection.find.firstCall.args[0]).to.deep.equal({ - reports: { $in: [3] }, - }); - - expect(incidentsCollection.updateMany.firstCall.args[0]).to.deep.equal({ - reports: { $in: [3] }, - }); - expect(incidentsCollection.updateMany.firstCall.args[1]).to.deep.equal({ - $pull: { reports: { $in: [3] } }, - }); - - expect(incidentsCollection.updateMany.secondCall.args[0]).to.deep.equal({ - incident_id: { $in: [1, 2] }, - }); - expect(incidentsCollection.updateMany.secondCall.args[1]).to.deep.equal({ - $addToSet: { reports: { $each: [3] } }, - }); - - expect(incidentsCollection.find.secondCall.args[0]).to.deep.equal({ - reports: { $in: [3] }, - }); - - expect(reportsCollection.find.firstCall.args[0]).to.deep.equal({ - report_number: { $in: [1, 2] }, - }); - - expect(incidentsCollection.updateOne.firstCall.args[0]).to.deep.equal({ - incident_id: 1, - }); - expect(incidentsCollection.updateOne.firstCall.args[1]).to.deep.equal({ - $set: { - embedding: { - vector: [3.5, 4.5, 5.5], - from_reports: [1, 2], - }, - }, - }); - - expect(incidentsCollection.updateOne.secondCall.args[0]).to.deep.equal({ - incident_id: 2, - }); - expect(incidentsCollection.updateOne.secondCall.args[1]).to.deep.equal({ - $set: { - embedding: { - vector: [10], - from_reports: [3], - }, - }, - }); - - expect(reportsCollection.updateMany.firstCall.args[0]).to.deep.equal({ - report_number: { $in: [3] }, - title: { $nin: [null, ''] }, - url: { $nin: [null, ''] }, - source_domain: { $nin: [null, ''] }, - }); - expect(reportsCollection.updateMany.firstCall.args[1]).to.deep.equal({ - $set: { is_incident_report: true }, - }); - }); - }); - - it('Should unlink a report from an incident and set it to issue', () => { - const incidentsCollection = { - find: cy - .stub() - .onFirstCall() - .returns({ - toArray: cy.stub().resolves([incident_2]), - }) - .onSecondCall() - .returns({ - toArray: cy.stub().resolves([]), - }) - .onThirdCall() - .returns({ - toArray: cy.stub().resolves([]), - }), - updateMany: cy.stub().resolves({}), - updateOne: cy.stub().resolves({}), - }; - - const reportsCollection = { - find: cy - .stub() - .onFirstCall() - .returns({ - toArray: cy.stub().resolves([]), - }), - updateOne: cy.stub().resolves(), - updateMany: cy.stub().resolves({}), - }; - - global.context = { - // @ts-ignore - services: { - get: cy.stub().returns({ - db: cy.stub().returns({ - collection: (() => { - const stub = cy.stub(); - - stub.withArgs('incidents').returns(incidentsCollection); - stub.withArgs('reports').returns(reportsCollection); - - return stub; - })(), - }), - }), - }, - functions: { - execute: cy.stub(), - }, - }; - - chai.config.truncateThreshold = 0; - - global.BSON = { Int32: (x) => x }; - - cy.wrap(linkReportsToIncidents({ incident_ids: [], report_numbers: [3] })).then(() => { - expect(incidentsCollection.find.firstCall.args[0]).to.deep.equal({ - reports: { $in: [3] }, - }); - - expect(incidentsCollection.updateMany.firstCall.args[0]).to.deep.equal({ - reports: { $in: [3] }, - }); - expect(incidentsCollection.updateMany.firstCall.args[1]).to.deep.equal({ - $pull: { reports: { $in: [3] } }, - }); - - expect(reportsCollection.find.firstCall.args[0]).to.deep.equal({ - report_number: { $in: [] }, - }); - - expect(incidentsCollection.updateOne.firstCall.args[0]).to.deep.equal({ incident_id: 2 }); - expect(incidentsCollection.updateOne.firstCall.args[1]).to.deep.equal({ - $unset: { embedding: '' }, - }); - - expect(reportsCollection.updateMany.firstCall.args[0]).to.deep.equal({ - report_number: { $in: [3] }, - title: { $nin: [null, ''] }, - url: { $nin: [null, ''] }, - source_domain: { $nin: [null, ''] }, - }); - expect(reportsCollection.updateMany.firstCall.args[1]).to.deep.equal({ - $set: { is_incident_report: false }, - }); - }); - }); -}); diff --git a/site/gatsby-site/cypress/e2e/unit/functions/promoteSubmissionToReport.cy.js b/site/gatsby-site/cypress/e2e/unit/functions/promoteSubmissionToReport.cy.js deleted file mode 100644 index 4a6be30bd9..0000000000 --- a/site/gatsby-site/cypress/e2e/unit/functions/promoteSubmissionToReport.cy.js +++ /dev/null @@ -1,887 +0,0 @@ -const { SUBSCRIPTION_TYPE } = require('../../../../src/utils/subscriptions'); - -const promoteSubmissionToReport = require('../../../../../realm/functions/promoteSubmissionToReport'); - -//should be on its own /cypress/unit folder or something - -const submission = { - _id: '5f9c3ebfd4896d392493f03c', - authors: ['Nedi Bedi and Kathleen McGrory'], - cloudinary_id: 'something', - date_downloaded: '2020-10-30', - date_modified: '2021-07-27', - date_published: '2017-05-03', - date_submitted: '2020-10-30', - epoch_date_modified: 1686182943, - description: - 'By NEIL BEDI and KATHLEEN McGRORY\nTimes staff writers\nNov. 19, 2020\nThe Pasco Sheriff’s Office keeps a secret list of kids it thinks could “fall into a life of crime” based on factors like wheth', - image_url: 'https://s3.amazonaws.com/ledejs/resized/s2020-pasco-ilp/600/nocco5.jpg', - incident_date: '2015-09-01', - incident_editors: ['1', '2'], - incident_id: 0, - language: 'en', - source_domain: 'projects.tampabay.com', - submitters: ['Kate Perkins'], - text: '## Submission 1 text\n\n_Markdown content!_', - plain_text: 'Submission 1 text\n\nMarkdown content!', - title: 'Submisssion 1 title', - url: 'https://projects.tampabay.com/projects/2020/investigations/police-pasco-sheriff-targeted/school-data/', - editor_notes: '', - developers: ['AI Dev'], - deployers: ['Youtube'], - harmed_parties: ['Adults'], - nlp_similar_incidents: [], - editor_dissimilar_incidents: [], - editor_similar_incidents: [], - tags: [], - user: 'user1', - quiet: false, -}; - -const submission_with_embedding = { - _id: '5f9c3ebfd4896d392493f03c', - authors: ['Nedi Bedi and Kathleen McGrory'], - cloudinary_id: 'something', - date_downloaded: '2020-10-30', - date_modified: '2021-07-27', - date_published: '2017-05-03', - date_submitted: '2020-10-30', - epoch_date_modified: 1686182943, - description: - 'By NEIL BEDI and KATHLEEN McGRORY\nTimes staff writers\nNov. 19, 2020\nThe Pasco Sheriff’s Office keeps a secret list of kids it thinks could “fall into a life of crime” based on factors like wheth', - image_url: 'https://s3.amazonaws.com/ledejs/resized/s2020-pasco-ilp/600/nocco5.jpg', - incident_date: '2015-09-01', - incident_editors: ['1', '2'], - incident_id: 0, - language: 'en', - source_domain: 'projects.tampabay.com', - submitters: ['Kate Perkins'], - text: '## Submission 1 text\n\n_Markdown content!_', - plain_text: 'Submission 1 text\n\nMarkdown content!', - title: 'Submisssion 1 title', - url: 'https://projects.tampabay.com/projects/2020/investigations/police-pasco-sheriff-targeted/school-data/', - editor_notes: '', - developers: ['AI Dev'], - deployers: ['Youtube'], - harmed_parties: ['Adults'], - nlp_similar_incidents: [], - editor_dissimilar_incidents: [], - editor_similar_incidents: [], - tags: [], - user: 'user1', - quiet: false, - embedding: { - vector: [1, 2, 3], - from_reports: [1], - }, -}; - -const incident = { - AllegedDeployerOfAISystem: [], - AllegedDeveloperOfAISystem: [], - AllegedHarmedOrNearlyHarmedParties: [], - __typename: 'Incident', - date: '2018-11-16', - description: - 'Twenty-four Amazon workers in New Jersey were hospitalized after a robot punctured a can of bear repellent spray in a warehouse.', - editors: ['1', '2'], - incident_id: 1, - nlp_similar_incidents: [], - reports: [1, 2], - title: '24 Amazon workers sent to hospital after robot accidentally unleashes bear spray', -}; - -describe('Functions', () => { - it('Should promote a submission to a new report & new incident', () => { - const submissionsCollection = { - findOne: cy.stub().resolves(submission), - deleteOne: cy.stub(), - }; - - const incidentsCollection = { - find: cy - .stub() - .onFirstCall() - .returns({ - toArray: cy.stub().resolves([]), - }) - .onSecondCall() - .returns({ - sort: cy.stub().returns({ - limit: cy.stub().returns({ - next: cy.stub().resolves(incident), - }), - }), - }), - insertOne: cy.stub().resolves(), - }; - - const incidentsHistoryCollection = { - insertOne: cy.stub().resolves(), - }; - - const reportsCollection = { - find: cy.stub().returns({ - sort: cy.stub().returns({ - limit: cy.stub().returns({ - next: cy.stub().resolves({ report_number: 1 }), - }), - }), - }), - insertOne: cy.stub().resolves(), - }; - - const notificationsCollection = { - insertOne: cy.stub().resolves(), - }; - - const subscriptionsCollection = { - insertOne: cy.stub().resolves(), - }; - - const reportsHistoryCollection = { - insertOne: cy.stub().resolves(), - }; - - global.context = { - // @ts-ignore - services: { - get: cy.stub().returns({ - db: (() => { - const stub = cy.stub(); - - stub.withArgs('aiidprod').returns({ - collection: (() => { - const stub = cy.stub(); - - stub.withArgs('submissions').returns(submissionsCollection); - stub.withArgs('incidents').returns(incidentsCollection); - stub.withArgs('reports').returns(reportsCollection); - - return stub; - })(), - }); - - stub.withArgs('customData').returns({ - collection: (() => { - const stub = cy.stub(); - - stub.withArgs('notifications').returns(notificationsCollection); - stub.withArgs('subscriptions').returns(subscriptionsCollection); - - return stub; - })(), - }); - - stub.withArgs('history').returns({ - collection: (() => { - const stub = cy.stub(); - - stub.withArgs('incidents').returns(incidentsHistoryCollection); - stub.withArgs('reports').returns(reportsHistoryCollection); - - return stub; - })(), - }); - - return stub; - })(), - }), - }, - functions: { - execute: cy.stub(), - }, - }; - - global.BSON = { Int32: (x) => x }; - - cy.wrap( - promoteSubmissionToReport({ is_incident_report: true, incident_ids: [], submission_id: 1 }) - ).then(() => { - const expectedIncident = { - 'Alleged deployer of AI system': ['Youtube'], - 'Alleged developer of AI system': ['AI Dev'], - 'Alleged harmed or nearly harmed parties': ['Adults'], - date: '2015-09-01', - epoch_date_modified: 1686182943, - description: - 'By NEIL BEDI and KATHLEEN McGRORY\nTimes staff writers\nNov. 19, 2020\nThe Pasco Sheriff’s Office keeps a secret list of kids it thinks could “fall into a life of crime” based on factors like wheth', - editor_dissimilar_incidents: [], - editor_similar_incidents: [], - editors: ['1', '2'], - incident_id: 2, - nlp_similar_incidents: [], - reports: [], - title: 'Submisssion 1 title', - }; - - expect(incidentsCollection.insertOne.firstCall.args[0]).to.deep.equal(expectedIncident); - - expect(incidentsHistoryCollection.insertOne.firstCall.args[0]).to.deep.equal({ - ...expectedIncident, - reports: [2], - modifiedBy: submission.user, - }); - - const expectedReport = { - report_number: 2, - is_incident_report: true, - title: 'Submisssion 1 title', - date_downloaded: new Date('2020-10-30'), - date_modified: new Date('2021-07-27'), - date_published: new Date('2017-05-03'), - date_submitted: new Date('2020-10-30'), - epoch_date_downloaded: 1604016000, - epoch_date_modified: 1686182943, - epoch_date_published: 1493769600, - epoch_date_submitted: 1604016000, - image_url: 'https://s3.amazonaws.com/ledejs/resized/s2020-pasco-ilp/600/nocco5.jpg', - cloudinary_id: 'something', - authors: ['Nedi Bedi and Kathleen McGrory'], - submitters: ['Kate Perkins'], - text: '## Submission 1 text\n\n_Markdown content!_', - plain_text: 'Submission 1 text\n\nMarkdown content!', - url: 'https://projects.tampabay.com/projects/2020/investigations/police-pasco-sheriff-targeted/school-data/', - source_domain: 'projects.tampabay.com', - language: 'en', - tags: [], - user: 'user1', - quiet: false, - }; - - expect(reportsCollection.insertOne.firstCall.args[0]).to.deep.eq(expectedReport); - - expect(submissionsCollection.deleteOne).to.be.calledOnceWith({ _id: 1 }); - - expect(global.context.functions.execute).to.be.calledOnceWith('linkReportsToIncidents', { - incident_ids: [2], - report_numbers: [2], - }); - - expect(reportsHistoryCollection.insertOne.firstCall.args[0]).to.deep.eq({ - ...expectedReport, - modifiedBy: submission.user, - }); - - expect(notificationsCollection.insertOne.firstCall.args[0]).to.deep.equal({ - type: SUBSCRIPTION_TYPE.submissionPromoted, - incident_id: 2, - userId: 'user1', - processed: false, - }); - }); - }); - - it('Should promote a submission to a new report & existing incident', () => { - const submissionsCollection = { - findOne: cy.stub().resolves(submission_with_embedding), - deleteOne: cy.stub(), - }; - - const incidentsCollection = { - find: cy - .stub() - .onFirstCall() - .returns({ - toArray: cy.stub().resolves([incident]), - }) - .onSecondCall() - .returns({ - sort: cy.stub().returns({ - limit: cy.stub().returns({ - next: cy.stub().resolves(incident), - }), - }), - }), - insertOne: cy.stub().resolves(), - updateOne: cy.stub().resolves(), - }; - - const incidentsHistoryCollection = { - insertOne: cy.stub().resolves(), - }; - - const reportsCollection = { - find: cy.stub().returns({ - sort: cy.stub().returns({ - limit: cy.stub().returns({ - next: cy.stub().resolves({ report_number: 1 }), - }), - }), - }), - findOne: cy.stub().resolves({ report_number: 1 }), - insertOne: cy.stub().resolves(), - }; - - const reportsHistoryCollection = { - insertOne: cy.stub().resolves(), - }; - - const notificationsCollection = { - insertOne: cy.stub().resolves(), - }; - - const subscriptionsCollection = { - insertOne: cy.stub().resolves(), - }; - - global.context = { - // @ts-ignore - services: { - get: cy.stub().returns({ - db: (() => { - const stub = cy.stub(); - - stub.withArgs('aiidprod').returns({ - collection: (() => { - const stub = cy.stub(); - - stub.withArgs('submissions').returns(submissionsCollection); - stub.withArgs('incidents').returns(incidentsCollection); - stub.withArgs('reports').returns(reportsCollection); - - return stub; - })(), - }); - - stub.withArgs('customData').returns({ - collection: (() => { - const stub = cy.stub(); - - stub.withArgs('notifications').returns(notificationsCollection); - stub.withArgs('subscriptions').returns(subscriptionsCollection); - - return stub; - })(), - }); - - stub.withArgs('history').returns({ - collection: (() => { - const stub = cy.stub(); - - stub.withArgs('incidents').returns(incidentsHistoryCollection); - stub.withArgs('reports').returns(reportsHistoryCollection); - - return stub; - })(), - }); - - return stub; - })(), - }), - }, - functions: { - execute: cy.stub(), - }, - }; - - global.BSON = { Int32: (x) => x }; - - cy.wrap( - promoteSubmissionToReport({ is_incident_report: true, incident_ids: [1], submission_id: 1 }) - ).then(() => { - expect(incidentsCollection.insertOne.callCount).to.eq(0); - - expect(incidentsHistoryCollection.insertOne.callCount).to.eq(1); - - const expectedReport = { - report_number: 2, - is_incident_report: true, - title: 'Submisssion 1 title', - date_downloaded: new Date('2020-10-30'), - date_modified: new Date('2021-07-27'), - date_published: new Date('2017-05-03'), - date_submitted: new Date('2020-10-30'), - epoch_date_downloaded: 1604016000, - epoch_date_modified: 1686182943, - epoch_date_published: 1493769600, - epoch_date_submitted: 1604016000, - image_url: 'https://s3.amazonaws.com/ledejs/resized/s2020-pasco-ilp/600/nocco5.jpg', - cloudinary_id: 'something', - authors: ['Nedi Bedi and Kathleen McGrory'], - submitters: ['Kate Perkins'], - text: '## Submission 1 text\n\n_Markdown content!_', - plain_text: 'Submission 1 text\n\nMarkdown content!', - url: 'https://projects.tampabay.com/projects/2020/investigations/police-pasco-sheriff-targeted/school-data/', - source_domain: 'projects.tampabay.com', - language: 'en', - tags: [], - user: 'user1', - quiet: false, - embedding: { - vector: [1, 2, 3], - from_reports: [1], - }, - }; - - expect(reportsCollection.insertOne.firstCall.args[0]).to.deep.eq(expectedReport); - - expect(submissionsCollection.deleteOne).to.be.calledOnceWith({ _id: 1 }); - - expect(global.context.functions.execute).to.be.calledOnceWith('linkReportsToIncidents', { - incident_ids: [1], - report_numbers: [2], - }); - - expect(reportsHistoryCollection.insertOne.firstCall.args[0]).to.deep.eq({ - ...expectedReport, - modifiedBy: submission_with_embedding.user, - }); - - expect(subscriptionsCollection.insertOne.called).to.be.false; - }); - }); - - it('Should promote a submission to a new issue', () => { - const submissionsCollection = { - findOne: cy.stub().resolves(submission), - deleteOne: cy.stub(), - }; - - const incidentsCollection = { - find: cy - .stub() - .onFirstCall() - .returns({ - toArray: cy.stub().resolves([]), - }) - .onSecondCall() - .returns({ - sort: cy.stub().returns({ - limit: cy.stub().returns({ - next: cy.stub().resolves(incident), - }), - }), - }), - insertOne: cy.stub().resolves(), - }; - - const incidentsHistoryCollection = { - insertOne: cy.stub().resolves(), - }; - - const reportsCollection = { - find: cy.stub().returns({ - sort: cy.stub().returns({ - limit: cy.stub().returns({ - next: cy.stub().resolves({ report_number: 1 }), - }), - }), - }), - insertOne: cy.stub().resolves(), - }; - - const reportsHistoryCollection = { - insertOne: cy.stub().resolves(), - }; - - const notificationsCollection = { - insertOne: cy.stub().resolves(), - }; - - const subscriptionsCollection = { - insertOne: cy.stub().resolves(), - }; - - global.context = { - // @ts-ignore - services: { - get: cy.stub().returns({ - db: (() => { - const stub = cy.stub(); - - stub.withArgs('aiidprod').returns({ - collection: (() => { - const stub = cy.stub(); - - stub.withArgs('submissions').returns(submissionsCollection); - stub.withArgs('incidents').returns(incidentsCollection); - stub.withArgs('reports').returns(reportsCollection); - - return stub; - })(), - }); - - stub.withArgs('customData').returns({ - collection: (() => { - const stub = cy.stub(); - - stub.withArgs('notifications').returns(notificationsCollection); - stub.withArgs('subscriptions').returns(subscriptionsCollection); - - return stub; - })(), - }); - - stub.withArgs('history').returns({ - collection: (() => { - const stub = cy.stub(); - - stub.withArgs('incidents').returns(incidentsHistoryCollection); - stub.withArgs('reports').returns(reportsHistoryCollection); - - return stub; - })(), - }); - - return stub; - })(), - }), - }, - functions: { - execute: cy.stub(), - }, - }; - - global.BSON = { Int32: (x) => x }; - - cy.wrap( - promoteSubmissionToReport({ is_incident_report: false, incident_ids: [], submission_id: 1 }) - ).then(() => { - expect(incidentsCollection.insertOne.callCount).to.equal(0); - - expect(incidentsHistoryCollection.insertOne.callCount).to.equal(0); - - const expectedReport = { - report_number: 2, - is_incident_report: false, - title: 'Submisssion 1 title', - date_downloaded: new Date('2020-10-30'), - date_modified: new Date('2021-07-27'), - date_published: new Date('2017-05-03'), - date_submitted: new Date('2020-10-30'), - epoch_date_downloaded: 1604016000, - epoch_date_modified: 1686182943, - epoch_date_published: 1493769600, - epoch_date_submitted: 1604016000, - image_url: 'https://s3.amazonaws.com/ledejs/resized/s2020-pasco-ilp/600/nocco5.jpg', - cloudinary_id: 'something', - authors: ['Nedi Bedi and Kathleen McGrory'], - submitters: ['Kate Perkins'], - text: '## Submission 1 text\n\n_Markdown content!_', - plain_text: 'Submission 1 text\n\nMarkdown content!', - url: 'https://projects.tampabay.com/projects/2020/investigations/police-pasco-sheriff-targeted/school-data/', - source_domain: 'projects.tampabay.com', - language: 'en', - tags: [], - user: 'user1', - quiet: false, - }; - - expect(reportsCollection.insertOne.firstCall.args[0]).to.deep.eq(expectedReport); - - expect(submissionsCollection.deleteOne).to.be.calledOnceWith({ _id: 1 }); - - expect(global.context.functions.execute).to.be.calledOnceWith('linkReportsToIncidents', { - incident_ids: [], - report_numbers: [2], - }); - - expect(reportsHistoryCollection.insertOne.firstCall.args[0]).to.deep.eq({ - ...expectedReport, - modifiedBy: submission.user, - }); - - expect(subscriptionsCollection.insertOne.called).to.be.false; - }); - }); - - it("Should default to Anonymous's user id", () => { - const submissionsCollection = { - findOne: cy.stub().resolves({ ...submission, incident_editors: [] }), - deleteOne: cy.stub(), - }; - - const incidentsCollection = { - find: cy - .stub() - .onFirstCall() - .returns({ - toArray: cy.stub().resolves([]), - }) - .onSecondCall() - .returns({ - sort: cy.stub().returns({ - limit: cy.stub().returns({ - next: cy.stub().resolves(incident), - }), - }), - }), - insertOne: cy.stub().resolves(), - }; - - const reportsCollection = { - find: cy.stub().returns({ - sort: cy.stub().returns({ - limit: cy.stub().returns({ - next: cy.stub().resolves({ report_number: 1 }), - }), - }), - }), - insertOne: cy.stub().resolves(), - }; - - const notificationsCollection = { - insertOne: cy.stub().resolves(), - }; - - const subscriptionsCollection = { - insertOne: cy.stub().resolves(), - }; - - const incidentsHistoryCollection = { - insertOne: cy.stub().resolves(), - }; - - const reportsHistoryCollection = { - insertOne: cy.stub().resolves(), - }; - - global.context = { - // @ts-ignore - services: { - get: cy.stub().returns({ - db: (() => { - const stub = cy.stub(); - - stub.withArgs('aiidprod').returns({ - collection: (() => { - const stub = cy.stub(); - - stub.withArgs('submissions').returns(submissionsCollection); - stub.withArgs('incidents').returns(incidentsCollection); - stub.withArgs('reports').returns(reportsCollection); - - return stub; - })(), - }); - - stub.withArgs('customData').returns({ - collection: (() => { - const stub = cy.stub(); - - stub.withArgs('notifications').returns(notificationsCollection); - stub.withArgs('subscriptions').returns(subscriptionsCollection); - - return stub; - })(), - }); - - stub.withArgs('history').returns({ - collection: (() => { - const stub = cy.stub(); - - stub.withArgs('incidents').returns(incidentsHistoryCollection); - stub.withArgs('reports').returns(reportsHistoryCollection); - - return stub; - })(), - }); - - return stub; - })(), - }), - }, - functions: { - execute: cy.stub(), - }, - }; - - global.BSON = { Int32: (x) => x }; - - cy.wrap( - promoteSubmissionToReport({ is_incident_report: true, incident_ids: [], submission_id: 1 }) - ).then(() => { - expect(incidentsCollection.insertOne.firstCall.args[0]).to.deep.equal({ - 'Alleged deployer of AI system': ['Youtube'], - 'Alleged developer of AI system': ['AI Dev'], - 'Alleged harmed or nearly harmed parties': ['Adults'], - date: '2015-09-01', - epoch_date_modified: 1686182943, - description: - 'By NEIL BEDI and KATHLEEN McGRORY\nTimes staff writers\nNov. 19, 2020\nThe Pasco Sheriff’s Office keeps a secret list of kids it thinks could “fall into a life of crime” based on factors like wheth', - editor_dissimilar_incidents: [], - editor_similar_incidents: [], - editors: ['65031f49ec066d7c64380f5c'], // Default user. For more information refer to the wiki page: https://github.com/responsible-ai-collaborative/aiid/wiki/Special-non%E2%80%90secret-values - incident_id: 2, - nlp_similar_incidents: [], - reports: [], - title: 'Submisssion 1 title', - }); - }); - }); - - it('Should promote a submission submitted by an anonymous user', () => { - const anonymousSubmission = submission; - - delete anonymousSubmission.user; - - const submissionsCollection = { - findOne: cy.stub().resolves(submission), - deleteOne: cy.stub(), - }; - - const incidentsCollection = { - find: cy - .stub() - .onFirstCall() - .returns({ - toArray: cy.stub().resolves([]), - }) - .onSecondCall() - .returns({ - sort: cy.stub().returns({ - limit: cy.stub().returns({ - next: cy.stub().resolves(incident), - }), - }), - }), - insertOne: cy.stub().resolves(), - }; - - const incidentsHistoryCollection = { - insertOne: cy.stub().resolves(), - }; - - const reportsCollection = { - find: cy.stub().returns({ - sort: cy.stub().returns({ - limit: cy.stub().returns({ - next: cy.stub().resolves({ report_number: 1 }), - }), - }), - }), - insertOne: cy.stub().resolves(), - }; - - const notificationsCollection = { - insertOne: cy.stub().resolves(), - }; - - const subscriptionsCollection = { - insertOne: cy.stub().resolves(), - }; - - const reportsHistoryCollection = { - insertOne: cy.stub().resolves(), - }; - - global.context = { - // @ts-ignore - services: { - get: cy.stub().returns({ - db: (() => { - const stub = cy.stub(); - - stub.withArgs('aiidprod').returns({ - collection: (() => { - const stub = cy.stub(); - - stub.withArgs('submissions').returns(submissionsCollection); - stub.withArgs('incidents').returns(incidentsCollection); - stub.withArgs('reports').returns(reportsCollection); - - return stub; - })(), - }); - - stub.withArgs('customData').returns({ - collection: (() => { - const stub = cy.stub(); - - stub.withArgs('notifications').returns(notificationsCollection); - stub.withArgs('subscriptions').returns(subscriptionsCollection); - - return stub; - })(), - }); - - stub.withArgs('history').returns({ - collection: (() => { - const stub = cy.stub(); - - stub.withArgs('incidents').returns(incidentsHistoryCollection); - stub.withArgs('reports').returns(reportsHistoryCollection); - - return stub; - })(), - }); - - return stub; - })(), - }), - }, - functions: { - execute: cy.stub(), - }, - }; - - global.BSON = { Int32: (x) => x }; - - cy.wrap( - promoteSubmissionToReport({ is_incident_report: true, incident_ids: [], submission_id: 1 }) - ).then(() => { - const expectedIncident = { - 'Alleged deployer of AI system': ['Youtube'], - 'Alleged developer of AI system': ['AI Dev'], - 'Alleged harmed or nearly harmed parties': ['Adults'], - date: '2015-09-01', - epoch_date_modified: 1686182943, - description: - 'By NEIL BEDI and KATHLEEN McGRORY\nTimes staff writers\nNov. 19, 2020\nThe Pasco Sheriff’s Office keeps a secret list of kids it thinks could “fall into a life of crime” based on factors like wheth', - editor_dissimilar_incidents: [], - editor_similar_incidents: [], - editors: ['1', '2'], - incident_id: 2, - nlp_similar_incidents: [], - reports: [], - title: 'Submisssion 1 title', - }; - - expect(incidentsCollection.insertOne.firstCall.args[0]).to.deep.equal(expectedIncident); - - expect(incidentsHistoryCollection.insertOne.firstCall.args[0]).to.deep.equal({ - ...expectedIncident, - reports: [2], - }); - - const expectedReport = { - report_number: 2, - is_incident_report: true, - title: 'Submisssion 1 title', - date_downloaded: new Date('2020-10-30'), - date_modified: new Date('2021-07-27'), - date_published: new Date('2017-05-03'), - date_submitted: new Date('2020-10-30'), - epoch_date_downloaded: 1604016000, - epoch_date_modified: 1686182943, - epoch_date_published: 1493769600, - epoch_date_submitted: 1604016000, - image_url: 'https://s3.amazonaws.com/ledejs/resized/s2020-pasco-ilp/600/nocco5.jpg', - cloudinary_id: 'something', - authors: ['Nedi Bedi and Kathleen McGrory'], - submitters: ['Kate Perkins'], - text: '## Submission 1 text\n\n_Markdown content!_', - plain_text: 'Submission 1 text\n\nMarkdown content!', - url: 'https://projects.tampabay.com/projects/2020/investigations/police-pasco-sheriff-targeted/school-data/', - source_domain: 'projects.tampabay.com', - language: 'en', - tags: [], - quiet: false, - }; - - expect(reportsCollection.insertOne.firstCall.args[0]).to.deep.eq(expectedReport); - - expect(submissionsCollection.deleteOne).to.be.calledOnceWith({ _id: 1 }); - - expect(global.context.functions.execute).to.be.calledOnceWith('linkReportsToIncidents', { - incident_ids: [2], - report_numbers: [2], - }); - - expect(reportsHistoryCollection.insertOne.firstCall.args[0]).to.deep.eq({ - ...expectedReport, - }); - }); - }); -}); diff --git a/site/gatsby-site/cypress/support/utils.js b/site/gatsby-site/cypress/support/utils.js index e4871b3a78..5a46ab01e8 100644 --- a/site/gatsby-site/cypress/support/utils.js +++ b/site/gatsby-site/cypress/support/utils.js @@ -17,11 +17,9 @@ export const getApolloClient = () => { const password = Cypress.env('e2ePassword'); - const realmAppId = Cypress.env('realmAppId'); - const client = new ApolloClient({ link: new HttpLink({ - uri: `https://services.cloud.mongodb.com/api/client/v2.0/app/${realmAppId}/graphql`, + uri: `/api/graphql`, fetch: async (uri, options) => { options.headers.email = email; diff --git a/site/gatsby-site/package.json b/site/gatsby-site/package.json index 6fd4c6568f..a2808140ca 100644 --- a/site/gatsby-site/package.json +++ b/site/gatsby-site/package.json @@ -130,7 +130,7 @@ "cypress:open": "cypress open", "cypress:run": "cypress run", "test:e2e": "start-server-and-test start http://localhost:8000 cypress:open", - "test:e2e:ci": "DEBUG=pw:api,pw:webserver npx playwright test $TEST_FOLDER --shard=$SHARD_INDEX/$SHARD_TOTAL", + "test:e2e:ci": "DEBUG=pw:webserver npx playwright test $TEST_FOLDER --shard=$SHARD_INDEX/$SHARD_TOTAL", "test:api": "jest --setupFiles dotenv/config --runInBand --forceExit", "test:api:ci": "jest --runInBand --forceExit", "codegen": "graphql-codegen --config codegen.ts", diff --git a/site/gatsby-site/playwright.config.ts b/site/gatsby-site/playwright.config.ts index 17a3725913..711629eaaa 100644 --- a/site/gatsby-site/playwright.config.ts +++ b/site/gatsby-site/playwright.config.ts @@ -39,7 +39,7 @@ export default defineConfig({ projects: [ { name: 'chromium', - use: { ...devices['Desktop Chrome'] }, + use: { ...devices['Desktop Chrome'], launchOptions: { args: ['--auto-open-devtools-for-tabs'] } }, }, // { diff --git a/site/gatsby-site/playwright/e2e-full/apps/checklistForm.spec.ts b/site/gatsby-site/playwright/e2e-full/apps/checklistForm.spec.ts new file mode 100644 index 0000000000..57903ccbd3 --- /dev/null +++ b/site/gatsby-site/playwright/e2e-full/apps/checklistForm.spec.ts @@ -0,0 +1,346 @@ +import { expect } from '@playwright/test'; +import riskSortingRisks from '../../fixtures/checklists/riskSortingChecklist.json'; +import riskSortingChecklist from '../../fixtures/checklists/riskSortingChecklist.json'; +import { conditionalIntercept, test, waitForRequest } from '../../utils'; +import config from '../../config'; +import { init } from '../../memory-mongo'; + +test.describe('Checklists App Form', () => { + const url = '/apps/checklists?id=testChecklist'; + + const defaultChecklist = { + __typename: 'Checklist', + about: '', + id: 'testChecklist', + name: 'Test Checklist', + owner_id: 'a-fake-user-id-that-does-not-exist', + risks: [], + tags_goals: [], + tags_methods: [], + tags_other: [], + }; + + test.skip('Should have read-only access for non-logged-in users', async ({ page }) => { + await conditionalIntercept( + page, + '**/graphql', + (req) => req.postDataJSON()?.operationName === 'findChecklist', + { data: { checklist: defaultChecklist } }, + 'findChecklist' + ); + + await page.goto(url); + + await expect(page.locator('[data-cy="checklist-form"] textarea:not([disabled])')).not.toBeVisible(); + await expect(page.locator('[data-cy="checklist-form"] input:not([disabled]):not([readonly])')).not.toBeVisible(); + }); + + test('Should have read-only access for logged-in non-owners', async ({ page, login }) => { + + await login(config.E2E_ADMIN_USERNAME, config.E2E_ADMIN_PASSWORD, { customData: { first_name: 'Test', last_name: 'User', roles: ['admin'] } }); + + await conditionalIntercept( + page, + '**/graphql', + (req) => req.postDataJSON()?.operationName === 'findChecklist', + { data: { checklist: defaultChecklist } }, + 'findChecklist', + ); + + await page.goto(url); + + await waitForRequest('findChecklist'); + + await expect(page.locator('[data-cy="checklist-form"] textarea:not([disabled])')).not.toBeVisible(); + await expect(page.locator('[data-cy="checklist-form"] input:not([disabled]):not([readonly])')).not.toBeVisible(); + }); + + test('Should allow editing for owner', async ({ page, login }) => { + + const userId = await login(config.E2E_ADMIN_USERNAME, config.E2E_ADMIN_PASSWORD, { customData: { first_name: 'Test', last_name: 'User', roles: ['admin'] } }); + + + await conditionalIntercept( + page, + '**/graphql', + (req) => req.postDataJSON()?.operationName === 'findChecklist', + { data: { checklist: { ...defaultChecklist, owner_id: userId } } }, + 'findChecklist', + ); + + await conditionalIntercept( + page, + '**/graphql', + (req) => req.postDataJSON()?.operationName === 'upsertChecklist', + { + data: { + checklist: { + ...defaultChecklist, + owner_id: userId, + about: "It's a system that does something probably.", + }, + }, + }, + 'upsertChecklist', + ); + + await page.goto(url); + + await waitForRequest('findChecklist'); + + await page.locator('[data-cy="about"]').type("It's a system that does something probably."); + + await waitForRequest('upsertChecklist'); + }); + + test('Should trigger GraphQL upsert query on adding tag', async ({ page, login }) => { + + const userId = await login(config.E2E_ADMIN_USERNAME, config.E2E_ADMIN_PASSWORD, { customData: { first_name: 'Test', last_name: 'User', roles: ['admin'] } }); + + await conditionalIntercept( + page, + '**/graphql', + (req) => req.postDataJSON()?.operationName === 'findChecklist', + { data: { checklist: { ...defaultChecklist, owner_id: userId } } }, + 'findChecklist', + ); + + await conditionalIntercept( + page, + '**/graphql', + (req) => req.postDataJSON()?.operationName === 'upsertChecklist', + { data: { checklist: {} } }, + 'upsertChecklist', + ); + + await page.goto(url); + + await waitForRequest('findChecklist'); + + await page.locator('#tags_goals_input').type('Code Generation'); + await page.locator('#tags_goals').click(); + + await waitForRequest('upsertChecklist'); + }); + + test('Should trigger GraphQL update on removing tag', async ({ page, login }) => { + + const userId = await login(config.E2E_ADMIN_USERNAME, config.E2E_ADMIN_PASSWORD, { customData: { first_name: 'Test', last_name: 'User', roles: ['admin'] } }); + + await conditionalIntercept( + page, + '**/graphql', + (req) => req.postDataJSON()?.operationName === 'findChecklist', + { + data: { + checklist: { + ...defaultChecklist, + owner_id: userId, + tags_goals: ['GMF:Known AI Goal:Code Generation'], + }, + }, + }, + 'findChecklist', + ); + + await conditionalIntercept( + page, + '**/graphql', + (req) => req.postDataJSON()?.operationName === 'upsertChecklist', + { data: { checklist: {} } }, + 'upsertChecklist', + ); + + await page.goto(url); + + await waitForRequest('findChecklist'); + + await page.locator('[option="GMF:Known AI Goal:Code Generation"] .close').click(); + + await waitForRequest('upsertChecklist'); + }); + + test('Should trigger UI update on adding and removing tag', async ({ page, login }) => { + + const userId = await login(config.E2E_ADMIN_USERNAME, config.E2E_ADMIN_PASSWORD, { customData: { first_name: 'Test', last_name: 'User', roles: ['admin'] } }); + + await conditionalIntercept( + page, + '**/graphql', + (req) => req.postDataJSON()?.operationName === 'findChecklist', + { data: { checklist: { ...defaultChecklist, owner_id: userId } } }, + 'findChecklist', + ); + + await conditionalIntercept( + page, + '**/graphql', + (req) => req.postDataJSON()?.operationName === 'upsertChecklist', + { data: { checklist: {} } }, + 'upsertChecklist', + ); + + await conditionalIntercept( + page, + '**/graphql', + (req) => req.postDataJSON()?.operationName === 'FindRisks', + { data: { risks: riskSortingRisks.data.checklist.risks } }, + 'risks' + ); + + await page.goto(url); + + await waitForRequest('findChecklist'); + + await page.locator('#tags_methods_input').type('Transformer'); + await page.locator('#tags_methods').click(); + + await waitForRequest('upsertChecklist'); + + await waitForRequest('risks'); + + await expect(page.locator('details').first()).toBeVisible(); + + await page.locator('.rbt-close').click(); + + await expect(page.locator('details')).not.toBeVisible(); + }); + + test('Should change sort order of risk items', async ({ page, login }) => { + + const userId = await login(config.E2E_ADMIN_USERNAME, config.E2E_ADMIN_PASSWORD, { customData: { first_name: 'Test', last_name: 'User', roles: ['admin'] } }); + + await page.setViewportSize({ width: 1920, height: 1080 }); + + await conditionalIntercept( + page, + '**/graphql', + (req) => req.postDataJSON()?.operationName === 'findChecklist', + { data: { checklist: { ...riskSortingChecklist.data.checklist, owner_id: userId } } }, + 'findChecklist' + ); + + await conditionalIntercept( + page, + '**/graphql', + (req) => req.postDataJSON()?.query.includes('GMF'), + { data: { risks: riskSortingRisks.data.checklist.risks } }, + 'risks' + ); + + await page.goto(url); + + await waitForRequest('findChecklist'); + await waitForRequest('risks'); + + await page.locator('text=Mitigated').first().click(); + await expect(page.locator('details:nth-child(2)')).toContainText('Distributional Bias'); + + await page.locator('text=Minor').first().click(); + await expect(page.locator('details:nth-child(2)')).toContainText('Dataset Imbalance'); + }); + + test.skip('Should remove a manually-created risk', async ({ page, login }) => { + + await init(); + + const userId = await login(config.E2E_ADMIN_USERNAME, config.E2E_ADMIN_PASSWORD, { customData: { first_name: 'Test', last_name: 'User', roles: ['admin'] } }); + + await conditionalIntercept( + page, + '**/graphql', + (req) => req.postDataJSON()?.operationName === 'findChecklist', + { + data: { + checklist: { + ...defaultChecklist, + owner_id: userId, + risks: [ + { + __typename: 'ChecklistRisk', + generated: false, + id: '5bb31fa6-2d32-4a01-b0a0-fa3fb4ec4b7d', + likelihood: '', + precedents: [], + risk_notes: '', + risk_status: 'Mitigated', + severity: '', + tags: ['GMF:Known AI Goal:Content Search'], + title: 'Manual Test Risk', + touched: false, + }, + ], + }, + }, + }, + 'findChecklist' + ); + + await conditionalIntercept( + page, + '**/graphql', + (req) => req.postDataJSON()?.operationName === 'upsertChecklist', + { data: { checklist: { ...defaultChecklist, owner_id: userId } } }, + 'upsertChecklist' + ); + + await conditionalIntercept( + page, + '**/graphql', + (req) => req.postDataJSON()?.operationName === 'FindRisks', + { data: { risks: [] } }, + 'risks' + ); + + await page.goto(url); + + await waitForRequest('findChecklist'); + + page.on('dialog', (dialog) => dialog.accept()); + + await page.getByTestId('delete-risk').click(); + + await waitForRequest('upsertChecklist'); + + await waitForRequest('FindRisks'); + + await expect(page.locator('text=Manual Test Risk')).not.toBeVisible(); + }); + + // TODO: test is crashing not sure if it is a bug or missing seed data + test.skip('Should persist open state on editing query', async ({ page, login }) => { + + const userId = await login(config.E2E_ADMIN_USERNAME, config.E2E_ADMIN_PASSWORD, { customData: { first_name: 'Test', last_name: 'User', roles: ['admin'] } }); + + await conditionalIntercept( + page, + '**/graphql', + (req) => req.postDataJSON()?.operationName === 'findChecklist', + { + data: { checklist: { ...riskSortingChecklist.data.checklist, owner_id: userId } }, + }, + 'findChecklist' + ); + + await conditionalIntercept( + page, + '**/graphql', + (req) => req.postDataJSON()?.query.includes('GMF'), + { data: { risks: riskSortingRisks.data.checklist.risks } }, + 'risks' + ); + + await page.goto(url); + + await waitForRequest('findChecklist'); + await waitForRequest('risks'); + + await page.locator('text=Distributional Artifacts').first().click(); + + await page.locator('[data-cy="risk_query-container"] .rbt-input-main').first().fill('CSETv1:Physical Objects:no'); + + await page.locator('[aria-label="CSETv1:Physical Objects:no"]').click(); + + await expect(page.locator('[data-cy="risk_query-container"]').locator('..').first()).toHaveAttribute('open', ''); + }); +}); \ No newline at end of file diff --git a/site/gatsby-site/playwright/e2e-full/apps/checklistIndex.spec.ts b/site/gatsby-site/playwright/e2e-full/apps/checklistIndex.spec.ts new file mode 100644 index 0000000000..8569f8dc85 --- /dev/null +++ b/site/gatsby-site/playwright/e2e-full/apps/checklistIndex.spec.ts @@ -0,0 +1,251 @@ +import { expect, request } from '@playwright/test'; +import { conditionalIntercept, test, waitForRequest } from '../../utils'; +import config from '../../config'; + +test.describe('Checklists App Index', () => { + const url = '/apps/checklists'; + const newChecklistButtonSelector = '#new-checklist-button'; + + test('Should sort checklists', async ({ page, login }) => { + + const userId = await login(config.E2E_ADMIN_USERNAME, config.E2E_ADMIN_PASSWORD, { customData: { first_name: 'Test', last_name: 'User', roles: ['admin'] } }); + + await conditionalIntercept( + page, + '**/graphql', + (req: any) => req.postDataJSON()?.operationName === 'findChecklists', + { + data: { + checklists: [ + { + about: '', + id: 'fakeChecklist1', + name: 'My Checklist', + owner_id: userId, + risks: [], + tags_goals: ['GMF:Known AI Goal:Translation'], + tags_methods: [], + tags_other: [], + date_created: '2024-01-01T00:26:02.959+00:00', + date_updated: '2024-01-05T00:26:02.959+00:00', + }, + { + about: '', + id: 'fakeChecklist2', + name: 'Another checklist', + owner_id: userId, + risks: [], + tags_goals: [], + tags_methods: [], + tags_other: [], + date_created: '2024-01-03T00:26:02.959+00:00', + date_updated: '2024-01-03T00:26:02.959+00:00', + }, + ], + }, + }, + 'findChecklists', + ); + + + await page.goto(url); + + await waitForRequest('findChecklists'); + + await page.selectOption('#sort-by', 'newest-first'); + await expect(page.locator('[data-cy="checklist-card"]').first()).toContainText('Another checklist'); + + await page.selectOption('#sort-by', 'last-updated'); + await expect(page.locator('[data-cy="checklist-card"]').first()).toContainText('My Checklist'); + + await page.selectOption('#sort-by', 'alphabetical'); + await expect(page.locator('[data-cy="checklist-card"]').first()).toContainText('Another checklist'); + + await page.selectOption('#sort-by', 'oldest-first'); + await expect(page.locator('[data-cy="checklist-card"]').first()).toContainText('My Checklist'); + }); + + test('Should not display New Checklist button as non-logged-in user', async ({ page }) => { + await page.goto(url); + await expect(page.locator(newChecklistButtonSelector)).not.toBeVisible(); + }); + + test('Should display New Checklist button as logged-in user', async ({ page, login }) => { + + await login(config.E2E_ADMIN_USERNAME, config.E2E_ADMIN_PASSWORD, { customData: { first_name: 'Test', last_name: 'User', roles: ['admin'] } }); + + await page.goto(url); + await expect(page.locator(newChecklistButtonSelector)).toBeVisible(); + }); + + test.skip('Should show delete buttons only for owned checklists', async ({ page, login }) => { + + const userId = await login(config.E2E_ADMIN_USERNAME, config.E2E_ADMIN_PASSWORD, { customData: { first_name: 'Test', last_name: 'User', roles: ['admin'] } }); + + await conditionalIntercept( + page, + '**/graphql', + (req: any) => req.body.operationName == 'findChecklists', + { + data: { + checklists: [ + { + about: '', + id: 'fakeChecklist1', + name: 'My Checklist', + owner_id: user.userId, + risks: [], + tags_goals: [], + tags_methods: [], + tags_other: [], + }, + { + about: '', + id: 'fakeChecklist2', + name: "Somebody Else's Checklist", + owner_id: 'aFakeUserId', + risks: [], + tags_goals: [], + tags_methods: [], + tags_other: [], + }, + ], + }, + }, + 'findChecklists', + ); + + await page.goto(url); + + await waitForRequest('findChecklists'); + + await expect(page.locator('[data-cy="checklist-card"]:first-child button')).toContainText('Delete'); + await expect(page.locator('[data-cy="checklist-card"]:last-child button')).not.toContainText('Delete'); + }); + + test('Should show toast on error fetching checklists', async ({ page }) => { + + await conditionalIntercept( + page, + '**/graphql', + (req: any) => req.postDataJSON().operationName === 'findChecklists', + { errors: [{ message: 'Test error', locations: [{ line: 1, column: 1 }] }] }, + 'findChecklists', + ); + + await page.goto(url); + + await waitForRequest('findChecklists'); + + await expect(page.locator('[data-cy="toast"]')).toContainText('Could not fetch checklists'); + }); + + test('Should show toast on error fetching risks', async ({ page, login }) => { + + const userId = await login(config.E2E_ADMIN_USERNAME, config.E2E_ADMIN_PASSWORD, { customData: { first_name: 'Test', last_name: 'User', roles: ['admin'] } }); + + await conditionalIntercept( + page, + '**/graphql', + (req: any) => req.postDataJSON().operationName === 'findChecklists', + { + data: { + checklists: [ + { + about: '', + id: 'fakeChecklist1', + name: 'My Checklist', + owner_id: userId, + risks: [], + tags_goals: ['GMF:Known AI Goal:Translation'], + tags_methods: [], + tags_other: [], + }, + { + about: '', + id: 'fakeChecklist2', + name: "Somebody Else's Checklist", + owner_id: 'aFakeUserId', + risks: [], + tags_goals: [], + tags_methods: [], + tags_other: [], + }, + ], + }, + }, + 'findChecklists', + ); + + await conditionalIntercept( + page, + '**/graphql', + (req: any) => req.postDataJSON()?.query.includes('GMF'), + { errors: [{ message: 'Test error', locations: [{ line: 1, column: 1 }] }] }, + 'risks', + ); + + await page.goto(url); + + await waitForRequest('findChecklists'); + await waitForRequest('risks'); + + await expect(page.locator('[data-cy="toast"]')).toContainText('Failure searching for risks'); + }); + + test('Should show toast on error creating checklist', async ({ page, login }) => { + + const userId = await login(config.E2E_ADMIN_USERNAME, config.E2E_ADMIN_PASSWORD, { customData: { first_name: 'Test', last_name: 'User', roles: ['admin'] } }); + + await conditionalIntercept( + page, + '**/graphql', + (req: any) => req.postDataJSON().operationName === 'insertChecklist', + { errors: [{ message: 'Test error', locations: [{ line: 1, column: 1 }] }] }, + 'insertChecklist', + ); + + await conditionalIntercept( + page, + '**/graphql', + (req: any) => req.postDataJSON().operationName === 'findChecklists', + { + data: { + checklists: [ + { + about: '', + id: 'fakeChecklist1', + name: 'My Checklist', + owner_id: userId, + risks: [], + tags_goals: [], + tags_methods: [], + tags_other: [], + }, + { + about: '', + id: 'fakeChecklist2', + name: "Somebody Else's Checklist", + owner_id: 'aFakeUserId', + risks: [], + tags_goals: [], + tags_methods: [], + tags_other: [], + }, + ], + }, + }, + 'findChecklists', + ); + + await page.goto(url); + + await waitForRequest('findChecklists'); + + await page.click(newChecklistButtonSelector); + + await waitForRequest('insertChecklist'); + + await expect(page.locator('[data-cy="toast"]')).toContainText('Could not create checklist.'); + }); +}); \ No newline at end of file diff --git a/site/gatsby-site/playwright/e2e-full/apps/classifications.spec.ts b/site/gatsby-site/playwright/e2e-full/apps/classifications.spec.ts index 9f06614d4f..f32ac2e070 100644 --- a/site/gatsby-site/playwright/e2e-full/apps/classifications.spec.ts +++ b/site/gatsby-site/playwright/e2e-full/apps/classifications.spec.ts @@ -19,7 +19,7 @@ test.describe('Classifications App', () => { test('Successfully edit a CSET classification', async ({ page, login }) => { - test.slow(); + await login(process.env.E2E_ADMIN_USERNAME, process.env.E2E_ADMIN_PASSWORD, { customData: { roles: ['admin'] } }); diff --git a/site/gatsby-site/playwright/e2e-full/apps/submitted.spec.ts b/site/gatsby-site/playwright/e2e-full/apps/submitted.spec.ts new file mode 100644 index 0000000000..2f3312b4c4 --- /dev/null +++ b/site/gatsby-site/playwright/e2e-full/apps/submitted.spec.ts @@ -0,0 +1,579 @@ +import { expect } from '@playwright/test'; +import { gql } from 'graphql-tag'; +import { isArray } from 'lodash'; +import { init, seedCollection } from '../../memory-mongo'; +import { fillAutoComplete, query, setEditorText, test } from '../../utils'; +import config from '../../config'; +import { DBSubmission } from '../../seeds/aiidprod/submissions'; +import { ObjectId } from 'mongodb'; + +test.describe('Submitted reports', () => { + const url = '/apps/submitted'; + + const getSubmissions = async () => { + const { data: { submissions } } = await query({ + query: gql`{ + submissions { + _id + title + submitters + incident_date + incident_editors { + userId + } + status + text + } + } + `, + }); + + return submissions; + } + + test('Loads submissions', async ({ page }) => { + + await init(); + + const submissions = await getSubmissions(); + + await page.goto(url); + + await expect(page.locator('[data-cy="submissions"] [data-cy="row"]')).toHaveCount(submissions.length); + + for (let index = 0; index < submissions.length; index++) { + const report = submissions[index]; + const row = page.locator('[data-cy="submissions"] [data-cy="row"]').nth(index); + + await expect(row.locator('[data-cy="cell"] [data-cy="review-submission"]')).not.toBeVisible(); + + const keys = ['title', 'submitters', 'incident_date', 'incident_editors', 'status']; + + for (let cellIndex = 0; cellIndex < keys.length; cellIndex++) { + if (report[keys[cellIndex]]) { + let value = report[keys[cellIndex]]; + + if (isArray(value)) { + + if (keys[cellIndex] === 'incident_editors') { + value = value.map((s) => s.userId); + } + + for (let i = 0; i < value.length; i++) { + await expect(row.locator('[data-cy="cell"]').nth(cellIndex)).toContainText(value[i]); + } + } + else { + + await expect(row.locator('[data-cy="cell"]').nth(cellIndex)).toContainText(value); + } + + } + } + } + }); + + test('Promotes a submission to a new report and links it to a new incident', async ({ page, login }) => { + await init(); + + await login(config.E2E_ADMIN_USERNAME, config.E2E_ADMIN_PASSWORD, { customData: { first_name: 'Test', last_name: 'User', roles: ['admin'] } }); + + await page.goto(url + `?editSubmission=6140e4b4b9b4f7b3b3b1b1b1`); + + await page.locator('select[data-cy="promote-select"]').selectOption('Incident'); + + page.on('dialog', dialog => dialog.accept()); + + await page.locator('[data-cy="promote-button"]').click(); + + await expect(page.locator('[data-cy="toast"]').first()).toContainText('Successfully promoted submission to Incident 4 and Report 9'); + + const { data: { incidents } } = await query({ + query: gql`{ + incidents { + incident_id + title + reports { + report_number + } + } + } + `, + }); + + expect(incidents.find((i) => i.incident_id === 4).reports.map((r) => r.report_number)).toContain(9); + }); + + test('Promotes a submission to a new report and links it to an existing incident', async ({ page, login }) => { + + await init(); + + await login(config.E2E_ADMIN_USERNAME, config.E2E_ADMIN_PASSWORD, { customData: { first_name: 'Test', last_name: 'User', roles: ['incident_editor'] } }); + + await page.goto(url + `?editSubmission=6140e4b4b9b4f7b3b3b1b1b1`); + + await fillAutoComplete(page, '#input-incident_ids', 'Inc', 'Incident 1'); + + page.on('dialog', dialog => dialog.accept()); + + await page.locator('[data-cy="promote-to-report-button"]').click(); + + await expect(page.locator('[data-cy="toast"]')).toContainText('Successfully promoted submission to Incident 1 and Report 9'); + + const { data: { incidents } } = await query({ + query: gql`{ + incidents { + incident_id + title + reports { + report_number + } + } + } + `, + }); + + expect(incidents.find((i) => i.incident_id === 1).reports.map((r) => r.report_number)).toContain(9); + }); + + test('Promotes a submission to a new report and links it to multiple incidents', async ({ page, login }) => { + + await init(); + + await login(config.E2E_ADMIN_USERNAME, config.E2E_ADMIN_PASSWORD, { customData: { first_name: 'Test', last_name: 'User', roles: ['incident_editor'] } }); + + await page.goto(url + `?editSubmission=6140e4b4b9b4f7b3b3b1b1b1`); + + await fillAutoComplete(page, '#input-incident_ids', 'inci', 'Incident 2'); + await fillAutoComplete(page, '#input-incident_ids', 'Kron', 'Kronos'); + + page.on('dialog', dialog => dialog.accept()); + + await page.locator('[data-cy="promote-to-report-button"]').click(); + + await expect(page.getByText('Successfully promoted submission to Incident 2 and Report 9')).toBeVisible(); + await expect(page.getByText('Successfully promoted submission to Incident 3 and Report 9')).toBeVisible(); + + const { data: { incidents } } = await query({ + query: gql`{ + incidents { + incident_id + title + reports { + report_number + } + } + } + `, + }); + + expect(incidents.find((i) => i.incident_id === 2).reports.map((r) => r.report_number)).toContain(9); + expect(incidents.find((i) => i.incident_id === 3).reports.map((r) => r.report_number)).toContain(9); + }); + + test('Promotes a submission to a new issue', async ({ page, login }) => { + + await init(); + + await login(config.E2E_ADMIN_USERNAME, config.E2E_ADMIN_PASSWORD, { customData: { first_name: 'Test', last_name: 'User', roles: ['incident_editor'] } }); + + await page.goto(url + `?editSubmission=6140e4b4b9b4f7b3b3b1b1b1`); + + await page.locator('select[data-cy="promote-select"]').selectOption('Issue'); + + page.on('dialog', dialog => dialog.accept()); + + await page.locator('[data-cy="promote-button"]').click(); + + await expect(page.locator('[data-cy="toast"]').first()).toContainText('Successfully promoted submission to Issue 9'); + + const { data: { reports } } = await query({ + query: gql`{ + reports { + report_number + } + } + `, + }); + + expect(reports.find((r) => r.report_number === 9)).toBeDefined(); + }); + + test('Rejects a submission', async ({ page, login }) => { + + await init(); + + const submissions = await getSubmissions(); + + await login(config.E2E_ADMIN_USERNAME, config.E2E_ADMIN_PASSWORD, { customData: { first_name: 'Test', last_name: 'User', roles: ['incident_editor'] } }); + + await page.goto(url + `?editSubmission=${submissions[0]._id}`); + + + page.on('dialog', dialog => dialog.accept()); + + await page.locator('[data-cy="reject-button"]').click(); + + await page.waitForResponse((response) => response.request()?.postData()?.includes('deleteOneSubmission')); + + const updated = await getSubmissions(); + + expect(updated.find((s) => s._id === submissions[0]._id)).toBeUndefined(); + }); + + test('Edits a submission - update just a text', async ({ page, login }) => { + + await init(); + + const submissions = await getSubmissions(); + + await login(config.E2E_ADMIN_USERNAME, config.E2E_ADMIN_PASSWORD, { customData: { first_name: 'Test', last_name: 'User', roles: ['incident_editor'] } }); + + await page.goto(url + `?editSubmission=${submissions[0]._id}`); + + const text = '## Another one\n\n**More markdown**\n\nAnother paragraph with more text to reach the minimum character count!'; + + await setEditorText(page, text); + + await page.waitForResponse((response) => response.request()?.postData()?.includes('UpdateSubmission')); + + const updated = await getSubmissions(); + + expect(updated.find((s) => s._id === submissions[0]._id).text).toContain(text); + }); + + test('Edits a submission - uses fetch info', async ({ page, login }) => { + + await init(); + + await login(config.E2E_ADMIN_USERNAME, config.E2E_ADMIN_PASSWORD, { customData: { first_name: 'Test', last_name: 'User', roles: ['incident_editor'] } }); + + await page.goto(url + `?editSubmission=6140e4b4b9b4f7b3b3b1b1b1`); + + await page.locator('input[name="url"]').fill('https://arstechnica.com/gadgets/2017/11/youtube-to-crack-down-on-inappropriate-content-masked-as-kids-cartoons/'); + await page.click('[data-cy="fetch-info"]'); + + await page.waitForResponse(response => response.url().includes('/api/parseNews') && response.status() === 200); + + await expect(page.locator('input[label="Title"]')).toHaveValue('YouTube to crack down on inappropriate content masked as kids’ cartoons'); + }); + + test('Does not allow promotion of submission to Incident if schema is invalid (missing Description)', async ({ page, login }) => { + + const submissions: DBSubmission[] = [{ + _id: new ObjectId('5d34b8c29ced494f010ed469'), + authors: ["Author 1", "Author 2"], + cloudinary_id: "sample_cloudinary_id", + date_downloaded: "2021-09-14", + date_modified: "2021-09-14T00:00:00.000Z", + date_published: "2021-09-14", + date_submitted: "2021-09-14T00:00:00.000Z", + deployers: ["entity1"], + developers: ["entity2"], + harmed_parties: ["entity3"], + incident_editors: ["editor1"], + image_url: "https://sample_image_url.com", + language: "en", + source_domain: "example.com", + submitters: ["Submitter 1", "Submitter 2"], + tags: ["tag1", "tag2"], + text: "Sample text that must have at least 80 characters, so I will keep writing until I reach the minimum number of characters.", + title: "Sample title", + url: "http://example.com", + user: "user1", + incident_title: "Incident title", + incident_date: "2021-09-14", + editor_notes: "", + }] + + await init({ aiidprod: { submissions } }); + + await login(config.E2E_ADMIN_USERNAME, config.E2E_ADMIN_PASSWORD, { customData: { first_name: 'Test', last_name: 'User', roles: ['incident_editor'] } }); + + await page.goto(url + `?editSubmission=5d34b8c29ced494f010ed469`); + + await page.locator('select[data-cy="promote-select"]').selectOption('Incident'); + + await page.locator('[data-cy="promote-button"]').click(); + + await expect(page.locator('[data-cy="toast"]')).toContainText('Description is required'); + }); + + test('Does not allow promotion of submission to Issue if schema is invalid (missing Title)', async ({ page, login }) => { + + const submissions: DBSubmission[] = [{ + _id: new ObjectId('5d34b8c29ced494f010ed469'), + authors: ["Author 1", "Author 2"], + cloudinary_id: "sample_cloudinary_id", + date_downloaded: "2021-09-14", + date_modified: "2021-09-14T00:00:00.000Z", + date_published: "2021-09-14", + date_submitted: "2021-09-14T00:00:00.000Z", + deployers: ["entity1"], + developers: ["entity2"], + harmed_parties: ["entity3"], + incident_editors: ["editor1"], + image_url: "https://sample_image_url.com", + language: "en", + source_domain: "example.com", + submitters: ["Submitter 1", "Submitter 2"], + tags: ["tag1", "tag2"], + text: "Sample text that must have at least 80 characters, so I will keep writing until I reach the minimum number of characters.", + url: "http://example.com", + user: "user1", + incident_title: "Incident title", + incident_date: "2021-09-14", + editor_notes: "", + description: 'Sarasa', + title: "", + }] + + await init({ aiidprod: { submissions } }); + + await login(config.E2E_ADMIN_USERNAME, config.E2E_ADMIN_PASSWORD, { customData: { first_name: 'Test', last_name: 'User', roles: ['incident_editor'] } }); + + await page.goto(url + `?editSubmission=5d34b8c29ced494f010ed469`); + + await page.locator('select[data-cy="promote-select"]').selectOption('Issue'); + + await page.locator('[data-cy="promote-button"]').click(); + + await expect(page.locator('[data-cy="toast"]').first()).toContainText('*Title must have at least 6 characters'); + }); + + test('Should display an error message if Date Published is not in the past', async ({ page, login }) => { + + await init(); + + await login(config.E2E_ADMIN_USERNAME, config.E2E_ADMIN_PASSWORD, { customData: { first_name: 'Test', last_name: 'User', roles: ['incident_editor'] } }); + + await page.goto(url + `?editSubmission=6140e4b4b9b4f7b3b3b1b1b1`); + + await page.fill('input[name="date_published"]', '3000-01-01'); + + await expect(page.locator('[data-cy="submission-form"]')).toContainText('Date must be in the past'); + }); + + test('Should display an error message if Date Downloaded is not in the past', async ({ page, login }) => { + + await init(); + + await login(config.E2E_ADMIN_USERNAME, config.E2E_ADMIN_PASSWORD, { customData: { first_name: 'Test', last_name: 'User', roles: ['incident_editor'] } }); + + await page.goto(url + `?editSubmission=6140e4b4b9b4f7b3b3b1b1b1`); + + await page.fill('input[name="date_downloaded"]', '3000-01-01'); + + await expect(page.locator('[data-cy="submission-form"]')).toContainText('Date must be in the past'); + }); + + test('Claims a submission', async ({ page, login }) => { + + await init(); + + const userId = await login(config.E2E_ADMIN_USERNAME, config.E2E_ADMIN_PASSWORD, { customData: { first_name: 'Test', last_name: 'User', roles: ['incident_editor'] } }); + + await page.goto(url); + + await page.click('[data-cy="claim-submission"]'); + + await page.waitForResponse((response) => response.request()?.postData()?.includes('UpdateSubmission')); + + const { data: { submissions } } = await query({ + query: gql`{ + submissions { + _id + incident_editors { + userId + } + } + } + `, + }); + + expect(submissions.find((s) => s._id === '6140e4b4b9b4f7b3b3b1b1b1').incident_editors.map((e) => e.userId)).toContain(userId); + }); + + test('Unclaims a submission', async ({ page, login }) => { + + await init(); + + const userId = await login(config.E2E_ADMIN_USERNAME, config.E2E_ADMIN_PASSWORD, { customData: { first_name: 'Test', last_name: 'User', roles: ['incident_editor'] } }); + + const submissions: DBSubmission[] = [{ + _id: new ObjectId('63f3d58c26ab981f33b3f9c7'), + authors: ["Author 1", "Author 2"], + cloudinary_id: "sample_cloudinary_id", + date_downloaded: "2021-09-14", + date_modified: "2021-09-14T00:00:00.000Z", + date_published: "2021-09-14", + date_submitted: "2021-09-14T00:00:00.000Z", + deployers: ["entity1"], + developers: ["entity2"], + harmed_parties: ["entity3"], + incident_editors: [userId], + image_url: "https://sample_image_url.com", + language: "en", + source_domain: "example.com", + submitters: ["Submitter 1", "Submitter 2"], + tags: ["tag1", "tag2"], + text: "Sample text that must have at least 80 characters, so I will keep writing until I reach the minimum number of characters.", + url: "http://example.com", + user: "user1", + incident_title: "Incident title", + incident_date: "2021-09-14", + editor_notes: "", + description: 'Sarasa', + title: "Already Claimed", + }] + + await seedCollection({ name: 'submissions', docs: submissions, drop: false }); + + await page.goto(url); + + await page.getByText('Unclaim', { exact: true }).click(); + + await page.waitForResponse((response) => response.request()?.postData()?.includes('UpdateSubmission')); + + const { data: { submissions: updated } } = await query({ + query: gql`{ + submissions { + _id + incident_editors { + userId + } + } + } + `, + }); + + expect(updated.find((s) => s._id === '63f3d58c26ab981f33b3f9c7').incident_editors.map((e) => e.userId)).not.toContain(userId); + }); + + test('Should maintain current page while claiming', async ({ page, login }) => { + + await init(); + + const userId = await login(config.E2E_ADMIN_USERNAME, config.E2E_ADMIN_PASSWORD, { customData: { first_name: 'Test', last_name: 'User', roles: ['incident_editor'] } }); + + const submissions: DBSubmission[] = Array.from(Array(10).keys()).map(i => { + + return { + authors: ["Author 1", "Author 2"], + cloudinary_id: "sample_cloudinary_id", + date_downloaded: "2021-09-14", + date_modified: "2021-09-14T00:00:00.000Z", + date_published: "2021-09-14", + date_submitted: "2021-09-14T00:00:00.000Z", + deployers: ["entity1"], + developers: ["entity2"], + harmed_parties: ["entity3"], + incident_editors: [userId], + image_url: "https://sample_image_url.com", + language: "en", + source_domain: "example.com", + submitters: ["Submitter 1", "Submitter 2"], + tags: ["tag1", "tag2"], + text: "Sample text that must have at least 80 characters, so I will keep writing until I reach the minimum number of characters.", + url: "http://example.com", + user: "user1", + incident_title: "Incident title", + incident_date: "2021-09-14", + editor_notes: "", + description: 'Sarasa', + title: "Submission " + i, + } + }) + + await seedCollection({ name: 'submissions', docs: submissions, drop: false }); + + await page.goto(url); + + await page.click('.pagination button:has-text("Next")'); + await page.click('[data-cy="claim-submission"]'); + + await expect(page.locator('.pagination [aria-current="page"] button')).toHaveText('2'); + }); + + test('Should display "No reports found" if no quick adds are found', async ({ page }) => { + + await init({ aiidprod: { quickadds: [] } }, { drop: true }); + + await page.goto(url); + + + await expect(page.locator('[data-cy="no-results"]')).toContainText('No reports found'); + }); + + test('Should display submission image on edit page', async ({ page, login }) => { + + await init(); + + await login(config.E2E_ADMIN_USERNAME, config.E2E_ADMIN_PASSWORD, { customData: { first_name: 'Test', last_name: 'User', roles: ['incident_editor'] } }); + + await page.goto(url + `?editSubmission=6140e4b4b9b4f7b3b3b1b1b1`); + + await expect(page.locator('[data-cy="image-preview-figure"] img')).toHaveAttribute( + 'src', + 'https://res.cloudinary.com/pai/image/upload/f_auto/q_auto/v1/reports/s3.amazonaws.com/ledejs/resized/s2020-pasco-ilp/600/nocco5.jpg' + ); + }); + + test('Should display fallback image on edit modal if submission does not have an image', async ({ page, login }) => { + + await init(); + + const userId = await login(config.E2E_ADMIN_USERNAME, config.E2E_ADMIN_PASSWORD, { customData: { first_name: 'Test', last_name: 'User', roles: ['incident_editor'] } }); + + const submissions: DBSubmission[] = [{ + _id: new ObjectId('63f3d58c26ab981f33b3f9c7'), + authors: ["Author 1", "Author 2"], + cloudinary_id: "sample_cloudinary_id", + date_downloaded: "2021-09-14", + date_modified: "2021-09-14T00:00:00.000Z", + date_published: "2021-09-14", + date_submitted: "2021-09-14T00:00:00.000Z", + deployers: ["entity1"], + developers: ["entity2"], + harmed_parties: ["entity3"], + incident_editors: [userId], + image_url: "null", + language: "en", + source_domain: "example.com", + submitters: ["Submitter 1", "Submitter 2"], + tags: ["tag1", "tag2"], + text: "Sample text that must have at least 80 characters, so I will keep writing until I reach the minimum number of characters.", + url: "http://example.com", + user: "user1", + incident_title: "Incident title", + incident_date: "2021-09-14", + editor_notes: "", + description: 'Sarasa', + title: "Already Claimed", + }] + + await seedCollection({ name: 'submissions', docs: submissions, drop: false }); + + + await page.goto(url + `?editSubmission=63f3d58c26ab981f33b3f9c7`); + + + await expect(page.locator('[data-cy="image-preview-figure"] canvas')).toBeVisible(); + }); + + test('Edits a submission - links to existing incident - Incident Data should be hidden', async ({ page, login }) => { + await init(); + + await login(config.E2E_ADMIN_USERNAME, config.E2E_ADMIN_PASSWORD, { customData: { first_name: 'Test', last_name: 'User', roles: ['incident_editor'] } }); + + await page.goto(url + `?editSubmission=6140e4b4b9b4f7b3b3b1b1b1`); + + await page.fill(`input[name="incident_ids"]`, '1'); + + await page.waitForSelector(`[role="option"]`); + + await fillAutoComplete(page, '#input-incident_ids', 'inci', 'Incident 1'); + + await expect(page.locator('[data-cy="incident-data-section"]')).not.toBeVisible(); + }); +}); \ No newline at end of file diff --git a/site/gatsby-site/playwright/e2e-full/cite.spec.ts b/site/gatsby-site/playwright/e2e-full/cite.spec.ts index 63e217b59b..6c5ef34c67 100644 --- a/site/gatsby-site/playwright/e2e-full/cite.spec.ts +++ b/site/gatsby-site/playwright/e2e-full/cite.spec.ts @@ -17,8 +17,6 @@ test.describe('Cite pages', () => { const url = `/cite/${incidentId}`; - let user: { userId: string }; - let lastIncidentId: number; test.beforeAll(async ({ request }) => { @@ -30,11 +28,6 @@ test.describe('Cite pages', () => { const response = await query({ query: gql` { - user(filter: { first_name: {EQ: "Test"}, last_name: {EQ: "User" }}) { - userId - first_name - last_name - } incidents(sort: {incident_id: DESC}, pagination: {limit: 1}) { incident_id } @@ -42,7 +35,6 @@ test.describe('Cite pages', () => { `, }); - user = response.data.user; lastIncidentId = response.data.incidents[0].incident_id; }); @@ -567,8 +559,6 @@ test.describe('Cite pages', () => { test('Should link similar incidents', async ({ page, login }) => { - test.slow(); - await init(); await login(process.env.E2E_ADMIN_USERNAME, process.env.E2E_ADMIN_PASSWORD, { customData: { first_name: 'John', last_name: 'Doe', roles: ['admin'] } }); diff --git a/site/gatsby-site/playwright/e2e-full/citeEdit.spec.ts b/site/gatsby-site/playwright/e2e-full/citeEdit.spec.ts index 989e4fb361..f5546063c6 100644 --- a/site/gatsby-site/playwright/e2e-full/citeEdit.spec.ts +++ b/site/gatsby-site/playwright/e2e-full/citeEdit.spec.ts @@ -20,8 +20,6 @@ test.describe('Edit report', () => { test('Should load and update report values', async ({ page, login }) => { - test.slow(); - await login(config.E2E_ADMIN_USERNAME, config.E2E_ADMIN_PASSWORD, { customData: { roles: ['admin'] } }); // TODO: delete once we implement the new report history @@ -149,7 +147,7 @@ test.describe('Edit report', () => { test('Should load and update Issue values', async ({ page, login }) => { - test.slow(); + await login(config.E2E_ADMIN_USERNAME, config.E2E_ADMIN_PASSWORD, { customData: { roles: ['admin'] } }); @@ -264,7 +262,7 @@ test.describe('Edit report', () => { test('Should link a report to another incident', async ({ page, login }) => { - test.slow(); + const userId = await login(config.E2E_ADMIN_USERNAME, config.E2E_ADMIN_PASSWORD); await init({ customData: { users: [{ userId, first_name: 'Test', last_name: 'User', roles: ['admin'] }] } }, { drop: true }); @@ -323,7 +321,7 @@ test.describe('Edit report', () => { test('Should convert an incident report to an issue', async ({ page, login }) => { - test.slow(); + await login(config.E2E_ADMIN_USERNAME, config.E2E_ADMIN_PASSWORD, { customData: { roles: ['admin'] } }); diff --git a/site/gatsby-site/playwright/e2e-full/classificationsEditor.spec.ts b/site/gatsby-site/playwright/e2e-full/classificationsEditor.spec.ts index 986b3c589e..e779fe9ca2 100644 --- a/site/gatsby-site/playwright/e2e-full/classificationsEditor.spec.ts +++ b/site/gatsby-site/playwright/e2e-full/classificationsEditor.spec.ts @@ -76,7 +76,6 @@ test.describe('Classifications Editor', () => { }); test('Should show classifications editor on incident page and save edited values', async ({ page, login, skipOnEmptyEnvironment }) => { - await init(); await login(process.env.E2E_ADMIN_USERNAME, process.env.E2E_ADMIN_PASSWORD, { customData: { roles: ['admin'] } }); @@ -144,10 +143,10 @@ test.describe('Classifications Editor', () => { }); test('Should show classifications editor on report page and add a new classification', async ({ page, login, skipOnEmptyEnvironment }) => { - + await init(); - await login(process.env.E2E_ADMIN_USERNAME, process.env.E2E_ADMIN_PASSWORD, { customData: { first_name: 'John', last_name: 'Doe', roles: ['admin'] } }); + await login(process.env.E2E_ADMIN_USERNAME, process.env.E2E_ADMIN_PASSWORD, { customData: { first_name: 'John', last_name: 'Doe', roles: ['admin'] } }); await page.goto(reportURL); await waitForRequest('FindClassifications'); @@ -184,17 +183,20 @@ test.describe('Classifications Editor', () => { }) }); - const namespaces = ['GMF', 'CSETv1']; + const tests = [ + { incident_id: 2, namespace: 'GMF' }, + { incident_id: 1, namespace: 'CSETv1' } + ]; - for (const namespace of namespaces) { + for (const { incident_id, namespace } of tests) { test(`Should properly display and store ${namespace} classification values`, async ({ page, login, skipOnEmptyEnvironment }) => { - + await init(); - + await login(process.env.E2E_ADMIN_USERNAME, process.env.E2E_ADMIN_PASSWORD, { customData: { first_name: 'John', last_name: 'Doe', roles: ['admin'] } }); - await page.goto('/cite/1'); + await page.goto(`/cite/${incident_id}`); const { data: { taxas } } = await query({ query: gql` @@ -252,7 +254,17 @@ test.describe('Classifications Editor', () => { for (const [name, value] of Object.entries(selectedValues)) { - classification.attributes.find(({ short_name }) => short_name === name).value_json === JSON.stringify(value); + const attribute = classification.attributes.find(({ short_name }) => short_name === name); + const definition = taxa.field_list.find(({ short_name }) => short_name === name); + + if (definition.required) { + + expect(attribute.value_json === JSON.stringify(value)); + } + else if (attribute) { + + expect(attribute.value_json === JSON.stringify(value)); + } } } }); @@ -271,4 +283,4 @@ test.describe('Classifications Editor', () => { await page.locator('[data-cy="taxonomy-CSETv1"] [data-cy="AI System"] [value="yes"]').last().click(); await expect(page.locator('[data-cy="taxonomy-CSETv1"] [data-cy="AI System"] [value="yes"]').first()).not.toBeChecked(); }); -}); +}); \ No newline at end of file diff --git a/site/gatsby-site/playwright/e2e-full/incidentVariants.spec.ts b/site/gatsby-site/playwright/e2e-full/incidentVariants.spec.ts new file mode 100644 index 0000000000..408f4884d9 --- /dev/null +++ b/site/gatsby-site/playwright/e2e-full/incidentVariants.spec.ts @@ -0,0 +1,211 @@ +import { expect } from '@playwright/test'; +import { getVariantStatus, getVariantStatusText, isCompleteReport, VARIANT_STATUS } from '../../src/utils/variants'; +import { query, test } from '../utils'; +import gql from 'graphql-tag'; +import { init } from '../memory-mongo'; + +const incidentId = 3; + +async function getVariants() { + + const { data: { incident } } = await query({ + query: gql` + query { + incident(filter: { incident_id: { EQ: ${incidentId} } }) { + reports { + report_number + title + date_published + tags + url + source_domain + submitters + text + inputs_outputs + } + } + } + `}); + + const variants = incident.reports + .filter((r) => !isCompleteReport(r)) + .sort((a, b) => a.report_number - b.report_number); + + return variants; +} + +const new_date_published = '2000-01-01'; +const new_text = 'New text example with more than 80 characters. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.'; +const new_inputs_outputs_1 = 'New Input text'; +const new_inputs_outputs_2 = 'New Output text'; +const new_submitter = 'New Submitter'; + +test.describe('Variants pages', () => { + const url = `/cite/${incidentId}`; + + test('Successfully loads', async ({ page }) => { + await page.goto(url); + }); + + test('Should display Variant list', async ({ page }) => { + await page.goto(url); + await expect(page.getByText('Variants', { exact: true })).toBeVisible(); + + + const variants = await getVariants(); + + const variantCards = await page.locator('[data-cy=variant-card]'); + await expect(variantCards).toHaveCount(variants.length); + + for (let index = 0; index < variants.length; index++) { + const variant = variants[index]; + + const variantCard = await page.locator(`[data-cy=variant-card][id="r${variant.report_number}"]`); + const variantStatus = getVariantStatus(variant); + const variantStatusText = getVariantStatusText(variantStatus); + + await expect(variantCard.locator('[data-cy=variant-status-badge]')).toHaveText(variantStatusText); + await expect(variantCard.locator('[data-cy=variant-text]')).toHaveText(variant.text); + await expect(variantCard.locator('[data-cy=variant-inputs-outputs]').nth(0)).toHaveText(variant.inputs_outputs[0]); + await expect(variantCard.locator('[data-cy=variant-inputs-outputs]').nth(1)).toHaveText(variant.inputs_outputs[1]); + } + }); + + test('Should add a new Variant - Unauthenticated user', async ({ page }) => { + + await init(); + + await page.goto(url); + + await expect(page.getByText('Variants', { exact: true })).toBeVisible(); + + await expect(page.locator('[data-cy=variant-form]')).not.toBeVisible(); + + await page.locator('[data-cy=add-variant-btn]').click(); + await page.waitForSelector('[data-cy=variant-form]'); + + await page.locator('[data-cy="variant-form-date-published"]').fill(new_date_published); + await page.locator('[data-cy="variant-form-submitters"] input').first().fill(new_submitter); + await page.locator('[data-cy="variant-form-text"]').fill(new_text); + await page.locator('[data-cy="variant-form-inputs-outputs"]').nth(0).fill(new_inputs_outputs_1); + await page.locator('[data-cy="add-text-row-btn"]').click(); + await page.locator('[data-cy="variant-form-inputs-outputs"]').nth(1).fill(new_inputs_outputs_2); + + await page.locator('[data-cy=add-variant-submit-btn]').click(); + + await expect(page.locator('[data-cy=success-message]')).toContainText( + "Your variant has been added to the review queue and will appear on this page within 12 hours" + ); + await expect(page.locator('[data-cy="toast"]')).toContainText( + 'Your variant has been added to the review queue and will appear on this page within 12 hours.' + ); + }); + + test("Shouldn't edit a Variant - Unauthenticated user", async ({ page }) => { + await page.goto(url); + await expect(page.locator('[data-cy=edit-variant-btn]')).not.toBeVisible(); + }); + + test('Should Approve Variant - Incident Editor user', async ({ page, login }) => { + + await login(process.env.E2E_ADMIN_USERNAME, process.env.E2E_ADMIN_PASSWORD, { customData: { first_name: 'John', last_name: 'Doe', roles: ['incident_editor'] } }); + + await page.goto(url); + + const variants = await getVariants(); + + if (variants.length > 0) { + await page.locator('[data-cy=variant-card]').nth(0).locator('[data-cy=edit-variant-btn]').click(); + await page.waitForSelector('[data-cy=edit-variant-modal]'); + + await page.locator('[data-cy="variant-form-date-published"]').fill(new_date_published); + await page.locator('[data-cy="variant-form-submitters"] input').first().fill(new_submitter); + await page.locator('[data-cy="variant-form-text"]').fill(new_text); + await page.locator('[data-cy="variant-form-inputs-outputs"]').nth(0).fill(new_inputs_outputs_1); + await page.locator('[data-cy="variant-form-inputs-outputs"]').nth(1).fill(new_inputs_outputs_2); + + await page.locator('[data-cy=approve-variant-btn]').click(); + + await expect(page.locator('[data-cy="toast"]')).toHaveText( + 'Variant successfully updated. Your edits will be live within 24 hours.' + ); + await expect(page.locator('[data-cy=edit-variant-modal]')).not.toBeVisible(); + } + }); + + test('Should Reject Variant - Incident Editor user', async ({ page, login }) => { + + await login(process.env.E2E_ADMIN_USERNAME, process.env.E2E_ADMIN_PASSWORD, { customData: { first_name: 'John', last_name: 'Doe', roles: ['incident_editor'] } }); + + await page.goto(url); + + const variants = await getVariants(); + if (variants.length > 0) { + await page.locator('[data-cy=variant-card]').nth(0).locator('[data-cy=edit-variant-btn]').click(); + await page.waitForSelector('[data-cy=edit-variant-modal]'); + + await page.locator('[data-cy="variant-form-date-published"]').fill(new_date_published); + await page.locator('[data-cy="variant-form-submitters"] input').first().fill(new_submitter); + await page.locator('[data-cy="variant-form-text"]').fill(new_text); + await page.locator('[data-cy="variant-form-inputs-outputs"]').nth(0).fill(new_inputs_outputs_1); + await page.locator('[data-cy="variant-form-inputs-outputs"]').nth(1).fill(new_inputs_outputs_2); + + await page.locator('[data-cy=reject-variant-btn]').click(); + + await expect(page.locator('[data-cy="toast"]')).toHaveText( + 'Variant successfully updated. Your edits will be live within 24 hours.' + ); + await expect(page.locator('[data-cy=edit-variant-modal]')).not.toBeVisible(); + } + }); + + test('Should Save Variant - Incident Editor user', async ({ page, login }) => { + + await login(process.env.E2E_ADMIN_USERNAME, process.env.E2E_ADMIN_PASSWORD, { customData: { first_name: 'John', last_name: 'Doe', roles: ['incident_editor'] } }); + + await page.goto(url); + + const variants = await getVariants(); + + if (variants.length > 0) { + await page.locator('[data-cy=variant-card]').nth(0).locator('[data-cy=edit-variant-btn]').click(); + await page.waitForSelector('[data-cy=edit-variant-modal]'); + + await page.locator('[data-cy="variant-form-date-published"]').fill(new_date_published); + await page.locator('[data-cy="variant-form-submitters"] input').first().fill(new_submitter); + await page.locator('[data-cy="variant-form-text"]').fill(new_text); + await page.locator('[data-cy="variant-form-inputs-outputs"]').nth(0).fill(new_inputs_outputs_1); + + await page.locator('[data-cy=save-variant-btn]').click(); + + await expect(page.locator('[data-cy="toast"]')).toHaveText( + 'Variant successfully updated. Your edits will be live within 24 hours.' + ); + await expect(page.locator('[data-cy=edit-variant-modal]')).not.toBeVisible(); + } + }); + + test('Should Delete Variant - Incident Editor user', async ({ page, login }) => { + + await login(process.env.E2E_ADMIN_USERNAME, process.env.E2E_ADMIN_PASSWORD, { customData: { first_name: 'John', last_name: 'Doe', roles: ['incident_editor'] } }); + + await page.goto(url); + + const variants = await getVariants(); + + if (variants.length > 0) { + + await page.locator('[data-cy=variant-card]').nth(0).locator('[data-cy=edit-variant-btn]').click(); + await expect(page.locator('[data-cy=edit-variant-modal]')).toBeVisible(); + + page.on('dialog', dialog => dialog.accept()); + + await page.locator('[data-cy=delete-variant-btn]').click(); + + await expect(page.locator('[data-cy="toast"]')).toHaveText( + 'Variant successfully deleted. Your changes will be live within 24 hours.' + ); + await expect(page.locator('[data-cy=edit-variant-modal]')).not.toBeVisible(); + } + }); +}); \ No newline at end of file diff --git a/site/gatsby-site/playwright/e2e-full/incidents/new.spec.ts b/site/gatsby-site/playwright/e2e-full/incidents/new.spec.ts index 59f66e0ea9..5a35852e30 100644 --- a/site/gatsby-site/playwright/e2e-full/incidents/new.spec.ts +++ b/site/gatsby-site/playwright/e2e-full/incidents/new.spec.ts @@ -12,8 +12,6 @@ test.describe('New Incident page', () => { await init(); - test.slow(); - await login(process.env.E2E_ADMIN_USERNAME, process.env.E2E_ADMIN_PASSWORD, { customData: { roles: ['admin'], first_name: 'John', last_name: 'Doe' } }); await page.goto(url); @@ -57,7 +55,7 @@ test.describe('New Incident page', () => { await init(); - test.slow(); + await login(process.env.E2E_ADMIN_USERNAME, process.env.E2E_ADMIN_PASSWORD, { customData: { roles: ['admin'], first_name: 'John', last_name: 'Doe' } }); diff --git a/site/gatsby-site/playwright/e2e-full/socialShareButtons.spec.ts b/site/gatsby-site/playwright/e2e-full/socialShareButtons.spec.ts new file mode 100644 index 0000000000..d06d309f36 --- /dev/null +++ b/site/gatsby-site/playwright/e2e-full/socialShareButtons.spec.ts @@ -0,0 +1,105 @@ +import { expect } from '@playwright/test'; +import config from '../config'; +import { test } from '../utils'; + +const incidentId = 3; +const incidentUrl = `/cite/${incidentId}/`; +const blogPostUrl = `/blog/join-raic/`; +const shareButtonsPerSection = 4; +const urlsToTest = [ + { + page: 'Blog Post', + url: blogPostUrl, + title: `Join the Responsible AI Collaborative Founding Staff`, + shareButtonSections: 1, + fbShareUrl: [ + 'https://www.facebook.com/login/?next=https%3A%2F%2Fwww.facebook.com%2Fshare_channel%2F%3Flink%3Dhttps%253A%252F%252Fincidentdatabase.ai%252Fblog%252Fjoin-raic%252F%26', + ] + }, +]; + +if (!config.IS_EMPTY_ENVIRONMENT) { + urlsToTest.push({ + page: 'Incident', + url: incidentUrl, + title: 'Incident 3: Kronos Scheduling Algorithm Allegedly Caused Financial Issues for Starbucks Employees', + shareButtonSections: 1, + fbShareUrl: [ + 'https://www.facebook.com/login/?next=https%3A%2F%2Fwww.facebook.com%2Fshare_channel%2F%3Flink%3Dhttps%253A%252F%252Fincidentdatabase.ai%252Fcite%252F3%252F%26', + ] + }); +} + +test.describe('Social Share Buttons', () => { + + test.describe.configure({ retries: 4 }); + + urlsToTest.forEach(({ page, url, title, shareButtonSections, fbShareUrl }) => { + test(`${page} page should have ${shareButtonSections} Social Share button sections`, async ({ page }) => { + await page.goto(url, { waitUntil: 'domcontentloaded' }); + const buttons = page.locator('[data-cy="social-share-buttons"] button'); + await expect(buttons).toHaveCount(shareButtonSections * shareButtonsPerSection); + }); + + const canonicalUrl = `https://incidentdatabase.ai${url}`; + + test(`${page} page should have a Twitter share button`, async ({ page }) => { + await page.goto(url); + const twitterButton = page.locator('[data-cy=btn-share-twitter]'); + await expect(twitterButton).toBeVisible(); + + const popupPromise = page.waitForEvent('popup'); + await twitterButton.first().click(); + + const popup = await popupPromise; + + await popup.waitForURL(`https://x.com/intent/post?text=${encodeURIComponent(title).replace(/%20/g, '+')}&url=${encodeURIComponent(canonicalUrl)}`); + }); + + test(`${page} page should have a LinkedIn share button`, async ({ page }) => { + await page.goto(url); + const linkedInButton = page.locator('[data-cy=btn-share-linkedin]'); + await expect(linkedInButton).toBeVisible(); + await page.evaluate(() => { + window.open = (url: string) => { window.location.href = url; return null; }; + }); + await linkedInButton.first().click(); + await page.waitForURL(/https:\/\/www\.linkedin\.com*/); + }); + + test(`${page} page should have an Email share button`, async ({ page }) => { + await page.goto(url); + const emailButton = page.locator('[data-cy=btn-share-email]'); + await expect(emailButton).toBeTruthy(); + await page.evaluate((url) => { + window.open = (link: string) => { + if (link.startsWith('mailto:')) { + window.location.href = url; + } + return null; + }; + }, canonicalUrl); + await emailButton.first().click(); + await page.waitForURL(canonicalUrl); + + }); + + test(`${page} page should have a Facebook share button`, async ({ page }) => { + await page.goto(url); + const facebookButton = page.locator('[data-cy=btn-share-facebook]'); + await expect(facebookButton).toBeVisible(); + + const popupPromise = page.waitForEvent('popup'); + + await facebookButton.first().click(); + + const popup = await popupPromise; + + expect(async () => { + const popupUrl = await popup.url(); + await expect(fbShareUrl.some(url => popupUrl.includes(url))).toBeTruthy(); + }).toPass(); + }); + }); + +}); \ No newline at end of file diff --git a/site/gatsby-site/playwright/e2e-full/submit.spec.ts b/site/gatsby-site/playwright/e2e-full/submit.spec.ts index ed62314a48..83e1c2245f 100644 --- a/site/gatsby-site/playwright/e2e-full/submit.spec.ts +++ b/site/gatsby-site/playwright/e2e-full/submit.spec.ts @@ -1,1509 +1,580 @@ -import parseNews from '../fixtures/api/parseNews.json'; -import { conditionalIntercept, waitForRequest, setEditorText, test, trackRequest, query, fillAutoComplete } from '../utils'; import { expect } from '@playwright/test'; +import { gql } from 'graphql-tag'; +import { isArray } from 'lodash'; +import { init, seedCollection } from '../memory-mongo'; +import { fillAutoComplete, query, setEditorText, test } from '../utils'; import config from '../config'; -import { init } from '../memory-mongo'; -import gql from 'graphql-tag'; +import { DBSubmission } from '../seeds/aiidprod/submissions'; +import { ObjectId } from 'mongodb'; +test.describe('Submitted reports', () => { + const url = '/apps/submitted'; -test.describe('The Submit form', () => { - const url = '/apps/submit'; - const parserURL = '/api/parseNews**'; - - test('Successfully loads', async ({ page }) => { - await page.goto(url); - }); - - test('Should submit a new report not linked to any incident once all fields are filled properly', async ({ page }) => { - - await conditionalIntercept( - page, - '**/parseNews**', - () => true, - parseNews, - 'parseNews' - ); - - await trackRequest( - page, - '**/graphql', - (req) => req.postDataJSON().operationName == 'FindSubmissions', - 'findSubmissions' - ); - - await page.goto(url); - - await waitForRequest('findSubmissions'); - - await page.locator('input[name="url"]').fill( - `https://www.arstechnica.com/gadgets/2017/11/youtube-to-crack-down-on-inappropriate-content-masked-as-kids-cartoons/` - ); - - await page.locator('button:has-text("Fetch info")').click(); - - await waitForRequest('parseNews'); - - await page.locator('[name="incident_date"]').fill('2020-01-01'); - - await expect(page.locator('.form-has-errors')).not.toBeVisible(); - - await page.locator('[data-cy="to-step-2"]').click(); - - await page.locator('input[name="submitters"]').fill('Something'); - - await page.locator('[name="language"]').selectOption('Spanish'); - - await page.locator('[data-cy="to-step-3"]').click(); - - await expect(page.locator('[name="incident_title"]')).not.toBeVisible(); - - await page.locator('[name="description"]').fill('Description'); - - await expect(page.locator('[name="incident_editors"]')).not.toBeVisible(); - - await page.locator('[name="tags"]').fill('New Tag'); - await page.keyboard.press('Enter'); - - await page.locator('[name="editor_notes"]').fill('Here are some notes'); - - await page.locator('button[type="submit"]').click(); - - await expect(page.locator('.tw-toast:has-text("Report successfully added to review queue. You can see your submission")')).toBeVisible(); - - const { data } = await query({ - query: gql` - query { - submission(sort: { _id: DESC }){ + const getSubmissions = async () => { + const { data: { submissions } } = await query({ + query: gql`{ + submissions { _id title - text - authors - incident_ids + submitters + incident_date incident_editors { - userId + userId } + status + text } - } - `, + } + `, }); - expect(data.submission).toMatchObject({ - title: "YouTube to crack down on inappropriate content masked as kids’ cartoons", - text: parseNews.text, - authors: ["Valentina Palladino"], - incident_ids: [], - incident_editors: [], - }); - }); + return submissions; + } - test('Should autocomplete entities', async ({ page, skipOnEmptyEnvironment }) => { + test('Loads submissions', async ({ page }) => { - await conditionalIntercept( - page, - '**/parseNews**', - () => true, - parseNews, - 'parseNews' - ); + await init(); - await trackRequest( - page, - '**/graphql', - (req) => req.postDataJSON().operationName == 'FindSubmissions', - 'findSubmissions' - ); + const submissions = await getSubmissions(); await page.goto(url); - await waitForRequest('findSubmissions'); - - await page.locator('input[name="url"]').fill( - `https://www.arstechnica.com/gadgets/2017/11/youtube-to-crack-down-on-inappropriate-content-masked-as-kids-cartoons/` - ); + await expect(page.locator('[data-cy="submissions"] [data-cy="row"]')).toHaveCount(submissions.length); - await page.locator('button:has-text("Fetch info")').click(); + for (let index = 0; index < submissions.length; index++) { + const report = submissions[index]; + const row = page.locator('[data-cy="submissions"] [data-cy="row"]').nth(index); - await waitForRequest('parseNews'); + await expect(row.locator('[data-cy="cell"] [data-cy="review-submission"]')).not.toBeVisible(); - await page.locator('[name="incident_date"]').fill('2020-01-01'); + const keys = ['title', 'submitters', 'incident_date', 'incident_editors', 'status']; - await expect(page.locator('.form-has-errors')).not.toBeVisible(); + for (let cellIndex = 0; cellIndex < keys.length; cellIndex++) { + if (report[keys[cellIndex]]) { + let value = report[keys[cellIndex]]; - await page.locator('[data-cy="to-step-2"]').click(); + if (isArray(value)) { - await page.locator('[data-cy="to-step-3"]').click(); + if (keys[cellIndex] === 'incident_editors') { + value = value.map((s) => s.userId); + } - await page.locator('input[name="deployers"]').fill('Entity 1'); - - await page.locator('#deployers-tags .dropdown-item[aria-label="Entity 1"]').click(); + for (let i = 0; i < value.length; i++) { + await expect(row.locator('[data-cy="cell"]').nth(cellIndex)).toContainText(value[i]); + } + } + else { - await page.locator('input[name="deployers"]').fill('NewDeployer'); - await page.keyboard.press('Enter'); - await page.locator('button[type="submit"]').click(); + await expect(row.locator('[data-cy="cell"]').nth(cellIndex)).toContainText(value); + } - await expect(page.locator('.tw-toast:has-text("Report successfully added to review queue. You can see your submission")')).toBeVisible(); - await expect(page.locator(':text("Please review. Some data is missing.")')).not.toBeVisible(); + } + } + } }); - test('As editor, should submit a new incident report, adding an incident title and editors.', async ({ page, login, skipOnEmptyEnvironment }) => { - + test('Promotes a submission to a new report and links it to a new incident', async ({ page, login }) => { await init(); - const userId = await login(config.E2E_ADMIN_USERNAME, config.E2E_ADMIN_PASSWORD, { customData: { first_name: 'Cesar', last_name: 'Ito', roles: ['admin'] } }); - - await conditionalIntercept( - page, - '**/parseNews**', - () => true, - parseNews, - 'parseNews' - ); - - await trackRequest( - page, - '**/graphql', - (req) => req.postDataJSON().operationName == 'FindSubmissions', - 'findSubmissions' - ); - - await trackRequest( - page, - '**/graphql', - (req) => req.postDataJSON().operationName == 'FindUsers', - 'findUsers' - ); - - await page.goto(url); - - await waitForRequest('findSubmissions'); - - await page.locator('input[name="url"]').fill( - `https://www.arstechnica.com/gadgets/2017/11/youtube-to-crack-down-on-inappropriate-content-masked-as-kids-cartoons/` - ); - - await page.locator('button:has-text("Fetch info")').click(); - - await waitForRequest('parseNews'); - - await page.locator('[name="incident_date"]').fill('2020-01-01'); - - await expect(page.locator('.form-has-errors')).not.toBeVisible(); - - await page.locator('[data-cy="to-step-2"]').click(); - - await page.locator('[name="language"]').selectOption('Spanish'); - - await page.locator('[data-cy="to-step-3"]').click(); - - await waitForRequest('findUsers'); - - await page.locator('[name="incident_title"]').fill('Elsagate'); - - await page.locator('[name="description"]').fill('Description'); - - await fillAutoComplete(page, "#input-incident_editors", 'Ces', 'Cesar Ito'); - - await page.locator('[name="tags"]').fill('New Tag'); - await page.keyboard.press('Enter'); - - await page.locator('[name="editor_notes"]').fill('Here are some notes'); + await login(config.E2E_ADMIN_USERNAME, config.E2E_ADMIN_PASSWORD, { customData: { first_name: 'Test', last_name: 'User', roles: ['admin'] } }); - await page.locator('button[type="submit"]').click(); + await page.goto(url + `?editSubmission=6140e4b4b9b4f7b3b3b1b1b1`); - await expect(page.locator('.tw-toast:has-text("Report successfully added to review queue")')).toBeVisible(); + await page.locator('select[data-cy="promote-select"]').selectOption('Incident'); - await expect(page.locator('.tw-toast a')).toHaveAttribute('href', '/apps/submitted/'); + page.on('dialog', dialog => dialog.accept()); - await expect(page.locator(':text("Please review. Some data is missing.")')).not.toBeVisible(); + await page.locator('[data-cy="promote-button"]').click(); + await expect(page.locator('[data-cy="toast"]').first()).toContainText('Successfully promoted submission to Incident 4 and Report 9'); - const { data } = await query({ - query: gql` - query { - submission(sort: { _id: DESC }){ - _id + const { data: { incidents } } = await query({ + query: gql`{ + incidents { + incident_id title - text - authors - incident_ids - incident_editors { - userId + reports { + report_number } } - } - `, + } + `, }); - expect(data.submission).toMatchObject({ - title: "YouTube to crack down on inappropriate content masked as kids’ cartoons", - text: parseNews.text, - authors: ["Valentina Palladino"], - incident_ids: [], - incident_editors: [{ userId }], - }); + expect(incidents.find((i) => i.incident_id === 4).reports.map((r) => r.report_number)).toContain(9); }); - test('Should submit a new report linked to incident 1 once all fields are filled properly', async ({ page, login, skipOnEmptyEnvironment }) => { - - test.slow(); + test('Promotes a submission to a new report and links it to an existing incident', async ({ page, login }) => { await init(); - await conditionalIntercept( - page, - '**/parseNews**', - () => true, - parseNews, - 'parseNews' - ); - - await trackRequest( - page, - '**/graphql', - (req) => req.postDataJSON().operationName == 'FindSubmissions', - 'findInitialSubmissions' - ); - - await page.goto(url); - - await waitForRequest('findInitialSubmissions'); - - await page.locator('input[name="url"]').fill( - `https://www.arstechnica.com/gadgets/2017/11/youtube-to-crack-down-on-inappropriate-content-masked-as-kids-cartoons/` - ); - - await page.locator('button:has-text("Fetch info")').click(); - - await waitForRequest('parseNews'); - - await setEditorText( - page, - `Recent news stories and blog posts highlighted the underbelly of YouTube Kids, Google's children-friendly version of the wide world of YouTube. While all content on YouTube Kids is meant to be suitable for children under the age of 13, some inappropriate videos using animations, cartoons, and child-focused keywords manage to get past YouTube's algorithms and in front of kids' eyes. Now, YouTube will implement a new policy in an attempt to make the whole of YouTube safer: it will age-restrict inappropriate videos masquerading as children's content in the main YouTube app.` - ); - - await page.locator('[data-cy=related-byText] [data-cy=result] [data-cy=set-id]:has-text("#1")').first().click(); - - await page.locator('[data-cy=related-byText] [data-cy=result] [data-cy="similar-selector"] [data-cy="similar"]').last().click(); - - await expect(page.locator('.form-has-errors')).not.toBeVisible(); - - await page.locator('[data-cy="to-step-2"]').click(); - - await page.locator('[data-cy="to-step-3"]').click(); - - await expect(page.locator('[name="incident_title"]')).not.toBeVisible(); - - await expect(page.locator('[name="description"]')).not.toBeVisible(); - - await expect(page.locator('[name="incident_editors"]')).not.toBeVisible(); - - await page.locator('[name="tags"]').fill('New Tag'); - await page.keyboard.press('Enter'); - - await page.locator('[name="editor_notes"]').fill('Here are some notes'); - - await page.locator('button[type="submit"]').click(); - - await expect(page.locator(':text("Report successfully added to review queue")')).toBeVisible(); - - - await login(config.E2E_ADMIN_USERNAME, config.E2E_ADMIN_PASSWORD, { customData: { first_name: 'Test', last_name: 'User', roles: ['admin'] } }); - - await page.goto('/apps/submitted'); - - await expect(page.locator('[data-cy="row"]:has-text("YouTube to crack down on inappropriate content masked as kids’ cartoons")')).toBeVisible(); - }); - - test('Should show a toast on error when failing to reach parsing endpoint', async ({ page }) => { - await page.goto(url); - - await page.route(parserURL, route => route.abort('failed')); - - await page.locator('input[name="url"]').fill( - `https://www.cbsnews.com/news/is-starbucks-shortchanging-its-baristas/` - ); - - await page.locator('button:has-text("Fetch info")').click(); - - await page.waitForSelector('.tw-toast:has-text("Error reaching news info endpoint, please try again in a few seconds.")'); - }); - - test('Should pull parameters from the query string and auto-fill fields', async ({ page }) => { - - const values = { - url: 'https://incidentdatabase.ai', - title: 'test title', - authors: 'test author', - submitters: 'test submitter', - incident_date: '2022-01-01', - date_published: '2021-01-02', - date_downloaded: '2021-01-03', - image_url: 'https://incidentdatabase.ai/image.jpg', - incident_ids: [1], - text: '## Sit quo accusantium \n\n quia **assumenda**. Quod delectus similique labore optio quaease', - tags: 'test tag', - editor_notes: 'Here are some notes', - }; - - const params = new URLSearchParams(values); - - await conditionalIntercept( - page, - '**/parseNews**', - () => true, - { - title: 'test title', - authors: 'test author', - date_published: '2021-01-02', - date_downloaded: '2021-01-03', - image_url: 'https://incidentdatabase.ai/image.jpg', - text: '## Sit quo accusantium \n\n quia **assumenda**. Quod delectus similique labore optio quaease', - }, - 'parseNews' - ); - - await trackRequest( - page, - '**/graphql', - (req) => req.postDataJSON().operationName == 'FindSubmissions', - 'findSubmissions' - ); - - await page.goto(url + `?${params.toString()}`); + await login(config.E2E_ADMIN_USERNAME, config.E2E_ADMIN_PASSWORD, { customData: { first_name: 'Test', last_name: 'User', roles: ['incident_editor'] } }); - await waitForRequest('parseNews'); + await page.goto(url + `?editSubmission=6140e4b4b9b4f7b3b3b1b1b1`); - await waitForRequest('findSubmissions'); + await fillAutoComplete(page, '#input-incident_ids', 'Inc', 'Incident 1'); - await expect(page.locator('.form-has-errors')).not.toBeVisible(); + page.on('dialog', dialog => dialog.accept()); - await page.locator('[data-cy="to-step-2"]').click(); + await page.locator('[data-cy="promote-to-report-button"]').click(); - await page.locator('[data-cy="to-step-3"]').click(); + await expect(page.locator('[data-cy="toast"]')).toContainText('Successfully promoted submission to Incident 1 and Report 9'); - await page.locator('button[type="submit"]').click(); + const { data: { incidents } } = await query({ + query: gql`{ + incidents { + incident_id + title + reports { + report_number + } + } + } + `, + }); - await expect(page.locator('.tw-toast:has-text("Report successfully added to review queue")')).toBeVisible(); + expect(incidents.find((i) => i.incident_id === 1).reports.map((r) => r.report_number)).toContain(9); }); - test('Should submit a submission and link it to the current user id', async ({ page, login, skipOnEmptyEnvironment }) => { - - const userId = await login(config.E2E_ADMIN_USERNAME, config.E2E_ADMIN_PASSWORD, { customData: { first_name: 'Test', last_name: 'User', roles: ['admin'] } }); - - const values = { - url: 'https://incidentdatabase.ai', - title: 'test title', - authors: 'test author', - incident_date: '2022-01-01', - date_published: '2021-01-02', - date_downloaded: '2021-01-03', - image_url: 'https://incidentdatabase.ai/image.jpg', - incident_ids: [1], - text: '## Sit quo accusantium \n\n quia **assumenda**. Quod delectus similique labore optio quaease', - tags: 'test tag', - editor_notes: 'Here are some notes', - }; - - const params = new URLSearchParams(values); - - await page.route(parserURL, route => route.fulfill({ - body: JSON.stringify({ - title: 'test title', - authors: 'test author', - date_published: '2021-01-02', - date_downloaded: '2021-01-03', - image_url: 'https://incidentdatabase.ai/image.jpg', - text: '## Sit quo accusantium \n\n quia **assumenda**. Quod delectus similique labore optio quaease', - }) - })); - + test('Promotes a submission to a new report and links it to multiple incidents', async ({ page, login }) => { - await page.goto(url + `?${params.toString()}`); - - await waitForRequest('findIncidentsTitles'); + await init(); - await expect(page.locator('.form-has-errors')).not.toBeVisible(); + await login(config.E2E_ADMIN_USERNAME, config.E2E_ADMIN_PASSWORD, { customData: { first_name: 'Test', last_name: 'User', roles: ['incident_editor'] } }); - await page.locator('[data-cy="to-step-2"]').click(); + await page.goto(url + `?editSubmission=6140e4b4b9b4f7b3b3b1b1b1`); - await page.locator('[data-cy="to-step-3"]').click(); + await fillAutoComplete(page, '#input-incident_ids', 'inci', 'Incident 2'); + await fillAutoComplete(page, '#input-incident_ids', 'Kron', 'Kronos'); - await page.locator('button[type="submit"]').click(); + page.on('dialog', dialog => dialog.accept()); - await expect(page.locator('.tw-toast:has-text("Report successfully added to review queue")')).toBeVisible(); + await page.locator('[data-cy="promote-to-report-button"]').click(); + await expect(page.getByText('Successfully promoted submission to Incident 2 and Report 9')).toBeVisible(); + await expect(page.getByText('Successfully promoted submission to Incident 3 and Report 9')).toBeVisible(); - const { data } = await query({ - query: gql` - query { - submission(sort: { _id: DESC }){ - _id + const { data: { incidents } } = await query({ + query: gql`{ + incidents { + incident_id title - text - authors - incident_ids - user { - userId + reports { + report_number } } - } - `, + } + `, }); - expect(data.submission).toMatchObject({ - user: { userId }, - }); + expect(incidents.find((i) => i.incident_id === 2).reports.map((r) => r.report_number)).toContain(9); + expect(incidents.find((i) => i.incident_id === 3).reports.map((r) => r.report_number)).toContain(9); }); + test('Promotes a submission to a new issue', async ({ page, login }) => { - test.skip('Should show a list of related reports', async ({ page, skipOnEmptyEnvironment }) => { - - const relatedReports = { - byURL: { - data: { - reports: [ - { - __typename: 'Report', - report_number: 1501, - title: 'Zillow to exit its home buying business, cut 25% of staff', - url: 'https://www.cnn.com/2021/11/02/homes/zillow-exit-ibuying-home-business/index.html', - }, - ], - }, - }, - byDatePublished: { - data: { - reports: [ - { - __typename: 'Report', - report_number: 810, - title: "Google's Nest Stops Selling Its Smart Smoke Alarm For Now Due To Faulty Feature", - url: 'https://www.forbes.com/sites/aarontilley/2014/04/03/googles-nest-stops-selling-its-smart-smoke-alarm-for-now', - }, - { - __typename: 'Report', - report_number: 811, - title: 'Why Nest’s Smoke Detector Fail Is Actually A Win For Everyone', - url: 'https://readwrite.com/2014/04/04/nest-smoke-detector-fail/', - }, - ], - }, - }, - byAuthors: { - data: { reports: [] }, - }, - byIncidentId: { - data: { - incidents: [ - { - __typename: 'Incident', - incident_id: 1, - title: 'Google’s YouTube Kids App Presents Inappropriate Content', - reports: [ - { - __typename: 'Report', - report_number: 10, - title: 'Google’s YouTube Kids App Presents Inappropriate Content', - url: 'https://www.change.org/p/remove-youtube-kids-app-until-it-eliminates-its-inappropriate-content', - }, - ], - }, - ], - }, - }, - }; - - await trackRequest( - page, - '**/graphql', - (req) => req.postDataJSON().operationName == 'FindSubmissions', - 'findSubmissions' - ); - - await page.goto(url); - - await waitForRequest('findSubmissions'); - - // await conditionalIntercept( - // page, - // '**/graphql', - // (req) => - // req.postDataJSON().operationName == 'ProbablyRelatedReports' && - // req.postDataJSON().variables.query?.url_in, - // relatedReports.byURL, - // 'RelatedReportsByURL' - // ); - - // await conditionalIntercept( - // page, - // '**/graphql', - // (req) => - // req.postDataJSON().operationName == 'ProbablyRelatedReports' && - // req.postDataJSON().variables.query?.epoch_date_published_gt && - // req.postDataJSON().variables.query?.epoch_date_published_lt, - // relatedReports.byDatePublished, - // 'RelatedReportsByPublishedDate' - // ); - - // await conditionalIntercept( - // page, - // '**/graphql', - // (req) => - // req.postDataJSON().operationName == 'ProbablyRelatedReports' && - // req.postDataJSON().variables.query?.authors_in?.length, - // relatedReports.byAuthors, - // 'RelatedReportsByAuthor' - // ); - - const values = { - url: 'https://www.cnn.com/2021/11/02/homes/zillow-exit-ibuying-home-business/index.html', - authors: 'test author', - date_published: '2014-03-30', - incident_ids: 1, - }; - - for (const key in values) { - if (key == 'incident_ids') { - await page.locator(`input[name="${key}"]`).fill(values[key].toString()); - await page.waitForSelector(`[role="option"]`); - await page.locator(`[role="option"]`).first().click(); - } else { - await page.locator(`input[name="${key}"]`).fill(values[key]); - } - } - - // await waitForRequest('RelatedReportsByAuthor'); - // await waitForRequest('RelatedReportsByURL'); - // await waitForRequest('RelatedReportsByPublishedDate'); - - for (const key of ['byURL', 'byDatePublished']) { - const reports = - key == 'byIncidentId' - ? relatedReports[key].data.incidents[0].reports - : relatedReports[key].data.reports; - - const parentLocator = page.locator(`[data-cy="related-${key}"]`); - - await expect(async () => { - await expect(parentLocator.locator('[data-cy="result"]')).toHaveCount(reports.length); - }).toPass(); + await init(); - for (const report of reports) { - await expect(parentLocator.locator('[data-cy="result"]', { hasText: report.title })).toBeVisible(); - } + await login(config.E2E_ADMIN_USERNAME, config.E2E_ADMIN_PASSWORD, { customData: { first_name: 'Test', last_name: 'User', roles: ['incident_editor'] } }); - } + await page.goto(url + `?editSubmission=6140e4b4b9b4f7b3b3b1b1b1`); - await expect(page.locator(`[data-cy="related-byAuthors"]`).locator('[data-cy="no-related-reports"]')).toHaveText('No related reports found.'); - } - ); - - test.skip('Should show a preliminary checks message', async ({ page }) => { - const relatedReports = { - byURL: { - data: { - reports: [], - }, - }, - byDatePublished: { - data: { - reports: [], - }, - }, - byAuthors: { - data: { reports: [] }, - }, - byIncidentId: { - data: { - incidents: [], - }, - }, - }; - - await conditionalIntercept( - page, - '**/graphql', - (req) => - req.postDataJSON().operationName == 'ProbablyRelatedReports' && - req.postDataJSON().variables.query?.url_in?.[0] == - 'https://www.cnn.com/2021/11/02/homes/zillow-exit-ibuying-home-business/index.html', - relatedReports.byURL, - 'RelatedReportsByURL' - ); + await page.locator('select[data-cy="promote-select"]').selectOption('Issue'); - await conditionalIntercept( - page, - '**/graphql', - (req) => - req.postDataJSON().operationName == 'ProbablyRelatedReports' && - req.postDataJSON().variables.query?.epoch_date_published_gt == 1608346800 && - req.postDataJSON().variables.query?.epoch_date_published_lt == 1610766000, - relatedReports.byDatePublished, - 'RelatedReportsByPublishedDate' - ); + page.on('dialog', dialog => dialog.accept()); - await conditionalIntercept( - page, - '**/graphql', - (req) => - req.postDataJSON().operationName == 'ProbablyRelatedReports' && - req.postDataJSON().variables.query?.authors_in?.[0] == 'test author', - relatedReports.byAuthors, - 'RelatedReportsByAuthor' - ); + await page.locator('[data-cy="promote-button"]').click(); - await conditionalIntercept( - page, - '**/graphql', - (req) => req.postDataJSON().operationName == 'FindSubmissions', - { data: { submissions: [] } }, - 'findSubmissions' - ); + await expect(page.locator('[data-cy="toast"]').first()).toContainText('Successfully promoted submission to Issue 9'); - await page.goto(url); - - await waitForRequest('findSubmissions'); - - const values = { - url: 'https://www.cnn.com/2021/11/02/homes/zillow-exit-ibuying-home-business/index.html', - authors: 'test author', - date_published: '2021-01-02', - incident_ids: '1', - }; - - for (const key in values) { - if (key == 'incident_ids') { - await page.locator(`input[name="${key}"]`).fill(values[key]); - await page.waitForSelector(`[role="option"]`); - await page.locator(`[role="option"]`).first().click(); - } else { - await page.locator(`input[name="${key}"]`).fill(values[key]); + const { data: { reports } } = await query({ + query: gql`{ + reports { + report_number + } } - } - - - await waitForRequest('RelatedReportsByAuthor') - await waitForRequest('RelatedReportsByURL') - await waitForRequest('RelatedReportsByPublishedDate') - - await expect(page.locator('[data-cy="no-related-reports"]').first()).toBeVisible(); + `, + }); - await expect(page.locator('[data-cy="result"]')).not.toBeVisible(); + expect(reports.find((r) => r.report_number === 9)).toBeDefined(); }); - test('Should *not* show semantically related reports when the text is under 256 non-space characters', async ({ page }) => { - - await page.goto(url); - - await setEditorText( - page, - `Recent news stories and blog posts highlighted the underbelly of YouTube Kids, Google's children-friendly version of the wide world of YouTube.` - ); + test('Rejects a submission', async ({ page, login }) => { - await expect(page.locator('[data-cy=related-byText]')).toContainText('Reports must have at least'); - }); - - test('Should *not* show related orphan reports', async ({ page, skipOnEmptyEnvironment }) => { - await page.goto(url); - - const values = { - authors: 'Ashley Belanger', - }; + await init(); - for (const key in values) { - await page.locator(`input[name="${key}"]`).fill(values[key]); - } + const submissions = await getSubmissions(); - await expect(page.locator('[data-cy=related-byAuthors] [data-cy=result] a[data-cy=title]:has-text("Thousands scammed by AI voices mimicking loved ones in emergencies")')) - .not.toBeVisible(); - }); + await login(config.E2E_ADMIN_USERNAME, config.E2E_ADMIN_PASSWORD, { customData: { first_name: 'Test', last_name: 'User', roles: ['incident_editor'] } }); - test.skip('Should show fallback preview image on initial load', async ({ page }) => { - const values = { - url: 'https://incidentdatabase.ai', - title: 'test title', - authors: 'test author', - submitters: 'test submitter', - incident_date: '2022-01-01', - date_published: '2021-01-02', - date_downloaded: '2021-01-03', - incident_id: '1', - }; - - const params = new URLSearchParams(values); - - await conditionalIntercept( - page, - '**/parseNews**', - () => true, - parseNews, - 'parseNews', - ); + await page.goto(url + `?editSubmission=${submissions[0]._id}`); - await page.goto(url + `?${params.toString()}`); - await waitForRequest('parseNews'); + page.on('dialog', dialog => dialog.accept()); - await setEditorText( - page, - `Recent news stories and blog posts highlighted the underbelly of YouTube Kids, Google's children-friendly version of the wide world of YouTube. While all content on YouTube Kids is meant to be suitable for children under the age of 13, some inappropriate videos using animations, cartoons, and child-focused keywords manage to get past YouTube's algorithms and in front of kids' eyes. Now, YouTube will implement a new policy in an attempt to make the whole of YouTube safer: it will age-restrict inappropriate videos masquerading as children's content in the main YouTube app.` - ); + await page.locator('[data-cy="reject-button"]').click(); - await expect(page.locator('.form-has-errors')).not.toBeVisible(); + await page.waitForResponse((response) => response.request()?.postData()?.includes('deleteOneSubmission')); - await page.locator('[data-cy="to-step-2"]').click(); + const updated = await getSubmissions(); - await expect(page.locator('[data-cy="image-preview-figure"] canvas')).toBeVisible(); + expect(updated.find((s) => s._id === submissions[0]._id)).toBeUndefined(); }); - test('Should update preview image when url is typed', async ({ page }) => { - const values = { - url: 'https://incidentdatabase.ai', - title: 'test title', - authors: 'test author', - submitters: 'test submitter', - incident_date: '2022-01-01', - date_published: '2021-01-02', - date_downloaded: '2021-01-03', - incident_id: '1', - }; - - const params = new URLSearchParams(values); - - await conditionalIntercept( - page, - '**/parseNews**', - () => true, - parseNews, - 'parseNews', - ); + test('Edits a submission - update just a text', async ({ page, login }) => { - await page.goto(url + `?${params.toString()}`); + await init(); - await waitForRequest('parseNews'); + const submissions = await getSubmissions(); + await login(config.E2E_ADMIN_USERNAME, config.E2E_ADMIN_PASSWORD, { customData: { first_name: 'Test', last_name: 'User', roles: ['incident_editor'] } }); - const suffix = 'github.com/favicon.ico'; - const newImageUrl = 'https://' + suffix; - const cloudinaryImageUrl = 'https://res.cloudinary.com/pai/image/upload/f_auto/q_auto/v1/reports/' + suffix; + await page.goto(url + `?editSubmission=${submissions[0]._id}`); - await setEditorText( - page, - `Recent news stories and blog posts highlighted the underbelly of YouTube Kids, Google's children-friendly version of the wide world of YouTube. While all content on YouTube Kids is meant to be suitable for children under the age of 13, some inappropriate videos using animations, cartoons, and child-focused keywords manage to get past YouTube's algorithms and in front of kids' eyes. Now, YouTube will implement a new policy in an attempt to make the whole of YouTube safer: it will age-restrict inappropriate videos masquerading as children's content in the main YouTube app.` - ); + const text = '## Another one\n\n**More markdown**\n\nAnother paragraph with more text to reach the minimum character count!'; - await expect(page.locator('.form-has-errors')).not.toBeVisible(); + await setEditorText(page, text); - await page.locator('[data-cy="to-step-2"]').click(); + await page.waitForResponse((response) => response.request()?.postData()?.includes('UpdateSubmission')); - await page.locator('input[name=image_url]').fill(newImageUrl); + const updated = await getSubmissions(); - await expect(page.locator('[data-cy=image-preview-figure] img')).toHaveAttribute('src', cloudinaryImageUrl); + expect(updated.find((s) => s._id === submissions[0]._id).text).toContain(text); }); - test('Should show the editor notes field', async ({ page }) => { - - await page.goto(url); - - const valuesStep1 = { - url: 'https://incidentdatabase.ai', - title: 'test title', - authors: 'test author', - date_published: '2021-01-02', - date_downloaded: '2021-01-03', - incident_date: '2022-01-01', - }; - - for (const key in valuesStep1) { - await page.locator(`input[name="${key}"]`).fill(valuesStep1[key]); - } - - await setEditorText( - page, - 'Sit quo accusantium quia assumenda. Quod delectus similique labore optio quaease' - ); - - await expect(page.locator('.form-has-errors')).not.toBeVisible(); + test('Edits a submission - uses fetch info', async ({ page, login }) => { - await page.locator('[data-cy="to-step-2"]').click(); - - const valuesStep2 = { - submitters: 'test submitter', - image_url: 'https://incidentdatabase.ai/image.jpg', - }; - - for (const key in valuesStep2) { - await page.locator(`input[name="${key}"]`).fill(valuesStep2[key]); - } + await init(); - await page.mouse.click(0, 0); + await login(config.E2E_ADMIN_USERNAME, config.E2E_ADMIN_PASSWORD, { customData: { first_name: 'Test', last_name: 'User', roles: ['incident_editor'] } }); - await page.locator('[data-cy="to-step-3"]').click(); + await page.goto(url + `?editSubmission=6140e4b4b9b4f7b3b3b1b1b1`); - const valuesStep3 = { - editor_notes: 'Here are some notes', - }; + await page.locator('input[name="url"]').fill('https://arstechnica.com/gadgets/2017/11/youtube-to-crack-down-on-inappropriate-content-masked-as-kids-cartoons/'); + await page.click('[data-cy="fetch-info"]'); - for (const key in valuesStep3) { - await page.locator(`textarea[name="${key}"]`).fill(valuesStep3[key]); - } + await page.waitForResponse(response => response.url().includes('/api/parseNews') && response.status() === 200); - await expect(page.locator('[name="editor_notes"]')).toBeVisible(); + await expect(page.locator('input[label="Title"]')).toHaveValue('YouTube to crack down on inappropriate content masked as kids’ cartoons'); }); - test('Should show a popover', async ({ page }) => { - await page.goto(url); + test('Does not allow promotion of submission to Incident if schema is invalid (missing Description)', async ({ page, login }) => { - await page.locator('[data-cy="label-title"]').hover(); + const submissions: DBSubmission[] = [{ + _id: new ObjectId('5d34b8c29ced494f010ed469'), + authors: ["Author 1", "Author 2"], + cloudinary_id: "sample_cloudinary_id", + date_downloaded: "2021-09-14", + date_modified: "2021-09-14T00:00:00.000Z", + date_published: "2021-09-14", + date_submitted: "2021-09-14T00:00:00.000Z", + deployers: ["entity1"], + developers: ["entity2"], + harmed_parties: ["entity3"], + incident_editors: ["editor1"], + image_url: "https://sample_image_url.com", + language: "en", + source_domain: "example.com", + submitters: ["Submitter 1", "Submitter 2"], + tags: ["tag1", "tag2"], + text: "Sample text that must have at least 80 characters, so I will keep writing until I reach the minimum number of characters.", + title: "Sample title", + url: "http://example.com", + user: "user1", + incident_title: "Incident title", + incident_date: "2021-09-14", + editor_notes: "", + }] - await expect(page.locator('[data-cy="popover-title"]')).toBeVisible(); + await init({ aiidprod: { submissions } }); - await expect(page.locator('[data-cy="popover-title"] h5')).toHaveText('Headline'); + await login(config.E2E_ADMIN_USERNAME, config.E2E_ADMIN_PASSWORD, { customData: { first_name: 'Test', last_name: 'User', roles: ['incident_editor'] } }); - await expect(page.locator('[data-cy="popover-title"] div')).toContainText('Most works have a title'); - }); - - test('Should show a translated popover', async ({ page }) => { - await page.goto(`/es/apps/submit/`); - - await page.locator('[data-cy="label-title"]').hover(); + await page.goto(url + `?editSubmission=5d34b8c29ced494f010ed469`); - await expect(page.locator('[data-cy="popover-title"]')).toBeVisible(); + await page.locator('select[data-cy="promote-select"]').selectOption('Incident'); - await expect(page.locator('[data-cy="popover-title"] h5')).toHaveText('Título'); + await page.locator('[data-cy="promote-button"]').click(); - await expect(page.locator('[data-cy="popover-title"] div')).toContainText('La mayoría de los trabajos tienen un'); + await expect(page.locator('[data-cy="toast"]')).toContainText('Description is required'); }); - test('Should work with translated page', async ({ page }) => { - - await conditionalIntercept( - page, - '**/parseNews**', - () => true, - parseNews, - 'parseNews', - ); - - await trackRequest( - page, - '**/graphql', - (req) => req.postDataJSON().operationName == 'FindSubmissions', - 'findSubmissions' - ); - - await page.goto(`/es/apps/submit/`); - - await waitForRequest('findSubmissions'); - - await page.locator('input[name="url"]').fill( - `https://www.arstechnica.com/gadgets/2017/11/youtube-to-crack-down-on-inappropriate-content-masked-as-kids-cartoons/` - ); - - await page.locator('[data-cy="fetch-info"]').click(); - - await waitForRequest('parseNews'); - - await page.locator('input[name="authors"]').fill('Something'); + test('Does not allow promotion of submission to Issue if schema is invalid (missing Title)', async ({ page, login }) => { - await page.locator('[name="incident_date"]').fill('2020-01-01'); + const submissions: DBSubmission[] = [{ + _id: new ObjectId('5d34b8c29ced494f010ed469'), + authors: ["Author 1", "Author 2"], + cloudinary_id: "sample_cloudinary_id", + date_downloaded: "2021-09-14", + date_modified: "2021-09-14T00:00:00.000Z", + date_published: "2021-09-14", + date_submitted: "2021-09-14T00:00:00.000Z", + deployers: ["entity1"], + developers: ["entity2"], + harmed_parties: ["entity3"], + incident_editors: ["editor1"], + image_url: "https://sample_image_url.com", + language: "en", + source_domain: "example.com", + submitters: ["Submitter 1", "Submitter 2"], + tags: ["tag1", "tag2"], + text: "Sample text that must have at least 80 characters, so I will keep writing until I reach the minimum number of characters.", + url: "http://example.com", + user: "user1", + incident_title: "Incident title", + incident_date: "2021-09-14", + editor_notes: "", + description: 'Sarasa', + title: "", + }] - await expect(page.locator('.form-has-errors')).not.toBeVisible(); + await init({ aiidprod: { submissions } }); - await page.locator('[data-cy="to-step-2"]').click(); + await login(config.E2E_ADMIN_USERNAME, config.E2E_ADMIN_PASSWORD, { customData: { first_name: 'Test', last_name: 'User', roles: ['incident_editor'] } }); - await page.locator('input[name="submitters"]').fill('Something'); + await page.goto(url + `?editSubmission=5d34b8c29ced494f010ed469`); - await page.locator('[data-cy="to-step-3"]').click(); + await page.locator('select[data-cy="promote-select"]').selectOption('Issue'); - await page.locator('[name="editor_notes"]').fill('Here are some notes'); + await page.locator('[data-cy="promote-button"]').click(); - await page.locator('button[type="submit"]').click(); - - await expect(page.locator('.tw-toast:has-text("Informe agregado exitosamente a la cola de revisión.")')).toBeVisible(); + await expect(page.locator('[data-cy="toast"]').first()).toContainText('*Title must have at least 6 characters'); }); - test('Should submit on step 1', async ({ page }) => { - - await conditionalIntercept( - page, - '**/parseNews**', - () => true, - parseNews, - 'parseNews', - ); - - await trackRequest( - page, - '**/graphql', - (req) => req.postDataJSON().operationName == 'FindSubmissions', - 'findSubmissions' - ); - - await page.goto(url); - - await waitForRequest('findSubmissions'); + test('Should display an error message if Date Published is not in the past', async ({ page, login }) => { - await page.locator('input[name="url"]').fill( - `https://www.arstechnica.com/gadgets/2017/11/youtube-to-crack-down-on-inappropriate-content-masked-as-kids-cartoons/` - ); - - await page.locator('[data-cy="fetch-info"]').click(); - - await waitForRequest('parseNews'); - - await page.locator('input[name="authors"]').fill('Something'); - - await page.locator('[name="incident_date"]').fill('2020-01-01'); + await init(); - await page.locator('[data-cy="submit-step-1"]').click(); + await login(config.E2E_ADMIN_USERNAME, config.E2E_ADMIN_PASSWORD, { customData: { first_name: 'Test', last_name: 'User', roles: ['incident_editor'] } }); - await expect(page.locator('.tw-toast:has-text("Report successfully added to review queue. You can see your submission")')).toBeVisible(); + await page.goto(url + `?editSubmission=6140e4b4b9b4f7b3b3b1b1b1`); - const keys = ['url', 'title', 'authors', 'incident_date']; + await page.fill('input[name="date_published"]', '3000-01-01'); - for (const key of keys) { - await expect(page.locator(`input[name="${key}"]`)).toHaveValue(''); - } + await expect(page.locator('[data-cy="submission-form"]')).toContainText('Date must be in the past'); }); - test('Should submit on step 2', async ({ page }) => { - - await conditionalIntercept( - page, - '**/parseNews**', - () => true, - parseNews, - 'parseNews' - ); - - await trackRequest( - page, - '**/graphql', - (req) => req.postDataJSON().operationName == 'FindSubmissions', - 'findSubmissions' - ); - - await page.goto(url); - - await waitForRequest('findSubmissions'); - - await page.locator('input[name="url"]').fill( - `https://www.arstechnica.com/gadgets/2017/11/youtube-to-crack-down-on-inappropriate-content-masked-as-kids-cartoons/` - ); - - await page.locator('[data-cy="fetch-info"]').click(); - - await waitForRequest('parseNews'); - - await page.locator('input[name="authors"]').fill('Something'); - - await page.locator('[name="incident_date"]').fill('2020-01-01'); - - await expect(page.locator('.form-has-errors')).not.toBeVisible(); - - await page.locator('[data-cy="to-step-2"]').click(); - - await page.locator('input[name="submitters"]').fill('Something'); - - await page.locator('[data-cy="submit-step-2"]').click(); + test('Should display an error message if Date Downloaded is not in the past', async ({ page, login }) => { - await expect(page.locator('.tw-toast:has-text("Report successfully added to review queue. You can see your submission")')).toBeVisible(); - - const keys = ['url', 'title', 'authors', 'incident_date']; - - for (const key of keys) { - await expect(page.locator(`input[name="${key}"]`)).toHaveValue(''); - } - }); + await init(); - test('Should display an error message if data is missing', async ({ page }) => { + await login(config.E2E_ADMIN_USERNAME, config.E2E_ADMIN_PASSWORD, { customData: { first_name: 'Test', last_name: 'User', roles: ['incident_editor'] } }); - await page.goto(url); + await page.goto(url + `?editSubmission=6140e4b4b9b4f7b3b3b1b1b1`); - await page.locator('button:has-text("Submit")').click(); + await page.fill('input[name="date_downloaded"]', '3000-01-01'); - await expect(page.locator('text=Please review. Some data is missing.')).toBeVisible(); + await expect(page.locator('[data-cy="submission-form"]')).toContainText('Date must be in the past'); }); - test('Should submit a new report response', async ({ page }) => { - const values = { - url: 'https://incidentdatabase.ai', - title: 'test title', - authors: 'test author', - submitters: 'test submitter', - incident_date: '2022-01-01', - date_published: '2021-01-02', - date_downloaded: '2021-01-03', - image_url: 'https://incidentdatabase.ai/image.jpg', - incident_ids: [1], - text: '## Sit quo accusantium \n\n quia **assumenda**. Quod delectus similique labore optio quaease', - tags: 'response', - editor_notes: 'Here are some notes', - }; - - const params = new URLSearchParams(values); - - - await trackRequest( - page, - '**/graphql', - (req) => req.postDataJSON().operationName == 'FindSubmissions', - 'findSubmissions' - ); - - await conditionalIntercept( - page, - '**/parseNews**', - () => true, - parseNews, - 'parseNews' - ); - - await page.goto(url + `?${params.toString()}`); - - await waitForRequest('findSubmissions'); + test('Claims a submission', async ({ page, login }) => { - await waitForRequest('parseNews'); - - await expect(page.locator('[data-cy="submit-form-title"]')).toHaveText('New Incident Response'); - - await expect(page.locator('.form-has-errors')).not.toBeVisible(); - - await page.locator('[data-cy="to-step-2"]').click(); - - await page.locator('[data-cy="to-step-3"]').click(); - - await page.locator('button[type="submit"]').click(); - }); - - test('Should show related reports based on author', async ({ page }) => { + await init(); - await trackRequest( - page, - '**/graphql', - (req) => req.postDataJSON().operationName == 'FindSubmissions', - 'findSubmissions' - ); + const userId = await login(config.E2E_ADMIN_USERNAME, config.E2E_ADMIN_PASSWORD, { customData: { first_name: 'Test', last_name: 'User', roles: ['incident_editor'] } }); await page.goto(url); - await waitForRequest('findSubmissions'); - - const values = { - url: 'https://incidentdatabase.ai', - title: 'test title', - authors: 'BBC News', - incident_date: '2022-01-01', - date_published: '2021-01-02', - date_downloaded: '2021-01-03', - }; - - for (const key in values) { - await page.locator(`[name="${key}"]`).fill(values[key]); - } + await page.click('[data-cy="claim-submission"]'); - await setEditorText(page, 'Sit quo accusantium quia assumenda. Quod delectus similique labore optio quaease'); + await page.waitForResponse((response) => response.request()?.postData()?.includes('UpdateSubmission')); - await expect(page.locator('[data-cy="related-byAuthors"] [data-cy="result"]').first()).toBeVisible(); - await page.locator('[data-cy="related-byAuthors"] [data-cy="result"]').nth(0).locator('[data-cy="unspecified"]').first().click(); - await page.locator('[data-cy="related-byAuthors"] [data-cy="result"]').nth(1).locator('[data-cy="dissimilar"]').first().click(); - await page.locator('[data-cy="related-byAuthors"] [data-cy="result"]').nth(2).locator('[data-cy="similar"]').first().click(); - - await page.locator('button[data-cy="submit-step-1"]').click(); - - // const insertSubmissionRequest = await waitForRequest('insertSubmission'); - // const submissionVariables = insertSubmissionRequest.postDataJSON().variables.submission; - - // expect(submissionVariables).toMatchObject({ - // ...values, - // authors: [values.authors], - // plain_text: 'Sit quo accusantium quia assumenda. Quod delectus similique labore optio quaease\n', - // source_domain: `incidentdatabase.ai`, - // editor_dissimilar_incidents: [2], - // editor_similar_incidents: [3], - // }); - }); - - test('Should hide incident_date, description, deployers, developers & harmed_parties if incident_ids is set', async ({ page }) => { - - await trackRequest( - page, - '**/graphql', - (req) => req.postDataJSON().operationName == 'FindSubmissions', - 'findSubmissions' - ); - - await page.goto(url); - - await waitForRequest('findSubmissions'); - - const valuesStep1 = { - url: 'https://incidentdatabase.ai', - title: 'test title', - authors: 'test author', - date_published: '2021-01-02', - date_downloaded: '2021-01-03', - incident_ids: '1', - }; - - for (const key in valuesStep1) { - if (key == 'incident_ids') { - await page.locator(`input[name="${key}"]`).fill(valuesStep1[key]); - await page.locator(`[role="option"]`).first().click(); - } else { - await page.locator(`input[name="${key}"]`).fill(valuesStep1[key]); + const { data: { submissions } } = await query({ + query: gql`{ + submissions { + _id + incident_editors { + userId + } + } } - } - - await setEditorText(page, 'Sit quo accusantium quia assumenda. Quod delectus similique labore optio quaease'); - - await expect(page.locator('.form-has-errors')).not.toBeVisible(); - - await expect(page.locator('input[name="incident_date"]')).not.toBeVisible(); - - await page.locator('[data-cy="to-step-2"]').click(); - - const valuesStep2 = { - submitters: 'test submitter', - image_url: 'https://incidentdatabase.ai/image.jpg', - }; - - for (const key in valuesStep2) { - await page.locator(`input[name="${key}"]`).fill(valuesStep2[key]); - } - - await page.mouse.click(0, 0); - - await page.locator('[data-cy="to-step-3"]').click(); - - const valuesStep3 = { - editor_notes: 'Here are some notes', - }; - - for (const key in valuesStep3) { - await page.locator(`textarea[name="${key}"]`).fill(valuesStep3[key]); - } + `, + }); - await expect(page.locator('input[name="description"]')).not.toBeVisible(); - await expect(page.locator('input[name="deployers"]')).not.toBeVisible(); - await expect(page.locator('input[name="developers"]')).not.toBeVisible(); - await expect(page.locator('input[name="harmed_parties"]')).not.toBeVisible(); - - await page.locator('button[type="submit"]').click(); - - // const insertSubmissionRequest = await waitForRequest('insertSubmission'); - // const submissionVariables = insertSubmissionRequest.postDataJSON().variables.submission; - - // expect(submissionVariables).toMatchObject({ - // ...valuesStep1, - // ...valuesStep2, - // ...valuesStep3, - // incident_ids: [1], - // authors: [valuesStep1.authors], - // submitters: [valuesStep2.submitters], - // tags: [], - // plain_text: 'Sit quo accusantium quia assumenda. Quod delectus similique labore optio quaease\n', - // source_domain: `incidentdatabase.ai`, - // cloudinary_id: `reports/incidentdatabase.ai/image.jpg`, - // editor_notes: 'Here are some notes', - // }); - - await expect(page.locator('.tw-toast:has-text("Report successfully added to review queue")')).toBeVisible(); - await expect(page.locator('.tw-toast a')).toHaveAttribute('href', '/apps/submitted/'); - await expect(page.locator('text=Please review. Some data is missing.')).not.toBeVisible(); + expect(submissions.find((s) => s._id === '6140e4b4b9b4f7b3b3b1b1b1').incident_editors.map((e) => e.userId)).toContain(userId); }); - test('Should allow two submissions in a row', async ({ page }) => { - - await trackRequest( - page, - '**/graphql', - (req) => req.postDataJSON().operationName == 'FindSubmissions', - 'findSubmissions' - ); - - await page.goto(url); - - await waitForRequest('findSubmissions'); + test('Unclaims a submission', async ({ page, login }) => { + await init(); + const userId = await login(config.E2E_ADMIN_USERNAME, config.E2E_ADMIN_PASSWORD, { customData: { first_name: 'Test', last_name: 'User', roles: ['incident_editor'] } }); + + const submissions: DBSubmission[] = [{ + _id: new ObjectId('63f3d58c26ab981f33b3f9c7'), + authors: ["Author 1", "Author 2"], + cloudinary_id: "sample_cloudinary_id", + date_downloaded: "2021-09-14", + date_modified: "2021-09-14T00:00:00.000Z", + date_published: "2021-09-14", + date_submitted: "2021-09-14T00:00:00.000Z", + deployers: ["entity1"], + developers: ["entity2"], + harmed_parties: ["entity3"], + incident_editors: [userId], + image_url: "https://sample_image_url.com", + language: "en", + source_domain: "example.com", + submitters: ["Submitter 1", "Submitter 2"], + tags: ["tag1", "tag2"], + text: "Sample text that must have at least 80 characters, so I will keep writing until I reach the minimum number of characters.", + url: "http://example.com", + user: "user1", + incident_title: "Incident title", + incident_date: "2021-09-14", + editor_notes: "", + description: 'Sarasa', + title: "Already Claimed", + }] + + await seedCollection({ name: 'submissions', docs: submissions, drop: false }); - async function submitForm() { - - await conditionalIntercept( - page, - '**/parseNews**', - () => true, - parseNews, - 'parseNews', - ); - - await page.locator('input[name="url"]').fill( - `https://www.arstechnica.com/gadgets/2017/11/youtube-to-crack-down-on-inappropriate-content-masked-as-kids-cartoons/` - ); - - await page.locator('[data-cy="fetch-info"]').click(); - - await waitForRequest('parseNews'); - - await page.locator('input[name="authors"]').fill('Something'); + await page.goto(url); - await page.locator('[name="incident_date"]').fill('2020-01-01'); + await page.getByText('Unclaim', { exact: true }).click(); - await page.locator('[data-cy="submit-step-1"]').click(); + await page.waitForResponse((response) => response.request()?.postData()?.includes('UpdateSubmission')); - await expect(page.locator('.tw-toast:has-text("Report successfully added to review queue. You can see your submission")')).toBeVisible(); - } + const { data: { submissions: updated } } = await query({ + query: gql`{ + submissions { + _id + incident_editors { + userId + } + } + } + `, + }); - await submitForm(); - await submitForm(); + expect(updated.find((s) => s._id === '63f3d58c26ab981f33b3f9c7').incident_editors.map((e) => e.userId)).not.toContain(userId); }); - test('Should fetch the news if the url param is in the querystring', async ({ page }) => { - await conditionalIntercept( - page, - '**/parseNews**', - () => true, - parseNews, - 'parseNews', - ); - - await page.goto( - `${url}?url=https%3A%2F%2Fwww.arstechnica.com%2Fgadgets%2F2017%2F11%2Fyoutube-to-crack-down-on-inappropriate-content-masked-as-kids-cartoons%2F` - ); + test('Should maintain current page while claiming', async ({ page, login }) => { - await waitForRequest('parseNews'); + await init(); - await expect(page.locator('.tw-toast:has-text("Please verify all information programmatically pulled from the report")')).toBeVisible(); - }); + const userId = await login(config.E2E_ADMIN_USERNAME, config.E2E_ADMIN_PASSWORD, { customData: { first_name: 'Test', last_name: 'User', roles: ['incident_editor'] } }); + + const submissions: DBSubmission[] = Array.from(Array(10).keys()).map(i => { + + return { + authors: ["Author 1", "Author 2"], + cloudinary_id: "sample_cloudinary_id", + date_downloaded: "2021-09-14", + date_modified: "2021-09-14T00:00:00.000Z", + date_published: "2021-09-14", + date_submitted: "2021-09-14T00:00:00.000Z", + deployers: ["entity1"], + developers: ["entity2"], + harmed_parties: ["entity3"], + incident_editors: [userId], + image_url: "https://sample_image_url.com", + language: "en", + source_domain: "example.com", + submitters: ["Submitter 1", "Submitter 2"], + tags: ["tag1", "tag2"], + text: "Sample text that must have at least 80 characters, so I will keep writing until I reach the minimum number of characters.", + url: "http://example.com", + user: "user1", + incident_title: "Incident title", + incident_date: "2021-09-14", + editor_notes: "", + description: 'Sarasa', + title: "Submission " + i, + } + }) - test('Should load from localstorage', async ({ page, skipOnEmptyEnvironment }) => { - const values = { - url: 'https://incidentdatabase.ai', - authors: ['test author'], - title: 'test title', - date_published: '2021-01-02', - date_downloaded: '2021-01-03', - image_url: 'https://incidentdatabase.ai/image.jpg', - incident_ids: [1], - text: '## Sit quo accusantium \n\n quia **assumenda**. Quod delectus similique labore optio quaease', - submitters: ['test submitters'], - tags: ['test tags'], - source_domain: `incidentdatabase.ai`, - cloudinary_id: `reports/incidentdatabase.ai/image.jpg`, - editor_notes: 'Here are some notes', - }; - - await page.addInitScript(values => { - window.localStorage.setItem('formValues', JSON.stringify(values)); - }, values); + await seedCollection({ name: 'submissions', docs: submissions, drop: false }); await page.goto(url); - await page.locator('[data-cy="submit-step-1"]').click(); - - + await page.click('.pagination button:has-text("Next")'); + await page.click('[data-cy="claim-submission"]'); - // const insertSubmissionRequest = await waitForRequest('insertSubmission'); - // const submissionVariables = insertSubmissionRequest.postDataJSON().variables.submission; - - // expect(submissionVariables).toMatchObject({ - // ...values, - // incident_ids: [1], - // authors: values.authors, - // submitters: values.submitters, - // tags: values.tags, - // plain_text: 'Sit quo accusantium\n\nquia assumenda. Quod delectus similique labore optio quaease\n', - // source_domain: `incidentdatabase.ai`, - // cloudinary_id: `reports/incidentdatabase.ai/image.jpg`, - // editor_notes: 'Here are some notes', - // }); + await expect(page.locator('.pagination [aria-current="page"] button')).toHaveText('2'); }); - test('Should save form data in local storage', async ({ page }) => { + test('Should display "No reports found" if no quick adds are found', async ({ page }) => { - await conditionalIntercept( - page, - '**/graphql', - (req) => req.postDataJSON().operationName == 'FindSubmissions', - { data: { submissions: [] } }, - 'findSubmissions' - ); + await init({ aiidprod: { quickadds: [] } }, { drop: true }); await page.goto(url); - await waitForRequest('findSubmissions'); - - const valuesStep1 = { - url: 'https://incidentdatabase.ai', - title: 'test title', - authors: 'test author', - date_published: '2021-01-02', - date_downloaded: '2021-01-03', - incident_date: '2020-01-01', - }; - - for (const key in valuesStep1) { - await page.locator(`[name="${key}"]`).fill(valuesStep1[key]); - } - - await setEditorText(page, 'Sit quo accusantium quia assumenda. Quod delectus similique labore optio quaease'); - - await page.mouse.click(0, 0); - - await expect(page.locator('.form-has-errors')).not.toBeVisible(); - - await page.locator('[data-cy="to-step-2"]').click(); - - const valuesStep2 = { - submitters: 'test submitter', - image_url: 'https://incidentdatabase.ai/image.jpg', - language: 'en', - }; - - for (const key in valuesStep2) { - key == 'language' - ? await page.locator(`[name="${key}"]`).selectOption({ value: valuesStep2[key] }) - : await page.locator(`[name="${key}"]`).fill(valuesStep2[key]); - } - await page.mouse.click(0, 0); - - await page.locator('[data-cy="to-step-3"]').click(); - - const valuesStep3 = { - developers: 'test developer', - deployers: 'test deployer', - harmed_parties: 'test harmed_parties', - editor_notes: 'Here are some notes', - }; - - for (const key in valuesStep3) { - await page.locator(`[name="${key}"]`).fill(valuesStep3[key]); - await page.mouse.click(0, 0); - } - - expect(async () => { - - const formValues = await page.evaluate(() => { - return JSON.parse(localStorage.getItem('formValues')); - }); - - expect(formValues).toMatchObject({ - ...valuesStep1, - ...valuesStep2, - ...valuesStep3, - authors: [valuesStep1.authors], - submitters: [valuesStep2.submitters], - tags: [], - developers: [valuesStep3.developers], - deployers: [valuesStep3.deployers], - harmed_parties: [valuesStep3.harmed_parties], - nlp_similar_incidents: [], - cloudinary_id: `reports/incidentdatabase.ai/image.jpg`, - text: 'Sit quo accusantium quia assumenda. Quod delectus similique labore optio quaease', - incident_ids: [], - incident_editors: [], - }); - }).toPass(); + await expect(page.locator('[data-cy="no-results"]')).toContainText('No reports found'); }); - test('Should clear form', async ({ page, skipOnEmptyEnvironment }) => { - - const values = { - url: 'https://incidentdatabase.ai', - authors: 'test author', - title: 'test title', - date_published: '2021-01-02', - incident_ids: [1], - }; + test('Should display submission image on edit page', async ({ page, login }) => { - const params = new URLSearchParams(values); - - await page.goto(url + `?${params.toString()}`); - - await page.locator('[data-cy="clear-form"]').click(); + await init(); - for (const key in values) { - await expect(page.locator(`input[name="${key}"]`)).toHaveValue(''); - } - }); + await login(config.E2E_ADMIN_USERNAME, config.E2E_ADMIN_PASSWORD, { customData: { first_name: 'Test', last_name: 'User', roles: ['incident_editor'] } }); - test('Should display an error message if Date Published is not in the past', async ({ page }) => { + await page.goto(url + `?editSubmission=6140e4b4b9b4f7b3b3b1b1b1`); - await trackRequest( - page, - '**/graphql', - (req) => req.postDataJSON().operationName == 'FindSubmissions', - 'findSubmissions' + await expect(page.locator('[data-cy="image-preview-figure"] img')).toHaveAttribute( + 'src', + 'https://res.cloudinary.com/pai/image/upload/f_auto/q_auto/v1/reports/s3.amazonaws.com/ledejs/resized/s2020-pasco-ilp/600/nocco5.jpg' ); - - await page.goto(url); - - await waitForRequest('findSubmissions'); - - await page.locator('input[name="date_published"]').fill('3000-01-01'); - - await page.locator('button:has-text("Submit")').click(); - - await expect(page.locator(':has-text("*Date must be in the past")').first()).toBeVisible(); - }); - - test('Should display an error message if Date Downloaded is not in the past', async ({ page }) => { - await page.goto(url); - - await page.locator('input[name="date_downloaded"]').fill('3000-01-01'); - - await page.locator('button:has-text("Submit")').click(); - - await expect(page.locator('form:has-text("*Date must be in the past")')).toBeVisible(); }); - test('Should fetch article', async ({ page }) => { + test('Should display fallback image on edit modal if submission does not have an image', async ({ page, login }) => { - await trackRequest( - page, - '**/graphql', - (req) => req.postDataJSON().operationName == 'FindSubmissions', - 'findSubmissions' - ); + await init(); - await page.goto(url); + const userId = await login(config.E2E_ADMIN_USERNAME, config.E2E_ADMIN_PASSWORD, { customData: { first_name: 'Test', last_name: 'User', roles: ['incident_editor'] } }); + + const submissions: DBSubmission[] = [{ + _id: new ObjectId('63f3d58c26ab981f33b3f9c7'), + authors: ["Author 1", "Author 2"], + cloudinary_id: "sample_cloudinary_id", + date_downloaded: "2021-09-14", + date_modified: "2021-09-14T00:00:00.000Z", + date_published: "2021-09-14", + date_submitted: "2021-09-14T00:00:00.000Z", + deployers: ["entity1"], + developers: ["entity2"], + harmed_parties: ["entity3"], + incident_editors: [userId], + image_url: "null", + language: "en", + source_domain: "example.com", + submitters: ["Submitter 1", "Submitter 2"], + tags: ["tag1", "tag2"], + text: "Sample text that must have at least 80 characters, so I will keep writing until I reach the minimum number of characters.", + url: "http://example.com", + user: "user1", + incident_title: "Incident title", + incident_date: "2021-09-14", + editor_notes: "", + description: 'Sarasa', + title: "Already Claimed", + }] + + await seedCollection({ name: 'submissions', docs: submissions, drop: false }); + + + await page.goto(url + `?editSubmission=63f3d58c26ab981f33b3f9c7`); - await waitForRequest('findSubmissions'); - await conditionalIntercept( - page, - '**/parseNews**', - () => true, - parseNews, - 'parseNews', - ); + await expect(page.locator('[data-cy="image-preview-figure"] canvas')).toBeVisible(); + }); - await page.locator('input[name="url"]').fill( - `https://www.arstechnica.com/gadgets/2017/11/youtube-to-crack-down-on-inappropriate-content-masked-as-kids-cartoons/` - ); + test('Edits a submission - links to existing incident - Incident Data should be hidden', async ({ page, login }) => { - await page.locator('button:has-text("Fetch info")').click(); + await init(); - await waitForRequest('parseNews'); + await login(config.E2E_ADMIN_USERNAME, config.E2E_ADMIN_PASSWORD, { customData: { first_name: 'Test', last_name: 'User', roles: ['incident_editor'] } }); - await expect(page.locator('.tw-toast:has-text("Please verify all information programmatically pulled from the report")')).toBeVisible(); - await expect(page.locator('.tw-toast:has-text("Error fetching news.")')).not.toBeVisible(); - }); + await page.goto(url + `?editSubmission=6140e4b4b9b4f7b3b3b1b1b1`); - // I'm getting "*something* was blocked" error - test.skip('Should fetch article from site using cookies as fallback', async ({ page }) => { - await page.goto(url); + await page.fill(`input[name="incident_ids"]`, '1'); - await page.locator('input[name="url"]').fill( - 'https://www.washingtonpost.com/technology/2023/02/16/microsoft-bing-ai-chatbot-sydney/' - ); + await page.waitForSelector(`[role="option"]`); - await page.locator('button:has-text("Fetch info")').click(); + await fillAutoComplete(page, '#input-incident_ids', 'inci', 'Incident 1'); - await expect(page.locator('.tw-toast:has-text("Please verify all information programmatically pulled from the report")')).toBeVisible(); - await expect(page.locator('.tw-toast:has-text("Error fetching news.")')).not.toBeVisible(); + await expect(page.locator('[data-cy="incident-data-section"]')).not.toBeVisible(); }); }); \ No newline at end of file diff --git a/site/gatsby-site/playwright/e2e/discover.spec.ts b/site/gatsby-site/playwright/e2e/discover.spec.ts index 98644367e6..a0223c5fca 100644 --- a/site/gatsby-site/playwright/e2e/discover.spec.ts +++ b/site/gatsby-site/playwright/e2e/discover.spec.ts @@ -167,7 +167,8 @@ test.describe('The Discover app', () => { }).toPass(); }); - test('Should flag an incident', async ({ page, skipOnEmptyEnvironment }) => { + // TODO: this test needs to be moved to e2e-full folder + test.skip('Should flag an incident', async ({ page, skipOnEmptyEnvironment }) => { await conditionalIntercept( page, diff --git a/site/gatsby-site/playwright/e2e/incidents/history.spec.ts b/site/gatsby-site/playwright/e2e/incidents/history.spec.ts index 37f2a1ec83..4e8f843491 100644 --- a/site/gatsby-site/playwright/e2e/incidents/history.spec.ts +++ b/site/gatsby-site/playwright/e2e/incidents/history.spec.ts @@ -9,23 +9,6 @@ const { gql } = require('@apollo/client'); test.describe('Incidents', () => { const url = '/incidents/history/?incident_id=10'; - let user; - - test.beforeAll(async () => { - const response = await query({ - query: gql` - { - user(filter: { first_name: { EQ: "Test" }, last_name: { EQ: "User" } }) { - userId - first_name - last_name - } - } - `, - }); - user = response.data.user; - }); - test('Successfully loads', async ({ page }) => { await page.goto(url); }); @@ -129,7 +112,9 @@ test.describe('Incidents', () => { }); test('Should restore an Incident previous version', async ({ page, login }) => { - await login(config.E2E_ADMIN_USERNAME, config.E2E_ADMIN_PASSWORD); + + const userId = await login(config.E2E_ADMIN_USERNAME, config.E2E_ADMIN_PASSWORD); + await page.goto(url); await conditionalIntercept(page, '**/graphql', (req) => req.postDataJSON().operationName == 'FindIncidentHistory', incidentHistory, 'FindIncidentHistory'); @@ -212,7 +197,7 @@ test.describe('Incidents', () => { AllegedDeployerOfAISystem: { link: initialVersion.AllegedDeployerOfAISystem }, AllegedDeveloperOfAISystem: { link: initialVersion.AllegedDeveloperOfAISystem }, AllegedHarmedOrNearlyHarmedParties: { link: initialVersion.AllegedHarmedOrNearlyHarmedParties }, - editors: { link: initialVersion.editors.concat(user.userId) }, + editors: { link: initialVersion.editors.concat(userId) }, }; delete updatedIncident._id; @@ -230,8 +215,8 @@ test.describe('Incidents', () => { ...initialVersion, editor_notes: updatedIncident.editor_notes, epoch_date_modified: updatedIncident.epoch_date_modified, - editors: initialVersion.editors.concat(user.userId), - modifiedBy: user.userId, + editors: initialVersion.editors.concat(userId), + modifiedBy: userId, }; delete expectedIncident._id; @@ -244,7 +229,7 @@ test.describe('Incidents', () => { test('Should display the Version History details modal', async ({ page }) => { - test.slow(); + await page.goto(url); diff --git a/site/gatsby-site/playwright/e2e/integration/cset.spec.ts b/site/gatsby-site/playwright/e2e/integration/cset.spec.ts index 2725b68961..5d0c716693 100644 --- a/site/gatsby-site/playwright/e2e/integration/cset.spec.ts +++ b/site/gatsby-site/playwright/e2e/integration/cset.spec.ts @@ -18,7 +18,7 @@ urls.forEach(({ namespace, url }) => { const fieldListQuery = gql` { - taxa(query: { namespace_in: ["${namespace}"] }) { + taxa(filter: { namespace: {IN: ["${namespace}"] } }) { namespace field_list { long_name diff --git a/site/gatsby-site/playwright/e2e/integrity.spec.ts b/site/gatsby-site/playwright/e2e/integrity.spec.ts index 5b701971a1..f3686184f2 100644 --- a/site/gatsby-site/playwright/e2e/integrity.spec.ts +++ b/site/gatsby-site/playwright/e2e/integrity.spec.ts @@ -78,7 +78,7 @@ test.describe('Integrity', () => { const { data: { classifications } } = await query({ query: gql` query { - classifications(limit: 999999) { + classifications { incidents { incident_id } @@ -94,13 +94,13 @@ test.describe('Integrity', () => { const { data: { classifications, taxas } } = await query({ query: gql` query { - taxas(limit: 999999) { + taxas { namespace field_list { short_name } } - classifications(limit: 999999) { + classifications { namespace attributes { short_name diff --git a/site/gatsby-site/playwright/e2e/reportHistory.spec.ts b/site/gatsby-site/playwright/e2e/reportHistory.spec.ts index fe5377f7c1..5e06f3300e 100644 --- a/site/gatsby-site/playwright/e2e/reportHistory.spec.ts +++ b/site/gatsby-site/playwright/e2e/reportHistory.spec.ts @@ -5,28 +5,10 @@ import updateOneReport from '../fixtures/reports/updateOneReport.json'; import supportedLanguages from '../../src/components/i18n/languages.json'; import { expect } from '@playwright/test'; import config from '../config'; -const { gql } = require('@apollo/client'); test.describe('Report History', () => { const url = '/cite/history?report_number=3206&incident_id=563'; - let user; - - test.beforeAll(async () => { - const { data: { user: userData } } = await query({ - query: gql` - { - user(filter: { first_name: { EQ: "Test" }, last_name: { EQ: "User" } }) { - userId - first_name - last_name - } - } - `, - }); - user = userData; - }); - test('Successfully loads', async ({ page }) => { await page.goto(url); }); @@ -146,7 +128,7 @@ test.describe('Report History', () => { await waitForRequest('FindReportHistory'); - await expect(page).toHaveURL('/cite/history/?report_number=3&incident_id=3'); + await expect(page).toHaveURL('/cite/history/?report_number=376&incident_id=3'); await page.goBack(); @@ -163,7 +145,7 @@ test.describe('Report History', () => { }); test('Should restore a Report previous version', async ({ page, login }) => { - await login(config.E2E_ADMIN_USERNAME, config.E2E_ADMIN_PASSWORD); + const userId = await login(config.E2E_ADMIN_USERNAME, config.E2E_ADMIN_PASSWORD); await page.goto(url); @@ -273,7 +255,7 @@ test.describe('Report History', () => { ...initialVersion, editor_notes: updatedReport.editor_notes, epoch_date_modified: updatedReport.epoch_date_modified, - modifiedBy: user.userId, + modifiedBy: userId, }; delete expectedReport._id; diff --git a/site/gatsby-site/playwright/e2e/socialShareButtons.spec.ts b/site/gatsby-site/playwright/e2e/socialShareButtons.spec.ts deleted file mode 100644 index 0cb05812b9..0000000000 --- a/site/gatsby-site/playwright/e2e/socialShareButtons.spec.ts +++ /dev/null @@ -1,85 +0,0 @@ -import { expect } from '@playwright/test'; -import config from '../config'; -import { test } from '../utils'; - -const incidentId = 10; -const incidentUrl = `/cite/${incidentId}/`; -const blogPostUrl = `/blog/join-raic/`; -const shareButtonsPerSection = 4; -const urlsToTest = [ - { - page: 'Blog Post', - url: blogPostUrl, - title: `Join the Responsible AI Collaborative Founding Staff`, - shareButtonSections: 1, - }, -]; - -if (!config.IS_EMPTY_ENVIRONMENT) { - urlsToTest.push({ - page: 'Incident', - url: incidentUrl, - title: 'Incident 10: Kronos Scheduling Algorithm Allegedly Caused Financial Issues for Starbucks Employees', - shareButtonSections: 1, - }); -} - -urlsToTest.forEach(({ page, url, title, shareButtonSections }) => { - test(`${page} page should have ${shareButtonSections} Social Share button sections`, async ({ page }) => { - await page.goto(url, { waitUntil: 'domcontentloaded' }); - const buttons = page.locator('[data-cy="social-share-buttons"] button'); - await expect(buttons).toHaveCount(shareButtonSections * shareButtonsPerSection); - }); - - const canonicalUrl = `https://incidentdatabase.ai${url}`; - - test(`${page} page should have a Twitter share button`, async ({ page }) => { - await page.goto(url); - const twitterButton = page.locator('[data-cy=btn-share-twitter]'); - await expect(twitterButton).toBeVisible(); - await page.evaluate(() => { - window.open = (url: string) => { window.location.href = url; return null; }; - }); - await twitterButton.first().click(); - await page.waitForURL(`https://x.com/intent/post?text=${encodeURIComponent(title).replace(/%20/g, '+')}&url=${encodeURIComponent(canonicalUrl)}`); - }); - - test(`${page} page should have a LinkedIn share button`, async ({ page }) => { - await page.goto(url); - const linkedInButton = page.locator('[data-cy=btn-share-linkedin]'); - await expect(linkedInButton).toBeVisible(); - await page.evaluate(() => { - window.open = (url: string) => { window.location.href = url; return null; }; - }); - await linkedInButton.first().click(); - await page.waitForURL(/https:\/\/www\.linkedin\.com*/); - }); - - test(`${page} page should have an Email share button`, async ({ page }) => { - await page.goto(url); - const emailButton = page.locator('[data-cy=btn-share-email]'); - await expect(emailButton).toBeTruthy(); - await page.evaluate((url) => { - window.open = (link: string) => { - if (link.startsWith('mailto:')) { - window.location.href = url; - } - return null; - }; - }, canonicalUrl); - await emailButton.first().click(); - await page.waitForURL(canonicalUrl); - - }); - - test(`${page} page should have a Facebook share button`, async ({ page }) => { - await page.goto(url); - const facebookButton = page.locator('[data-cy=btn-share-facebook]'); - await expect(facebookButton).toBeVisible(); - await page.evaluate(() => { - window.open = (url: string) => { window.location.href = url; return null; }; - }); - await facebookButton.first().click(); - await page.waitForURL(/https:\/\/www\.facebook\.com\/(login\.php|sharer\/sharer\.php\?u=)/); - }); -}); diff --git a/site/gatsby-site/playwright/fixtures/checklists/riskSortingChecklist.json b/site/gatsby-site/playwright/fixtures/checklists/riskSortingChecklist.json new file mode 100644 index 0000000000..e9299b1765 --- /dev/null +++ b/site/gatsby-site/playwright/fixtures/checklists/riskSortingChecklist.json @@ -0,0 +1,137 @@ +{ + "data": { + "checklist": { + "__typename": "Checklist", + "about": "", + "date_created": "2024-01-02T23:38:07.875Z", + "date_updated": "2024-01-02T23:38:07.875Z", + "id": "1b4d984c-84c9-4417-a57f-a04acde37c36", + "name": "Unspecified System", + "risks": [ + { + "__typename": "ChecklistRisk", + "generated": false, + "id": "7876ca98-ca8a-4365-8c39-203474c1dc38", + "likelihood": "", + "precedents": [ + { + "__typename": "ChecklistRiskPrecedent", + "description": "The French digital care company, Nabla, in researching GPT-3’s capabilities for medical documentation, diagnosis support, and treatment recommendation, found its inconsistency and lack of scientific and medical expertise unviable and risky in healthcare applications. This incident has been downgraded to an issue as it does not meet current ingestion criteria.", + "incident_id": 287, + "tags": [ + "GMF:Known AI Goal:Question Answering", + "GMF:Known AI Technology:Transformer", + "GMF:Known AI Technology:Language Modeling", + "GMF:Known AI Technology:Distributional Learning", + "GMF:Known AI Technology Classification Discussion:Distributional Learning: If no training data citing sources is available and/or not enough data from a medical domain were available.", + "GMF:Potential AI Technical Failure:Limited Dataset", + "GMF:Potential AI Technical Failure:Problematic Input", + "GMF:Potential AI Technical Failure:Robustness Failure", + "GMF:Potential AI Technical Failure:Overfitting", + "GMF:Potential AI Technical Failure:Underfitting", + "GMF:Potential AI Technical Failure:Inadequate Sequential Memory", + "GMF:Potential AI Technical Failure Classification Discussion:Limited Dataset: If no training data citing sources is available and/or not enough data from a medical domain were available.\n\nProblematic Input: If the prompt does not state that references are required.\n\nOverfitting: System does not capture the semantic content of the prompt, but focuses on specific verbage.\n\nUnderfitting: Due to lack of fine-tuning, the model can be considered as having a poor fit for specific medical questions.", + "GMF:Known AI Technical Failure:Distributional Artifacts" + ], + "title": "OpenAI’s GPT-3 Reported as Unviable in Medical Tasks by Healthcare Firm" + } + ], + "risk_notes": "", + "risk_status": "Mitigated", + "severity": "Minor", + "tags": [ + "GMF:Known AI Technical Failure:Distributional Artifacts" + ], + "title": "Distributional Artifacts", + "touched": false + }, + { + "__typename": "ChecklistRisk", + "generated": false, + "id": "9a1d30d3-b45c-4d7f-a593-8d7dfe78ffb7", + "likelihood": "", + "precedents": [ + { + "__typename": "ChecklistRiskPrecedent", + "description": "Facebook's automatic language translation software incorrectly translated an Arabic post saying \"Good morning\" into Hebrew saying \"hurt them,\" leading to the arrest of a Palestinian man in Beitar Illit, Israel.", + "incident_id": 72, + "tags": [ + "GMF:Known AI Goal:Translation", + "GMF:Known AI Goal Classification Discussion:Translation: Presumably the arabic dialect in the text is not represented adequately in the training data, hence the translation performance issues. \n", + "GMF:Known AI Technical Failure:Dataset Imbalance", + "GMF:Known AI Technical Failure:Distributional Bias", + "GMF:Known AI Technical Failure Classification Discussion:Dataset Imbalance: Presumably the arabic dialect in the text is not represented adequately in the training data, hence the translation performance issues. \n\n\nDistributional Bias: Biased language in Western / Israeli media texts about Arabs could build false associations and high priors to terrorism and violence.", + "GMF:Potential AI Technical Failure:Generalization Failure", + "GMF:Potential AI Technical Failure Classification Discussion:Generalization Failure: Perhaps only one (standard arabic) or a few dialects are supported, and one or more language models is used as fallback for all arabic languages.", + "GMF:Known AI Technology:Convolutional Neural Network", + "GMF:Known AI Technology:Recurrent Neural Network", + "GMF:Known AI Technology:Distributional Learning", + "GMF:Potential AI Technology:Intermediate modeling", + "GMF:Potential AI Technology:Classification", + "GMF:Potential AI Technology:Multimodal Learning", + "GMF:Potential AI Technology:Image Classification", + "GMF:Potential AI Technology Classification Discussion:Intermediate modeling: Perhaps intermmediate languages are used (i.e. if no model has been trained to translate X to Y, use X->Z and then Z->Y), which accumulate errors.\n\nClassification: GIven the amount of supported languages for translation, a system must exist to detect the input language and classify amongst supported languages.\n\nMultimodal Learning: If image was also utilized to generate the translation, that would provide additional evidence to the mistranslation.\n\nImage Classification: If multimodal learning is used, perhaps the buldozer was recognized and its extracted keyword contributed to the bias in the NLP domain." + ], + "title": "Facebook translates 'good morning' into 'attack them', leading to arrest" + } + ], + "risk_notes": "", + "risk_status": "Not Mitigated", + "severity": "Minor", + "tags": [ + "GMF:Known AI Technical Failure:Dataset Imbalance" + ], + "title": "Dataset Imbalance", + "touched": false + }, + { + "__typename": "ChecklistRisk", + "generated": false, + "id": "687fe402-3dad-4630-beef-7e423e64e4fd", + "likelihood": "", + "precedents": [ + { + "__typename": "ChecklistRiskPrecedent", + "description": "Facebook's automatic language translation software incorrectly translated an Arabic post saying \"Good morning\" into Hebrew saying \"hurt them,\" leading to the arrest of a Palestinian man in Beitar Illit, Israel.", + "incident_id": 72, + "tags": [ + "GMF:Known AI Goal:Translation", + "GMF:Known AI Goal Classification Discussion:Translation: Presumably the arabic dialect in the text is not represented adequately in the training data, hence the translation performance issues. \n", + "GMF:Known AI Technical Failure:Dataset Imbalance", + "GMF:Known AI Technical Failure:Distributional Bias", + "GMF:Known AI Technical Failure Classification Discussion:Dataset Imbalance: Presumably the arabic dialect in the text is not represented adequately in the training data, hence the translation performance issues. \n\n\nDistributional Bias: Biased language in Western / Israeli media texts about Arabs could build false associations and high priors to terrorism and violence.", + "GMF:Potential AI Technical Failure:Generalization Failure", + "GMF:Potential AI Technical Failure Classification Discussion:Generalization Failure: Perhaps only one (standard arabic) or a few dialects are supported, and one or more language models is used as fallback for all arabic languages.", + "GMF:Known AI Technology:Convolutional Neural Network", + "GMF:Known AI Technology:Recurrent Neural Network", + "GMF:Known AI Technology:Distributional Learning", + "GMF:Potential AI Technology:Intermediate modeling", + "GMF:Potential AI Technology:Classification", + "GMF:Potential AI Technology:Multimodal Learning", + "GMF:Potential AI Technology:Image Classification", + "GMF:Potential AI Technology Classification Discussion:Intermediate modeling: Perhaps intermmediate languages are used (i.e. if no model has been trained to translate X to Y, use X->Z and then Z->Y), which accumulate errors.\n\nClassification: GIven the amount of supported languages for translation, a system must exist to detect the input language and classify amongst supported languages.\n\nMultimodal Learning: If image was also utilized to generate the translation, that would provide additional evidence to the mistranslation.\n\nImage Classification: If multimodal learning is used, perhaps the buldozer was recognized and its extracted keyword contributed to the bias in the NLP domain." + ], + "title": "Facebook translates 'good morning' into 'attack them', leading to arrest" + } + ], + "risk_notes": "", + "risk_status": "Mitigated", + "severity": "Severe", + "tags": [ + "GMF:Known AI Technical Failure:Distributional Bias" + ], + "title": "Distributional Bias", + "touched": false + } + ], + "tags_goals": [ + "GMF:Known AI Goal:Question Answering" + ], + "tags_methods": [ + "GMF:Potential AI Technology:Classification", + "GMF:Known AI Technology:Language Modeling" + ], + "tags_other": [] + } + } +} diff --git a/site/gatsby-site/playwright/fixtures/checklists/riskSortingRisks.json b/site/gatsby-site/playwright/fixtures/checklists/riskSortingRisks.json new file mode 100644 index 0000000000..7bb6f3732c --- /dev/null +++ b/site/gatsby-site/playwright/fixtures/checklists/riskSortingRisks.json @@ -0,0 +1,415 @@ +{ + "data": { + "risks": [ + { + "__typename": "RisksPayloadItem", + "precedents": [ + { + "__typename": "RisksPayloadPrecedent", + "description": "There are multiple reports of Amazon Alexa products (Echo, Echo Dot) reacting and acting upon unintended stimulus, usually from television commercials or news reporter's voices.", + "incident_id": 34, + "tags": [ + "GMF:Known AI Goal:AI Voice Assistant", + "GMF:Known AI Technology:Automatic Speech Recognition", + "GMF:Known AI Technology:Language Modeling", + "GMF:Known AI Technology:Acoustic Fingerprint", + "GMF:Known AI Technical Failure:Unsafe Exposure or Access", + "GMF:Known AI Technical Failure:Misuse", + "GMF:Potential AI Technical Failure:Unauthorized Data", + "GMF:Potential AI Technical Failure:Inadequate Anonymization", + "GMF:Potential AI Technical Failure:Context Misidentification", + "GMF:Potential AI Technical Failure:Lack of Capability Control", + "GMF:Potential AI Technical Failure:Underspecification", + "GMF:Potential AI Technical Failure Classification Discussion:Unauthorized Data: This may apply on an ethical level: presumably the users sign an agreement consenting to passive voice capture.\n\nInadequate Anonymization: This may apply on an ethical level: presumably the users sign an agreement consenting to passive voice capture.\n\nLack of Capability Control: This is relevant if the order went through.\n\nUnderspecification: Speaker diarization / recognition missing." + ], + "title": "Amazon Alexa Responding to Environmental Inputs" + }, + { + "__typename": "RisksPayloadPrecedent", + "description": "In Taiwan, a Tesla Model 3 on Autopilot mode whose driver did not pay attention to the road collided with a road repair truck; a road engineer immediately placed crash warnings in front of the Tesla, but soon after got hit and was killed by a BMW when its driver failed to see the sign and crashed into the accident.", + "incident_id": 221, + "tags": [ + "GMF:Potential AI Technology:Convolutional Neural Network", + "GMF:Potential AI Technology:Visual Object Detection", + "GMF:Potential AI Technology:Classification", + "GMF:Known AI Technology:Image Segmentation", + "GMF:Potential AI Technology Classification Discussion:Visual Object Detection: Potentially subtask of segmentation.\n\nClassification: Potentially subtask of segmentation.", + "GMF:Known AI Technical Failure:Misuse", + "GMF:Known AI Technical Failure:Generalization Failure", + "GMF:Known AI Technical Failure Classification Discussion:Misuse: Driver should not use autopilot without supervision.", + "GMF:Known AI Goal:Autonomous Driving" + ], + "title": "A Road Engineer Killed Following a Collision Involving a Tesla on Autopilot" + } + ], + "tags": [ + "GMF:Known AI Technical Failure:Misuse" + ], + "title": "Misuse" + }, + { + "__typename": "RisksPayloadItem", + "precedents": [ + { + "__typename": "RisksPayloadPrecedent", + "description": "Facebook's automatic language translation software incorrectly translated an Arabic post saying \"Good morning\" into Hebrew saying \"hurt them,\" leading to the arrest of a Palestinian man in Beitar Illit, Israel.", + "incident_id": 72, + "tags": [ + "GMF:Known AI Goal:Translation", + "GMF:Known AI Goal Classification Discussion:Translation: Presumably the arabic dialect in the text is not represented adequately in the training data, hence the translation performance issues. \n", + "GMF:Known AI Technical Failure:Dataset Imbalance", + "GMF:Known AI Technical Failure:Distributional Bias", + "GMF:Known AI Technical Failure Classification Discussion:Dataset Imbalance: Presumably the arabic dialect in the text is not represented adequately in the training data, hence the translation performance issues. \n\n\nDistributional Bias: Biased language in Western / Israeli media texts about Arabs could build false associations and high priors to terrorism and violence.", + "GMF:Potential AI Technical Failure:Generalization Failure", + "GMF:Potential AI Technical Failure Classification Discussion:Generalization Failure: Perhaps only one (standard arabic) or a few dialects are supported, and one or more language models is used as fallback for all arabic languages.", + "GMF:Known AI Technology:Convolutional Neural Network", + "GMF:Known AI Technology:Recurrent Neural Network", + "GMF:Known AI Technology:Distributional Learning", + "GMF:Potential AI Technology:Intermediate modeling", + "GMF:Potential AI Technology:Classification", + "GMF:Potential AI Technology:Multimodal Learning", + "GMF:Potential AI Technology:Image Classification", + "GMF:Potential AI Technology Classification Discussion:Intermediate modeling: Perhaps intermmediate languages are used (i.e. if no model has been trained to translate X to Y, use X->Z and then Z->Y), which accumulate errors.\n\nClassification: GIven the amount of supported languages for translation, a system must exist to detect the input language and classify amongst supported languages.\n\nMultimodal Learning: If image was also utilized to generate the translation, that would provide additional evidence to the mistranslation.\n\nImage Classification: If multimodal learning is used, perhaps the buldozer was recognized and its extracted keyword contributed to the bias in the NLP domain." + ], + "title": "Facebook translates 'good morning' into 'attack them', leading to arrest" + }, + { + "__typename": "RisksPayloadPrecedent", + "description": "A publicly accessible research model that was trained via Reddit threads showed racially biased advice on moral dilemmas, allegedly demonstrating limitations of language-based models trained on moral judgments.", + "incident_id": 146, + "tags": [ + "GMF:Known AI Goal:Question Answering", + "GMF:Known AI Technology:Distributional Learning", + "GMF:Known AI Technology:Language Modeling", + "GMF:Potential AI Technology:Transformer", + "GMF:Known AI Technical Failure:Distributional Bias", + "GMF:Known AI Technical Failure:Gaming Vulnerability", + "GMF:Potential AI Technical Failure:Overfitting", + "GMF:Potential AI Technical Failure:Robustness Failure", + "GMF:Potential AI Technical Failure:Context Misidentification", + "GMF:Potential AI Technical Failure:Limited Dataset", + "GMF:Potential AI Technical Failure Classification Discussion:Limited Dataset: US ethics only, data sourced from two subreddits and a column." + ], + "title": "Research Prototype AI, Delphi, Reportedly Gave Racially Biased Answers on Ethics" + } + ], + "tags": [ + "GMF:Known AI Technical Failure:Distributional Bias" + ], + "title": "Distributional Bias" + }, + { + "__typename": "RisksPayloadItem", + "precedents": [ + { + "__typename": "RisksPayloadPrecedent", + "description": "YouTube’s content filtering and recommendation algorithms exposed children to disturbing and inappropriate videos.", + "incident_id": 1, + "tags": [ + "GMF:Known AI Goal:Content Recommendation", + "GMF:Known AI Goal:Content Search", + "GMF:Known AI Goal:Hate Speech Detection", + "GMF:Known AI Goal:NSFW Content Detection", + "GMF:Known AI Technology:Content-based Filtering", + "GMF:Known AI Technology:Collaborative Filtering", + "GMF:Potential AI Technology:Classification", + "GMF:Potential AI Technology:Ensemble Aggregation", + "GMF:Potential AI Technology:Distributional Learning", + "GMF:Potential AI Technology Classification Discussion:Classification: Appropriateness could arise by appropriateness classifiers\n\nEnsemble Aggregation: In cases where \"child-appropriateness\" measure arises from multiple marginal detectors of-related subclasses (e.g. violent, adult, political themes)", + "GMF:Potential AI Technical Failure:Concept Drift", + "GMF:Potential AI Technical Failure:Generalization Failure", + "GMF:Potential AI Technical Failure:Misconfigured Aggregation", + "GMF:Potential AI Technical Failure:Distributional Bias", + "GMF:Potential AI Technical Failure:Misaligned Objective", + "GMF:Potential AI Technical Failure Classification Discussion:Concept Drift: Concept drift in cases where appropriateness evolves and changes with the passage of time and is culturally determined -- e.g. akin to old messed up disney cartoons.\n\nGeneralization Failure: Based on huge dataset size.\n\nMisconfigured Aggregation: In cases where \"child-appropriateness\" measure arises from multiple marginal detectors of-related subclasses (e.g. violent, adult, political themes)\n\nMisaligned Objective: Recommendation training is using child-appropriateness in its objective in a diminished capacity (as a component with a small contribution), or not at all (completely relying in post-hoc reviewing and filtering by other systems and humans).", + "GMF:Known AI Technical Failure:Tuning Issues", + "GMF:Known AI Technical Failure:Lack of Adversarial Robustness", + "GMF:Known AI Technical Failure:Adversarial Data", + "GMF:Known AI Technical Failure Classification Discussion:Tuning Issues: Default classification, in cases where the poor consideration of child -appropriateness context information does not fall under current subclasses of this classification." + ], + "title": "Google’s YouTube Kids App Presents Inappropriate Content" + } + ], + "tags": [ + "GMF:Known AI Technical Failure:Tuning Issues" + ], + "title": "Tuning Issues" + }, + { + "__typename": "RisksPayloadItem", + "precedents": [ + { + "__typename": "RisksPayloadPrecedent", + "description": "YouTube’s content filtering and recommendation algorithms exposed children to disturbing and inappropriate videos.", + "incident_id": 1, + "tags": [ + "GMF:Known AI Goal:Content Recommendation", + "GMF:Known AI Goal:Content Search", + "GMF:Known AI Goal:Hate Speech Detection", + "GMF:Known AI Goal:NSFW Content Detection", + "GMF:Known AI Technology:Content-based Filtering", + "GMF:Known AI Technology:Collaborative Filtering", + "GMF:Potential AI Technology:Classification", + "GMF:Potential AI Technology:Ensemble Aggregation", + "GMF:Potential AI Technology:Distributional Learning", + "GMF:Potential AI Technology Classification Discussion:Classification: Appropriateness could arise by appropriateness classifiers\n\nEnsemble Aggregation: In cases where \"child-appropriateness\" measure arises from multiple marginal detectors of-related subclasses (e.g. violent, adult, political themes)", + "GMF:Potential AI Technical Failure:Concept Drift", + "GMF:Potential AI Technical Failure:Generalization Failure", + "GMF:Potential AI Technical Failure:Misconfigured Aggregation", + "GMF:Potential AI Technical Failure:Distributional Bias", + "GMF:Potential AI Technical Failure:Misaligned Objective", + "GMF:Potential AI Technical Failure Classification Discussion:Concept Drift: Concept drift in cases where appropriateness evolves and changes with the passage of time and is culturally determined -- e.g. akin to old messed up disney cartoons.\n\nGeneralization Failure: Based on huge dataset size.\n\nMisconfigured Aggregation: In cases where \"child-appropriateness\" measure arises from multiple marginal detectors of-related subclasses (e.g. violent, adult, political themes)\n\nMisaligned Objective: Recommendation training is using child-appropriateness in its objective in a diminished capacity (as a component with a small contribution), or not at all (completely relying in post-hoc reviewing and filtering by other systems and humans).", + "GMF:Known AI Technical Failure:Tuning Issues", + "GMF:Known AI Technical Failure:Lack of Adversarial Robustness", + "GMF:Known AI Technical Failure:Adversarial Data", + "GMF:Known AI Technical Failure Classification Discussion:Tuning Issues: Default classification, in cases where the poor consideration of child -appropriateness context information does not fall under current subclasses of this classification." + ], + "title": "Google’s YouTube Kids App Presents Inappropriate Content" + } + ], + "tags": [ + "GMF:Known AI Technical Failure:Lack of Adversarial Robustness" + ], + "title": "Lack of Adversarial Robustness" + }, + { + "__typename": "RisksPayloadItem", + "precedents": [ + { + "__typename": "RisksPayloadPrecedent", + "description": "YouTube’s content filtering and recommendation algorithms exposed children to disturbing and inappropriate videos.", + "incident_id": 1, + "tags": [ + "GMF:Known AI Goal:Content Recommendation", + "GMF:Known AI Goal:Content Search", + "GMF:Known AI Goal:Hate Speech Detection", + "GMF:Known AI Goal:NSFW Content Detection", + "GMF:Known AI Technology:Content-based Filtering", + "GMF:Known AI Technology:Collaborative Filtering", + "GMF:Potential AI Technology:Classification", + "GMF:Potential AI Technology:Ensemble Aggregation", + "GMF:Potential AI Technology:Distributional Learning", + "GMF:Potential AI Technology Classification Discussion:Classification: Appropriateness could arise by appropriateness classifiers\n\nEnsemble Aggregation: In cases where \"child-appropriateness\" measure arises from multiple marginal detectors of-related subclasses (e.g. violent, adult, political themes)", + "GMF:Potential AI Technical Failure:Concept Drift", + "GMF:Potential AI Technical Failure:Generalization Failure", + "GMF:Potential AI Technical Failure:Misconfigured Aggregation", + "GMF:Potential AI Technical Failure:Distributional Bias", + "GMF:Potential AI Technical Failure:Misaligned Objective", + "GMF:Potential AI Technical Failure Classification Discussion:Concept Drift: Concept drift in cases where appropriateness evolves and changes with the passage of time and is culturally determined -- e.g. akin to old messed up disney cartoons.\n\nGeneralization Failure: Based on huge dataset size.\n\nMisconfigured Aggregation: In cases where \"child-appropriateness\" measure arises from multiple marginal detectors of-related subclasses (e.g. violent, adult, political themes)\n\nMisaligned Objective: Recommendation training is using child-appropriateness in its objective in a diminished capacity (as a component with a small contribution), or not at all (completely relying in post-hoc reviewing and filtering by other systems and humans).", + "GMF:Known AI Technical Failure:Tuning Issues", + "GMF:Known AI Technical Failure:Lack of Adversarial Robustness", + "GMF:Known AI Technical Failure:Adversarial Data", + "GMF:Known AI Technical Failure Classification Discussion:Tuning Issues: Default classification, in cases where the poor consideration of child -appropriateness context information does not fall under current subclasses of this classification." + ], + "title": "Google’s YouTube Kids App Presents Inappropriate Content" + } + ], + "tags": [ + "GMF:Known AI Technical Failure:Adversarial Data" + ], + "title": "Adversarial Data" + }, + { + "__typename": "RisksPayloadItem", + "precedents": [ + { + "__typename": "RisksPayloadPrecedent", + "description": "There are multiple reports of Amazon Alexa products (Echo, Echo Dot) reacting and acting upon unintended stimulus, usually from television commercials or news reporter's voices.", + "incident_id": 34, + "tags": [ + "GMF:Known AI Goal:AI Voice Assistant", + "GMF:Known AI Technology:Automatic Speech Recognition", + "GMF:Known AI Technology:Language Modeling", + "GMF:Known AI Technology:Acoustic Fingerprint", + "GMF:Known AI Technical Failure:Unsafe Exposure or Access", + "GMF:Known AI Technical Failure:Misuse", + "GMF:Potential AI Technical Failure:Unauthorized Data", + "GMF:Potential AI Technical Failure:Inadequate Anonymization", + "GMF:Potential AI Technical Failure:Context Misidentification", + "GMF:Potential AI Technical Failure:Lack of Capability Control", + "GMF:Potential AI Technical Failure:Underspecification", + "GMF:Potential AI Technical Failure Classification Discussion:Unauthorized Data: This may apply on an ethical level: presumably the users sign an agreement consenting to passive voice capture.\n\nInadequate Anonymization: This may apply on an ethical level: presumably the users sign an agreement consenting to passive voice capture.\n\nLack of Capability Control: This is relevant if the order went through.\n\nUnderspecification: Speaker diarization / recognition missing." + ], + "title": "Amazon Alexa Responding to Environmental Inputs" + } + ], + "tags": [ + "GMF:Known AI Technical Failure:Unsafe Exposure or Access" + ], + "title": "Unsafe Exposure or Access" + }, + { + "__typename": "RisksPayloadItem", + "precedents": [ + { + "__typename": "RisksPayloadPrecedent", + "description": "Facebook's automatic language translation software incorrectly translated an Arabic post saying \"Good morning\" into Hebrew saying \"hurt them,\" leading to the arrest of a Palestinian man in Beitar Illit, Israel.", + "incident_id": 72, + "tags": [ + "GMF:Known AI Goal:Translation", + "GMF:Known AI Goal Classification Discussion:Translation: Presumably the arabic dialect in the text is not represented adequately in the training data, hence the translation performance issues. \n", + "GMF:Known AI Technical Failure:Dataset Imbalance", + "GMF:Known AI Technical Failure:Distributional Bias", + "GMF:Known AI Technical Failure Classification Discussion:Dataset Imbalance: Presumably the arabic dialect in the text is not represented adequately in the training data, hence the translation performance issues. \n\n\nDistributional Bias: Biased language in Western / Israeli media texts about Arabs could build false associations and high priors to terrorism and violence.", + "GMF:Potential AI Technical Failure:Generalization Failure", + "GMF:Potential AI Technical Failure Classification Discussion:Generalization Failure: Perhaps only one (standard arabic) or a few dialects are supported, and one or more language models is used as fallback for all arabic languages.", + "GMF:Known AI Technology:Convolutional Neural Network", + "GMF:Known AI Technology:Recurrent Neural Network", + "GMF:Known AI Technology:Distributional Learning", + "GMF:Potential AI Technology:Intermediate modeling", + "GMF:Potential AI Technology:Classification", + "GMF:Potential AI Technology:Multimodal Learning", + "GMF:Potential AI Technology:Image Classification", + "GMF:Potential AI Technology Classification Discussion:Intermediate modeling: Perhaps intermmediate languages are used (i.e. if no model has been trained to translate X to Y, use X->Z and then Z->Y), which accumulate errors.\n\nClassification: GIven the amount of supported languages for translation, a system must exist to detect the input language and classify amongst supported languages.\n\nMultimodal Learning: If image was also utilized to generate the translation, that would provide additional evidence to the mistranslation.\n\nImage Classification: If multimodal learning is used, perhaps the buldozer was recognized and its extracted keyword contributed to the bias in the NLP domain." + ], + "title": "Facebook translates 'good morning' into 'attack them', leading to arrest" + } + ], + "tags": [ + "GMF:Known AI Technical Failure:Dataset Imbalance" + ], + "title": "Dataset Imbalance" + }, + { + "__typename": "RisksPayloadItem", + "precedents": [ + { + "__typename": "RisksPayloadPrecedent", + "description": "A publicly accessible research model that was trained via Reddit threads showed racially biased advice on moral dilemmas, allegedly demonstrating limitations of language-based models trained on moral judgments.", + "incident_id": 146, + "tags": [ + "GMF:Known AI Goal:Question Answering", + "GMF:Known AI Technology:Distributional Learning", + "GMF:Known AI Technology:Language Modeling", + "GMF:Potential AI Technology:Transformer", + "GMF:Known AI Technical Failure:Distributional Bias", + "GMF:Known AI Technical Failure:Gaming Vulnerability", + "GMF:Potential AI Technical Failure:Overfitting", + "GMF:Potential AI Technical Failure:Robustness Failure", + "GMF:Potential AI Technical Failure:Context Misidentification", + "GMF:Potential AI Technical Failure:Limited Dataset", + "GMF:Potential AI Technical Failure Classification Discussion:Limited Dataset: US ethics only, data sourced from two subreddits and a column." + ], + "title": "Research Prototype AI, Delphi, Reportedly Gave Racially Biased Answers on Ethics" + } + ], + "tags": [ + "GMF:Known AI Technical Failure:Gaming Vulnerability" + ], + "title": "Gaming Vulnerability" + }, + { + "__typename": "RisksPayloadItem", + "precedents": [ + { + "__typename": "RisksPayloadPrecedent", + "description": "Three make-up artists lost their positions following an algorithmically-assessed video interview by HireVue who reportedly failed to provide adequate explanation of the findings.", + "incident_id": 192, + "tags": [ + "GMF:Known AI Goal:Automatic Skill Assessment", + "GMF:Known AI Technology:Automatic Speech Recognition", + "GMF:Potential AI Technology:Regression", + "GMF:Potential AI Technology:Classification", + "GMF:Potential AI Technical Failure:Dataset Imbalance", + "GMF:Potential AI Technical Failure:Context Misidentification", + "GMF:Potential AI Technical Failure:Inadequate Data Sampling", + "GMF:Potential AI Technical Failure:Problematic Input", + "GMF:Known AI Technical Failure:Lack of Explainability", + "GMF:Known AI Technical Failure:Incomplete Data Attribute Capture", + "GMF:Known AI Technical Failure Classification Discussion:Incomplete Data Attribute Capture: Makeup skill assessment with verbal descriptions, rather visual media." + ], + "title": "Three Make-Up Artists Lost Jobs Following Black-Box Automated Decision by HireVue" + } + ], + "tags": [ + "GMF:Known AI Technical Failure:Lack of Explainability" + ], + "title": "Lack of Explainability" + }, + { + "__typename": "RisksPayloadItem", + "precedents": [ + { + "__typename": "RisksPayloadPrecedent", + "description": "Three make-up artists lost their positions following an algorithmically-assessed video interview by HireVue who reportedly failed to provide adequate explanation of the findings.", + "incident_id": 192, + "tags": [ + "GMF:Known AI Goal:Automatic Skill Assessment", + "GMF:Known AI Technology:Automatic Speech Recognition", + "GMF:Potential AI Technology:Regression", + "GMF:Potential AI Technology:Classification", + "GMF:Potential AI Technical Failure:Dataset Imbalance", + "GMF:Potential AI Technical Failure:Context Misidentification", + "GMF:Potential AI Technical Failure:Inadequate Data Sampling", + "GMF:Potential AI Technical Failure:Problematic Input", + "GMF:Known AI Technical Failure:Lack of Explainability", + "GMF:Known AI Technical Failure:Incomplete Data Attribute Capture", + "GMF:Known AI Technical Failure Classification Discussion:Incomplete Data Attribute Capture: Makeup skill assessment with verbal descriptions, rather visual media." + ], + "title": "Three Make-Up Artists Lost Jobs Following Black-Box Automated Decision by HireVue" + } + ], + "tags": [ + "GMF:Known AI Technical Failure:Incomplete Data Attribute Capture" + ], + "title": "Incomplete Data Attribute Capture" + }, + { + "__typename": "RisksPayloadItem", + "precedents": [ + { + "__typename": "RisksPayloadPrecedent", + "description": "In Taiwan, a Tesla Model 3 on Autopilot mode whose driver did not pay attention to the road collided with a road repair truck; a road engineer immediately placed crash warnings in front of the Tesla, but soon after got hit and was killed by a BMW when its driver failed to see the sign and crashed into the accident.", + "incident_id": 221, + "tags": [ + "GMF:Potential AI Technology:Convolutional Neural Network", + "GMF:Potential AI Technology:Visual Object Detection", + "GMF:Potential AI Technology:Classification", + "GMF:Known AI Technology:Image Segmentation", + "GMF:Potential AI Technology Classification Discussion:Visual Object Detection: Potentially subtask of segmentation.\n\nClassification: Potentially subtask of segmentation.", + "GMF:Known AI Technical Failure:Misuse", + "GMF:Known AI Technical Failure:Generalization Failure", + "GMF:Known AI Technical Failure Classification Discussion:Misuse: Driver should not use autopilot without supervision.", + "GMF:Known AI Goal:Autonomous Driving" + ], + "title": "A Road Engineer Killed Following a Collision Involving a Tesla on Autopilot" + } + ], + "tags": [ + "GMF:Known AI Technical Failure:Generalization Failure" + ], + "title": "Generalization Failure" + }, + { + "__typename": "RisksPayloadItem", + "precedents": [ + { + "__typename": "RisksPayloadPrecedent", + "description": "The French digital care company, Nabla, in researching GPT-3’s capabilities for medical documentation, diagnosis support, and treatment recommendation, found its inconsistency and lack of scientific and medical expertise unviable and risky in healthcare applications. This incident has been downgraded to an issue as it does not meet current ingestion criteria.", + "incident_id": 287, + "tags": [ + "GMF:Known AI Goal:Question Answering", + "GMF:Known AI Technology:Transformer", + "GMF:Known AI Technology:Language Modeling", + "GMF:Known AI Technology:Distributional Learning", + "GMF:Known AI Technology Classification Discussion:Distributional Learning: If no training data citing sources is available and/or not enough data from a medical domain were available.", + "GMF:Potential AI Technical Failure:Limited Dataset", + "GMF:Potential AI Technical Failure:Problematic Input", + "GMF:Potential AI Technical Failure:Robustness Failure", + "GMF:Potential AI Technical Failure:Overfitting", + "GMF:Potential AI Technical Failure:Underfitting", + "GMF:Potential AI Technical Failure:Inadequate Sequential Memory", + "GMF:Potential AI Technical Failure Classification Discussion:Limited Dataset: If no training data citing sources is available and/or not enough data from a medical domain were available.\n\nProblematic Input: If the prompt does not state that references are required.\n\nOverfitting: System does not capture the semantic content of the prompt, but focuses on specific verbage.\n\nUnderfitting: Due to lack of fine-tuning, the model can be considered as having a poor fit for specific medical questions.", + "GMF:Known AI Technical Failure:Distributional Artifacts" + ], + "title": "OpenAI’s GPT-3 Reported as Unviable in Medical Tasks by Healthcare Firm" + } + ], + "tags": [ + "GMF:Known AI Technical Failure:Distributional Artifacts" + ], + "title": "Distributional Artifacts" + } + ] + } +} diff --git a/site/gatsby-site/playwright/meta/utils.spec.ts b/site/gatsby-site/playwright/meta/utils.spec.ts new file mode 100644 index 0000000000..2d6f299c7b --- /dev/null +++ b/site/gatsby-site/playwright/meta/utils.spec.ts @@ -0,0 +1,17 @@ +import { expect } from '@playwright/test'; +import { test } from '../utils'; +import config from '../config'; + +test.describe('Test playwright utils', () => { + + test('Login fixture should mock user and roles', async ({ page, login }) => { + + await login(config.E2E_ADMIN_USERNAME, config.E2E_ADMIN_PASSWORD, { customData: { roles: ['sarasa'], first_name: 'Fula', last_name: 'Nito' } }); + + await page.goto('/account'); + + await expect(page.getByText('Fula')).toBeVisible(); + await expect(page.getByText('Nito')).toBeVisible(); + await expect(page.getByText('bue')).toBeVisible(); + }); +}); \ No newline at end of file diff --git a/site/gatsby-site/playwright/seeds/aiidprod/classifications.ts b/site/gatsby-site/playwright/seeds/aiidprod/classifications.ts index 957b23fbef..79795544d7 100644 --- a/site/gatsby-site/playwright/seeds/aiidprod/classifications.ts +++ b/site/gatsby-site/playwright/seeds/aiidprod/classifications.ts @@ -1031,8 +1031,63 @@ const items: DBClassification[] = [ "notes": "", "publish": true, "reports": [], - } + }, + { + namespace: "GMF", + publish: true, + notes: "", + attributes: [ + { + short_name: "Known AI Goal", + value_json: "[\"Question Answering\"]" + }, + { + short_name: "Known AI Goal Snippets", + value_json: "[{\"attributes\":[{\"short_name\":\"Snippet Text\",\"value_json\":\"\\\"You can pose any question you like and be sure to receive an answer, wrapped in the authority of the algorithm rather than the soothsayer.\\\"\"},{\"short_name\":\"Related Classifications\",\"value_json\":\"[\\\"Question Answering\\\"]\"}]}]" + }, + { + short_name: "Known AI Technology", + value_json: "[\"Distributional Learning\",\"Language Modeling\"]" + }, + { + short_name: "Known AI Technology Snippets", + value_json: "[{\"attributes\":[{\"short_name\":\"Snippet Text\",\"value_json\":\"\\\" It has clear biases, telling you that America is “good” and that Somalia is “dangerous”; and it’s amenable to special pleading, noting that eating babies is “okay” as long as you are “really, really hungry.”\\\"\"},{\"short_name\":\"Related Classifications\",\"value_json\":\"[\\\"Distributional Learning\\\"]\"}]},{\"attributes\":[{\"short_name\":\"Snippet Text\",\"value_json\":\"\\\"You can pose any question you like and be sure to receive an answer, wrapped in the authority of the algorithm rather than the soothsayer.\\\"\"},{\"short_name\":\"Related Classifications\",\"value_json\":\"[\\\"Language Modeling\\\"]\"}]}]" + }, + { + short_name: "Potential AI Technology", + value_json: "[\"Transformer\"]" + }, + { + short_name: "Potential AI Technology Snippets", + value_json: "[{\"attributes\":[{\"short_name\":\"Snippet Text\",\"value_json\":\"\\\"You can pose any question you like and be sure to receive an answer, wrapped in the authority of the algorithm rather than the soothsayer.\\\"\"},{\"short_name\":\"Related Classifications\",\"value_json\":\"[\\\"Transformer\\\"]\"}]}]" + }, + { + short_name: "Known AI Technical Failure", + value_json: "[\"Distributional Bias\",\"Gaming Vulnerability\"]" + }, + { + short_name: "Known AI Technical Failure Snippets", + value_json: "[{\"attributes\":[{\"short_name\":\"Snippet Text\",\"value_json\":\"\\\"Ask Delphi is no different in this regard, and its training data incorporates some unusual sources, including a series of one-sentence prompts scraped from two subreddits: r/AmITheAsshole and r/Confessions.\\\"\"},{\"short_name\":\"Related Classifications\",\"value_json\":\"[\\\"Distributional Bias\\\"]\"}]},{\"attributes\":[{\"short_name\":\"Snippet Text\",\"value_json\":\"\\\" It has clear biases, telling you that America is “good” and that Somalia is “dangerous”; and it’s amenable to special pleading, noting that eating babies is “okay” as long as you are “really, really hungry.”\\\"\"},{\"short_name\":\"Related Classifications\",\"value_json\":\"[\\\"Distributional Bias\\\"]\"}]},{\"attributes\":[{\"short_name\":\"Snippet Text\",\"value_json\":\"\\\"Most of Ask Delphi’s judgements, though, aren’t so much ethically wrong as they are obviously influenced by their framing. Even very small changes to how you pose a particular quandary can flip the system’s judgement from condemnation to approval.\\\"\"},{\"short_name\":\"Related Classifications\",\"value_json\":\"[\\\"Gaming Vulnerability\\\"]\"}]}]" + }, + { + short_name: "Potential AI Technical Failure", + value_json: "[\"Overfitting\",\"Robustness Failure\",\"Context Misidentification\",\"Limited Dataset\"]" + }, + { + short_name: "Potential AI Technical Failure Snippets", + value_json: "[{\"attributes\":[{\"short_name\":\"Snippet Text\",\"value_json\":\"\\\"Sometimes it’s obvious how to tip the scales. For example, the AI will tell you that “drunk driving” is wrong but that “having a few beers while driving because it hurts no-one” is a-okay. If you add the phrase “if it makes everyone happy” to the end of your statement, then the AI will smile beneficently on any immoral activity of your choice, up to and including genocide. Similarly, if you add “without apologizing” to the end of many benign descriptions, like “standing still” or “making pancakes,” it will assume you should have apologized and tells you that you’re being rude. Ask Delphi is a creature of context.\\\"\"},{\"short_name\":\"Related Classifications\",\"value_json\":\"[\\\"Overfitting\\\",\\\"Context Misidentification\\\"]\"}]},{\"attributes\":[{\"short_name\":\"Snippet Text\",\"value_json\":\"\\\"Most of Ask Delphi’s judgements, though, aren’t so much ethically wrong as they are obviously influenced by their framing. Even very small changes to how you pose a particular quandary can flip the system’s judgement from condemnation to approval.\\\"\"},{\"short_name\":\"Related Classifications\",\"value_json\":\"[\\\"Overfitting\\\",\\\"Robustness Failure\\\"]\"}]},{\"attributes\":[{\"short_name\":\"Snippet Text\",\"value_json\":\"\\\"Others found the system woefully inconsistent, illogical and offensive. \\\"\"},{\"short_name\":\"Related Classifications\",\"value_json\":\"[\\\"Robustness Failure\\\"]\"}]},{\"attributes\":[{\"short_name\":\"Snippet Text\",\"value_json\":\"\\\"The folks behind the project drew on some eyebrow-raising sources to help train the AI, including the “Am I the Asshole?” subreddit, the “Confessions” subreddit, and the “Dear Abby” advice column, according to the paper the team behind Delphi published about the experiment.\\\"\"},{\"short_name\":\"Related Classifications\",\"value_json\":\"[\\\"Limited Dataset\\\"]\"}]},{\"attributes\":[{\"short_name\":\"Snippet Text\",\"value_json\":\"\\\"Ask Delphi is no different in this regard, and its training data incorporates some unusual sources, including a series of one-sentence prompts scraped from two subreddits: r/AmITheAsshole and r/Confessions.\\\"\"},{\"short_name\":\"Related Classifications\",\"value_json\":\"[\\\"Limited Dataset\\\"]\"}]}]" + }, + { + short_name: "Potential AI Technical Failure Classification Discussion", + value_json: "\"Limited Dataset: US ethics only, data sourced from two subreddits and a column.\"" + } + ], + incidents: [ + 1, + ], + reports: [], + }, ] export default items; \ No newline at end of file diff --git a/site/gatsby-site/playwright/seeds/aiidprod/reports.ts b/site/gatsby-site/playwright/seeds/aiidprod/reports.ts index 1f021bc37b..aa0289fd31 100644 --- a/site/gatsby-site/playwright/seeds/aiidprod/reports.ts +++ b/site/gatsby-site/playwright/seeds/aiidprod/reports.ts @@ -24,7 +24,7 @@ const items: DBReport[] = [ source_domain: "source_domain1", submitters: ["submitter1"], tags: ["tag1"], - url: "url1", + url: "https://report1.com", user: "user1", }, { @@ -51,7 +51,7 @@ const items: DBReport[] = [ source_domain: "source_domain1", submitters: ["submitter1"], tags: ["tag1"], - url: "url1", + url: "https://report2.com", user: "user1", }, { diff --git a/site/gatsby-site/playwright/seeds/aiidprod/submissions.ts b/site/gatsby-site/playwright/seeds/aiidprod/submissions.ts index 07ecdb6b8e..63eacb6f82 100644 --- a/site/gatsby-site/playwright/seeds/aiidprod/submissions.ts +++ b/site/gatsby-site/playwright/seeds/aiidprod/submissions.ts @@ -1,6 +1,7 @@ +import { ObjectId } from 'bson'; import { Submission } from '../../../server/generated/graphql' -type DBSubmission = Omit +export type DBSubmission = Omit & { developers: string[] } & { deployers: string[] } & { harmed_parties: string[] } @@ -9,27 +10,32 @@ type DBSubmission = Omit +export type DBUser = Omit; const users: DBUser[] = [ { diff --git a/site/gatsby-site/playwright/utils.ts b/site/gatsby-site/playwright/utils.ts index e67bfc335f..a08bdeebf4 100644 --- a/site/gatsby-site/playwright/utils.ts +++ b/site/gatsby-site/playwright/utils.ts @@ -194,7 +194,7 @@ export function query(data: QueryOptions) { const { query, variables } = data - return client.query({ query, variables }); + return client.query({ query, variables, fetchPolicy: 'no-cache' }); } const loginSteps = async (page: Page, email: string, password: string) => { @@ -250,7 +250,7 @@ export async function fillAutoComplete(page: Page, selector: string, sequence: s await expect(async () => { await page.locator(selector).clear(); await page.waitForTimeout(1000); - await page.locator(selector).pressSequentially(sequence, { delay: 500 }); - await page.getByText(target).click({ timeout: 1000 }); + await page.locator(selector).pressSequentially(sequence.substring(0, Math.floor(Math.random() * sequence.length) + 1), { delay: 500 }); + await page.getByText(target).first().click({ timeout: 1000 }); }).toPass(); -} +} \ No newline at end of file diff --git a/site/gatsby-site/server/fields/users.ts b/site/gatsby-site/server/fields/users.ts index 2c6761d27d..b9a3178dd6 100644 --- a/site/gatsby-site/server/fields/users.ts +++ b/site/gatsby-site/server/fields/users.ts @@ -1,7 +1,7 @@ import { GraphQLFieldConfigMap } from "graphql"; import { generateMutationFields, generateQueryFields } from "../utils"; import { Context } from "../interfaces"; -import { isAdmin, isRole } from "../rules"; +import { isRole, isSelf } from "../rules"; import { UserType } from "../types/user"; export const queryFields: GraphQLFieldConfigMap = { @@ -17,10 +17,10 @@ export const mutationFields: GraphQLFieldConfigMap = { export const permissions = { Query: { - user: isRole('self'), + user: isSelf(), users: isRole('incident_editor'), //TODO: this needs more work }, Mutation: { - updateOneUser: isRole('self'), + updateOneUser: isSelf(), }, } \ No newline at end of file diff --git a/site/gatsby-site/server/remote.ts b/site/gatsby-site/server/remote.ts index 583834ba85..3ccd6a60f1 100644 --- a/site/gatsby-site/server/remote.ts +++ b/site/gatsby-site/server/remote.ts @@ -47,7 +47,7 @@ const ignoreTypes = [ 'CreateVariantInput', // 'Incident', - 'IncidentQueryInput', + // 'IncidentQueryInput', 'IncidentUpdateInput', 'IncidentInsertInput', 'IncidentEditorsRelationInput', @@ -60,7 +60,7 @@ const ignoreTypes = [ 'EntityInsertInput', // 'User', - 'UserQueryInput', + // 'UserQueryInput', 'UserUpdateInput', 'UserInsertInput', diff --git a/site/gatsby-site/server/rules.ts b/site/gatsby-site/server/rules.ts index 86aaa6abbe..45461df37d 100644 --- a/site/gatsby-site/server/rules.ts +++ b/site/gatsby-site/server/rules.ts @@ -1,5 +1,10 @@ import { rule } from "graphql-shield"; import { Context } from "./interfaces"; +import { UserType } from "./types/user"; +import { getSimplifiedType } from "./utils"; +import { getMongoDbFilter } from "graphql-to-mongodb"; +import { GraphQLFilter } from "graphql-to-mongodb/lib/src/mongoDbFilter"; +import { DBUser } from '../playwright/seeds/customData/users' export const isRole = (role: string) => rule()( async (parent, args, context: Context, info) => { @@ -10,10 +15,32 @@ export const isRole = (role: string) => rule()( const meetsAdmin = user?.roles.includes('admin'); - const meetsSelf = role == 'self' && user?.id === (info.variableValues?.filter as any)?.userId?.EQ; + if (meetsRole || meetsAdmin) { - if (meetsRole || meetsAdmin || meetsSelf) { + return true; + } + + return new Error('not authorized') + }, +) + +export const isSelf = () => rule()( + async (parent, args, context: Context, info) => { + + const collection = context.client.db('customData').collection('users'); + const simpleType = getSimplifiedType(UserType); + const filter = getMongoDbFilter(simpleType, info.variableValues.filter as GraphQLFilter); + const users = await collection.find(filter).toArray(); + + const { user } = context; + + const meetsOwnership = users.every(s => s.userId === user?.id); + + const meetsAdmin = user?.roles.includes('admin'); + + + if (meetsAdmin || meetsOwnership) { return true; } diff --git a/site/gatsby-site/src/components/checklists/CheckListForm.js b/site/gatsby-site/src/components/checklists/CheckListForm.js index c838c1496d..6f5c3687d3 100644 --- a/site/gatsby-site/src/components/checklists/CheckListForm.js +++ b/site/gatsby-site/src/components/checklists/CheckListForm.js @@ -104,7 +104,7 @@ export default function CheckListForm({ error: generatedRisksErrors, } = useQuery( gql` - query { + query FindRisks { risks(input: { tags: [${searchTags.map((t) => `"${t}"`).join(', ')}] }) { tags title diff --git a/site/gatsby-site/src/components/submissions/schemas.js b/site/gatsby-site/src/components/submissions/schemas.js index 28b4500205..000d961ad5 100644 --- a/site/gatsby-site/src/components/submissions/schemas.js +++ b/site/gatsby-site/src/components/submissions/schemas.js @@ -139,7 +139,7 @@ export const schema = yup.object().shape({ message: "Incident Editor can't be longer than 200 characters", }) .nullable(), - editor_notes: yup.string(), + editor_notes: yup.string().nullable(), }); export const incidentSchema = schema.shape({ diff --git a/site/gatsby-site/src/utils/checklists.js b/site/gatsby-site/src/utils/checklists.js index 700d70507b..b8af5d8ef9 100644 --- a/site/gatsby-site/src/utils/checklists.js +++ b/site/gatsby-site/src/utils/checklists.js @@ -251,6 +251,7 @@ const exportCsv = (checklist, generatedRisks) => { const DeleteButton = (props) => (