Skip to content

Commit

Permalink
Merge branch 'staging' into feature/dates-refactor-reports
Browse files Browse the repository at this point in the history
  • Loading branch information
clari182 committed Dec 22, 2023
2 parents c89ee4f + b1a4672 commit 96421b3
Show file tree
Hide file tree
Showing 64 changed files with 4,804 additions and 1,732 deletions.
2 changes: 1 addition & 1 deletion LICENSE.txt
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
AI Incident Database: A program for the archiving of artificial intelligence incidents.

Copyright (C) 2022 Responsible AI Collaborative, Inc.
Copyright (C) 2023 Responsible AI Collaborative, Inc.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
Expand Down
7 changes: 7 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# Artificial Intelligence Incident Database (AIID)

[![Netlify Status](https://api.netlify.com/api/v1/badges/9eb0dda2-916c-46f9-a0bd-9ddab3879c6e/deploy-status)](https://app.netlify.com/sites/aiid/deploys)
[![Slack Link](https://img.shields.io/badge/Join%20the%20RAIC%20Slack!-purple?logo=slack)](https://forms.gle/v7UHJvEkYSJQ7jHj7)

Information about the goals and organization of the AI Incident Database can be found on the [production website](https://incidentdatabase.ai/). This page concentrates on onboarding for the following types of contributions to the database,

Expand Down Expand Up @@ -448,6 +449,12 @@ GATSBY_EXCLUDE_DATASTORE_FROM_BUNDLE=1 # specific to Netlify, for large sites
GATSBY_CPU_COUNT=2 # limits the number of Gatsby threads, helping with deployment stability
NODE_VERSION=18 # this is required by Gatsby v5
NODE_OPTIONS=--max-old-space-size=4096 # increase default heap size to prevent crashes during build
# The following "CLOUDFLARE_R2" variables are required to create the /research/snapshots/ page
CLOUDFLARE_R2_ACCOUNT_ID=[The Cloudflare R2 account ID (e.g.: 8f4144a9d995a9921d0200db59f6a00e)]
CLOUDFLARE_R2_ACCESS_KEY_ID=[The Cloudflare R2 access key ID (e.g.: 7aa73208bc89cee3195879e578b291ee)]
CLOUDFLARE_R2_SECRET_ACCESS_KEY=[The Cloudflare R2 secret access key]
CLOUDFLARE_R2_BUCKET_NAME=[The Cloudflare R2 bucket name (e.g.: 'aiid-public')]
GATSBY_CLOUDFLARE_R2_PUBLIC_BUCKET_URL=[The Cloudflare R2 public bucket URL (e.g.: https://pub-daddb16dc28841779b83690f75eb5c58.r2.dev)]
```
### Github Actions
Two workflows take care of deploying the Realm app to both `production` and `staging` environments, defined in `realm-production.yml` and `realm-staging.yml`. Each workflow looks for environment variables defined in a GitHub Environment named `production` and `staging`.
Expand Down
7 changes: 7 additions & 0 deletions site/gatsby-site/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,13 @@ const config = {
rollbar: {
token: process.env.GATSBY_ROLLBAR_TOKEN,
},
cloudflareR2: {
accountId: process.env.CLOUDFLARE_R2_ACCOUNT_ID,
accessKeyId: process.env.CLOUDFLARE_R2_ACCESS_KEY_ID,
secretAccessKey: process.env.CLOUDFLARE_R2_SECRET_ACCESS_KEY,
bucketName: process.env.CLOUDFLARE_R2_BUCKET_NAME,
publicBucketUrl: process.env.GATSBY_CLOUDFLARE_R2_PUBLIC_BUCKET_URL,
},
};

module.exports = config;
149 changes: 68 additions & 81 deletions site/gatsby-site/cypress/e2e/integration/apps/checklistsForm.cy.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,18 @@ 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`
{
Expand All @@ -19,27 +31,36 @@ describe('Checklists App Form', () => {
timeout: 120000, // mongodb admin api is extremely slow
};

it('Should have read-only access for non-logged-in users', () => {
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: {
__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: [],
},
},
}
{ data: { checklist } }
);
};

const interceptUpsertChecklist = (checklist) => {
cy.conditionalIntercept(
'**/graphql',
(req) => req.body.operationName == 'upsertChecklist',
'upsertChecklist',
{ data: { checklist } }
);
};

it('Should have read-only access for non-logged-in users', () => {
interceptFindChecklist(defaultChecklist);

cy.visit(url);

Expand All @@ -55,26 +76,7 @@ describe('Checklists App Form', () => {
maybeIt('Should have read-only access for logged-in non-owners', () => {
cy.login(Cypress.env('e2eUsername'), Cypress.env('e2ePassword'));

cy.conditionalIntercept(
'**/graphql',
(req) => req.body.operationName == 'findChecklist',
'findChecklist',
{
data: {
checklist: {
__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: [],
},
},
}
);
interceptFindChecklist(defaultChecklist);

cy.visit(url);

Expand All @@ -88,51 +90,13 @@ describe('Checklists App Form', () => {
});

maybeIt('Should allow editing for owner', () => {
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 == 'findChecklist',
'findChecklist',
{
data: {
checklist: {
__typename: 'Checklist',
about: '',
id: 'testChecklist',
name: 'Test Checklist',
owner_id: user.userId,
risks: [],
tags_goals: [],
tags_methods: [],
tags_other: [],
},
},
}
);
cy.conditionalIntercept(
'**/graphql',
(req) => req.body.operationName == 'upsertChecklist',
'upsertChecklist',
{
data: {
checklist: {
__typename: 'Checklist',
about: "It's a system that does something probably.",
id: 'testChecklist',
name: 'Test Checklist',
owner_id: user.userId,
risks: [],
tags_goals: [],
tags_methods: [],
tags_other: [],
},
},
}
);
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);

Expand All @@ -145,4 +109,27 @@ describe('Checklists App Form', () => {
cy.wait(['@upsertChecklist']);
});
});

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);
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,13 @@ describe('Checklists App Index', () => {
cy.get(newChecklistButtonQuery).should('exist');
});

maybeIt('Should show delete buttons only for owned checklists', () => {
/* 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 } }) => {
Expand Down
12 changes: 0 additions & 12 deletions site/gatsby-site/cypress/e2e/integration/apps/submitted.cy.js
Original file line number Diff line number Diff line change
Expand Up @@ -212,18 +212,6 @@ describe('Submitted reports', () => {
expect(variables.subscription.userId.link).to.eq(user.userId);
});

cy.wait('@UpsertSubscriptionPromoted')
.its('request.body.variables')
.then((variables) => {
expect(variables.query.type).to.eq(SUBSCRIPTION_TYPE.submissionPromoted);
expect(variables.query.incident_id.incident_id).to.eq(182);
expect(variables.query.userId.userId).to.eq(submission.user.userId);

expect(variables.subscription.type).to.eq(SUBSCRIPTION_TYPE.submissionPromoted);
expect(variables.subscription.incident_id.link).to.eq(182);
expect(variables.subscription.userId.link).to.eq(submission.user.userId);
});

cy.contains(
'[data-cy="toast"]',
'Successfully promoted submission to Incident 182 and Report 1565'
Expand Down
46 changes: 45 additions & 1 deletion site/gatsby-site/cypress/e2e/integration/incidents.cy.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,52 @@ describe('Incidents Summary', () => {
cy.get('[data-cy="incident-list"] > div')
.should('have.length', incidents.length)
.and('be.visible');
});
}
);

conditionalIt(
!Cypress.env('isEmptyEnvironment'),
'Should sort by Incident ID (ascending and descending)',
() => {
cy.visit(url);

cy.query({
query: gql`
{
incidents(limit: 9999, sortBy: INCIDENT_ID_DESC) {
incident_id
}
}
`,
}).then(({ data: { incidents } }) => {
cy.get('[data-cy="sort-ascending-button"]')
.should('exist')
.and('be.visible')
.and('not.be.disabled');
cy.get('[data-cy="sort-descending-button"]')
.should('exist')
.and('be.visible')
.and('be.disabled');

// could use some more toughly testing here
// Check default Incident ID descending order
cy.get('[data-cy="incident-list"] > div')
.first()
.should('contain', `Incident ${incidents[0].incident_id}`);

// Check Incident ID ascending order
cy.get('[data-cy="sort-ascending-button"]').click();
cy.waitForStableDOM();
cy.get('[data-cy="incident-list"] > div')
.first()
.should('contain', `Incident ${incidents[incidents.length - 1].incident_id}`);

// Check Incident ID descending order
cy.get('[data-cy="sort-descending-button"]').click();
cy.waitForStableDOM();
cy.get('[data-cy="incident-list"] > div')
.first()
.should('contain', `Incident ${incidents[0].incident_id}`);
});
}
);
Expand Down
30 changes: 30 additions & 0 deletions site/gatsby-site/cypress/e2e/integration/snapshots.cy.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { conditionalIt } from '../../support/utils';

describe('The Database Snapshots Page', () => {
const url = '/research/snapshots';

it('Successfully loads', () => {
cy.visit(url);
});

conditionalIt(
!Cypress.env('isEmptyEnvironment'),
'Should display a list of snapshots to download',
() => {
cy.visit(url);

cy.get('[data-cy="snapshots-list"] li')
.should('exist')
.and('be.visible')
.and('have.length.gt', 0);

cy.get('[data-cy="snapshots-list"] li').each((item) => {
expect(item[0].innerText).to.match(
/^\d{4}-\d{2}-\d{2} \d{1,2}:\d{2} (AM|PM) · \d+(\.\d{2})? MB · backup-\d{14}\.tar\.bz2$/
);

expect(item.find('a').attr('href')).to.match(/^https:\/\/.*\/backup-\d{14}\.tar\.bz2$/);
});
}
);
});
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ describe('Social Share buttons on pages', { retries: { runMode: 4 } }, () => {
});
}

urlsToTest.forEach(({ page, url, shareButtonSections }) => {
urlsToTest.forEach(({ page, url, title, shareButtonSections }) => {
it(`${page} page should have ${shareButtonSections} Social Share button sections`, () => {
cy.visit(url);

Expand All @@ -38,8 +38,7 @@ describe('Social Share buttons on pages', { retries: { runMode: 4 } }, () => {
const canonicalUrl = `https://incidentdatabase.ai${url}`;

// Twitter share
// TODO: https://github.com/responsible-ai-collaborative/aiid/issues/2481
it.skip(`${page} page should have a Twitter share button`, () => {
it(`${page} page should have a Twitter share button`, () => {
cy.visit(url);

cy.get('[data-cy=btn-share-twitter]').should('exist');
Expand All @@ -57,9 +56,8 @@ describe('Social Share buttons on pages', { retries: { runMode: 4 } }, () => {
cy.get('@popup_twitter', { timeout: 8000 }).should('be.called');
cy.url().should(
'contain',
`https://twitter.com/i/flow/login?redirect_after_login=%2Fintent%2Ftweet%3Ftext%3D`
`https://twitter.com/intent/tweet?text=${encodeURI(title)}&url=${canonicalUrl}`
);
cy.url().should('contain', `url%3D${encodeURIComponent(canonicalUrl)}`);
});

// LinkedIn share
Expand Down
Loading

0 comments on commit 96421b3

Please sign in to comment.