. Joint key Setup & Setup for API Project(#63)

* working version

* moved jointkey mocks to api

* moving mocks and models to api project

* adding joint key setup to the library webpage
adding calling the backend server async to get the data
adding the async http calls to be available
change build to allow api to be built when the library is tested locally

* Build fixes

* build changes

* Changed jsx setting to get builds to work

* build issue not finding api package

* getting ci/cd to recognize local package

* Cleaned up the tsconfig files to make the api inherit the main one.
Had to move jsx into each project separately

* move storybook dependancies to just the library project

* remove the util file that is not needed in the api project

* Cleaned up mock names

* clean up issues from review

* change order on analysis

* removed extra script

* cleanup scripts

* renamed the env variable to say mock or server

* updating the analysis settings

* cleanup for the code scanning
This commit is contained in:
Steve Maier 2021-09-14 11:53:10 -04:00 коммит произвёл GitHub
Родитель a819bb58be
Коммит 7ce17ceb35
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
113 изменённых файлов: 2205 добавлений и 1405 удалений

Просмотреть файл

@ -6,7 +6,7 @@ Dockerfile
build
node_modules
storybook-static
packages/api/build
packages/api/dist
packages/api/node_modules
packages/library/build
packages/library/node_modules

5
.github/codeql/codeql-config.yml поставляемый Normal file
Просмотреть файл

@ -0,0 +1,5 @@
paths:
- 'packages/**/src'
paths-ignore:
- 'packages/**/build'
- 'packages/**/dist'

63
.github/workflows/codeql-analysis.yml поставляемый
Просмотреть файл

@ -1,40 +1,41 @@
name: "CodeQL"
name: 'CodeQL'
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
schedule:
- cron: '35 6 * * 5'
push:
branches: [main]
pull_request:
branches: [main]
schedule:
- cron: '35 6 * * 5'
jobs:
analyze:
name: Analyze
runs-on: ubuntu-latest
permissions:
actions: read
contents: read
security-events: write
analyze:
name: Analyze
runs-on: ubuntu-latest
permissions:
actions: read
contents: read
security-events: write
strategy:
fail-fast: false
matrix:
language: [ 'javascript' ]
strategy:
fail-fast: false
matrix:
language: ['javascript']
steps:
- name: Checkout repository
uses: actions/checkout@v2
steps:
- name: Checkout repository
uses: actions/checkout@v2
- name: Initialize CodeQL
uses: github/codeql-action/init@v1
with:
languages: ${{ matrix.language }}
- name: Initialize CodeQL
uses: github/codeql-action/init@v1
with:
languages: ${{ matrix.language }}
config-file: ./.github/codeql/codeql-config.yml
- name: Build
run: |
make install
make build
- name: Build
run: |
make install
make build
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v1
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v1

4
.github/workflows/pull_request.yml поставляемый
Просмотреть файл

@ -23,7 +23,7 @@ jobs:
run: make install
- name: Lint
run: make lint
- name: Test
run: make test
- name: Build
run: make build
- name: Test
run: make test

5
.gitignore поставляемый
Просмотреть файл

@ -4,4 +4,7 @@
**/storybook-static/
# ignore mac file
.DS_store
.DS_store
**/dist
**/tsconfig.tsbuildinfo

Просмотреть файл

@ -1,4 +1,4 @@
FROM node:14.10
FROM node:14.15
WORKDIR /app
COPY ./package.json .

Просмотреть файл

@ -1,4 +1,4 @@
FROM node:14.10
FROM node:14.15
WORKDIR /app
COPY ./package.json .

Просмотреть файл

@ -22,6 +22,9 @@ build-storybook:
test:
lerna run test
local:
lerna run local
# Docker
docker-dev-app:
@echo 🐳 Running app in Docker with live reload 🚀

Просмотреть файл

@ -6,7 +6,34 @@
],
"version": "0.1.0",
"devDependencies": {
"lerna": "^4.0.0"
"@babel/core": "^7.13.15",
"@prettier/plugin-xml": "^0.13.1",
"@testing-library/dom": "^7.30.3",
"@testing-library/jest-dom": "^5.11.4",
"@testing-library/react": "^11.1.0",
"@testing-library/user-event": "^12.1.10",
"@typescript-eslint/eslint-plugin": "^4.22.0",
"@typescript-eslint/parser": "^4.22.0",
"babel-loader": "8.1.0",
"eslint": "^7.24.0",
"eslint-config-airbnb-typescript": "^12.3.1",
"eslint-config-prettier": "^8.2.0",
"eslint-plugin-formatjs": "^2.14.10",
"eslint-plugin-import": "^2.22.1",
"eslint-plugin-jest": "^24.3.5",
"eslint-plugin-jsx-a11y": "^6.4.1",
"eslint-plugin-prettier": "^3.3.1",
"eslint-plugin-react": "^7.23.2",
"eslint-plugin-react-hooks": "^4.2.0",
"husky": "^6.0.0",
"import-sort-style-module": "^6.0.0",
"jest": "26.6.0",
"lerna": "^4.0.0",
"prettier": "2.2.1",
"prettier-plugin-import-sort": "^0.0.6",
"pretty-quick": "^3.1.0",
"typescript": "^4.3.5",
"webpack": "4.44.2"
},
"license": "MIT"
}

Просмотреть файл

@ -1,4 +1,7 @@
{
"extends": "../../tsconfig",
"compilerOptions": {
"jsx": "react-jsx"
},
"include": ["src"]
}

Просмотреть файл

@ -1,12 +1,14 @@
{
"name": "@electionguard-ui/api",
"version": "0.1.0",
"main": "index.js",
"license": "MIT",
"devDependencies": {
"typescript": "^4.3.5"
},
"scripts": {
"build": "tsc"
}
"name": "@electionguard-ui/api",
"version": "0.2.0",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"files": [
"/dist"
],
"license": "MIT",
"scripts": {
"build": "tsc -b",
"build-local": "yarn build"
}
}

Просмотреть файл

@ -1,3 +1,30 @@
export default interface Api {
healthCheck: () => boolean;
}
import ElectionRow from './models/ElectionRow';
import User from './models/user';
import AssignedGuardian from './models/assignedGuardian';
import { BaseJointKey, JointKey } from './models/jointKey';
import ManifestPreview from './models/manifestPreview';
import { KeyCeremony, KeyCeremonyGuardian } from './models/keyCeremony';
import KeyCeremonyStep from './models/KeyCeremonyStep';
export default interface ElectionGuardApiClient {
healthCheck: () => boolean;
getUsersWithGuardianRole(): Promise<User[]>;
createJointKey(data: BaseJointKey): Promise<boolean>;
getElections(): ElectionRow[];
getAssignedGuardians(): AssignedGuardian[];
getJointKeys(): JointKey[];
getManifestPreview(): ManifestPreview;
// key ceremony methods
getKeyCeremonyGuardians(): KeyCeremonyGuardian[];
setKeyCeremonyGuardianToStep(
guardian: KeyCeremonyGuardian,
step: KeyCeremonyStep
): KeyCeremonyGuardian;
getKeyCeremonyGuardiansByStep(step: KeyCeremonyStep): KeyCeremonyGuardian[];
getKeyCeremonies(): KeyCeremony[];
createGuardian(id: string, name: string, sequenceOrder: number): void;
}

Просмотреть файл

@ -1,5 +1,33 @@
import Api from "./Api";
import ElectionGuardApiClient from "./Api";
import { getElections as mockGetElections } from './mocks/elections';
import { getJointKeys as mockGetJointKeys, getManifestPreview as mockGetManifestPreview } from './mocks/electionSetup';
import { getUsersWithGuardianRole as mockGetUsersWithGuardianRole } from './mocks/users';
import { getAssignedGuardians as mockGetAssignedGuardians, createGuardian as mockCreateGuardian } from './mocks/guardians';
import { createJointKey as mockCreateJointKey } from './mocks/jointKey';
import { getKeyCeremonies as mockGetKeyCeremonies, getKeyCeremonyGuardians as mockGetKeyCeremonyGuardians, setKeyCeremonyGuardianToStep as mockSetKeyCeremonyGuardianToStep, getKeyCeremonyGuardiansByStep as mockGetKeyCeremonyGuardiansByStep } from './mocks/keyCeremony';
export default class MockApi implements Api {
healthCheck = () => true;
export default class MockApi implements ElectionGuardApiClient {
getElections = mockGetElections;
getUsersWithGuardianRole = mockGetUsersWithGuardianRole;
getAssignedGuardians = mockGetAssignedGuardians;
createJointKey = mockCreateJointKey;
getJointKeys = mockGetJointKeys;
getManifestPreview = mockGetManifestPreview;
getKeyCeremonyGuardians = mockGetKeyCeremonyGuardians;
setKeyCeremonyGuardianToStep = mockSetKeyCeremonyGuardianToStep;
getKeyCeremonyGuardiansByStep = mockGetKeyCeremonyGuardiansByStep;
getKeyCeremonies = mockGetKeyCeremonies;
createGuardian = mockCreateGuardian;
healthCheck = (): boolean => true;
}

Просмотреть файл

@ -0,0 +1,33 @@
import ElectionGuardApiClient from './Api';
import { getUsersWithGuardianRole as serverGetUsersWithGuardianRole } from './server/users'
import { createJointKey as serverCreateJointKey } from './server/jointKey'
import { getElections as serverGetElections } from './server/elections';
import { getAssignedGuardians as serverGetAssignedGuardians, createGuardian as serverCreateGuardian } from './server/guardians'
import { getJointKeys as serverGetJointKeys, getManifestPreview as serverGetManifestPreview } from './server/electionSetup';
import { getKeyCeremonies as serverGetKeyCeremonies, getKeyCeremonyGuardians as serverGetKeyCeremonyGuardians, setKeyCeremonyGuardianToStep as serverSetKeyCeremonyGuardianToStep, getKeyCeremonyGuardiansByStep as serverGetKeyCeremonyGuardiansByStep } from './server/keyCeremony';
export default class ServiceApi implements ElectionGuardApiClient {
getElections = serverGetElections;
getUsersWithGuardianRole = serverGetUsersWithGuardianRole;
getAssignedGuardians = serverGetAssignedGuardians;
createJointKey = serverCreateJointKey;
getJointKeys = serverGetJointKeys;
getManifestPreview = serverGetManifestPreview;
getKeyCeremonyGuardians = serverGetKeyCeremonyGuardians;
setKeyCeremonyGuardianToStep = serverSetKeyCeremonyGuardianToStep;
getKeyCeremonyGuardiansByStep = serverGetKeyCeremonyGuardiansByStep;
getKeyCeremonies = serverGetKeyCeremonies;
createGuardian = serverCreateGuardian;
healthCheck = (): boolean => true;
}

Просмотреть файл

@ -1,2 +1,29 @@
export type { default as Api } from './Api';
export type { default as MockApi } from './MockApi';
import ElectionGuardApiClient from "./Api"
import MockApi from './MockApi';
import ServiceApi from './ServiceApi';
export type { default as ElectionGuardApiClient } from './Api';
export type { default as AssignedGuardian } from './models/assignedGuardian';
export type { BaseJointKey, JointKey } from './models/jointKey';
export type { default as User } from './models/user';
export type { default as ManifestPreview } from './models/manifestPreview';
export { default as TaskStatus } from './models/taskStatus';
export type { KeyCeremony, BackupVerification, KeyCeremonyGuardian } from './models/keyCeremony';
export { KeyCeremonyStatus } from './models/keyCeremony';
export { default as MockApi } from './MockApi';
export { default as ServiceApi } from './ServiceApi';
let data: ElectionGuardApiClient;
export function getApiClient() : ElectionGuardApiClient {
if(!data) {
if (process.env.REACT_APP_MOCK_ENABLED === 'true') {
data = new MockApi();
}
data = new ServiceApi();
}
return data;
}

Просмотреть файл

@ -1,4 +1,4 @@
import JointKey from '../models/jointKey';
import { JointKey } from '../models/jointKey';
import ManifestPreview from '../models/manifestPreview';
import { getAssignedGuardians } from './guardians';

Просмотреть файл

@ -1,6 +1,6 @@
import ElectionRow from '../components/ElectionTable/ElectionRow';
import ElectionRow from '../models/ElectionRow';
const getElections = (): ElectionRow[] => {
export const getElections = (): ElectionRow[] => {
const date = new Date();
const laterDate = new Date();
laterDate.setDate(date.getDate() + 10);

Просмотреть файл

@ -0,0 +1,14 @@
import AssignedGuardian from '../models/assignedGuardian';
export const getAssignedGuardians = (): AssignedGuardian[] => [
{ sequenceOrder: 1, id: '1', name: 'Snow mock' },
{ sequenceOrder: 2, id: '2', name: 'Lannister mock' },
{ sequenceOrder: 3, id: '3', name: 'Magic mock' },
{ sequenceOrder: 4, id: '4', name: 'Stark mock' },
{ sequenceOrder: 5, id: '5', name: 'Targaryen mock' },
];
export const createGuardian = (id: string, name: string, sequenceOrder: number): void => {
}
export default getAssignedGuardians;

Просмотреть файл

@ -0,0 +1,5 @@
import { BaseJointKey } from '../models/jointKey';
export const createJointKey = async (data: BaseJointKey): Promise<boolean> => true;
export default createJointKey;

Просмотреть файл

@ -1,4 +1,4 @@
import KeyCeremonyStep from '../components/KeyCeremonyWizard/KeyCeremonyStep';
import KeyCeremonyStep from '../models/KeyCeremonyStep';
import { KeyCeremony, KeyCeremonyGuardian, KeyCeremonyStatus } from '../models/keyCeremony';
import TaskStatus from '../models/taskStatus';
import { getAssignedGuardians } from './guardians';

Просмотреть файл

@ -0,0 +1,15 @@
import User from '../models/user';
export const getUsersWithGuardianRole = async (): Promise<User[]> => [
{ id: '1', name: 'Snow mock' },
{ id: '2', name: 'Lannister mock' },
{ id: '3', name: 'Magic mock' },
{ id: '4', name: 'Stark mock' },
{ id: '5', name: 'Targaryen mock' },
{ id: '6', name: 'Melisandre mock' },
{ id: '7', name: 'Clifford mock' },
{ id: '8', name: 'Frances mock' },
{ id: '9', name: 'Roxie mock' },
];
export default getUsersWithGuardianRole;

Просмотреть файл

@ -0,0 +1,36 @@
import { ElectionRowData } from './ElectionRowData';
class ElectionRow implements ElectionRowData {
/**
* Row of Election Data
*/
constructor(
id: string,
name: string,
state: string,
jurisdiction: string,
dateCreated: Date,
isNew = false
) {
this.id = id;
this.name = name;
this.state = state;
this.jurisdiction = jurisdiction;
this.dateCreated = dateCreated;
this.isNew = isNew;
}
id: string;
isNew: boolean;
name: string;
state: string;
jurisdiction: string;
dateCreated: Date;
}
export default ElectionRow;

Просмотреть файл

@ -0,0 +1,10 @@
import { GridRowData } from '@material-ui/data-grid';
export interface ElectionRowData extends GridRowData {
id: string;
isNew: boolean;
name: string;
state: string;
jurisdiction: string;
dateCreated: Date;
}

Просмотреть файл

@ -0,0 +1,13 @@
enum KeyCeremonyStep {
Instructions = 0,
MeetGuardians = 1,
CreateKeyPair = 2,
SharePublicKey = 3,
CreateBackups = 4,
ShareBackups = 5,
VerifyBackups = 6,
CombineKeys = 7,
Complete = 8,
}
export default KeyCeremonyStep;

Просмотреть файл

@ -0,0 +1,6 @@
import React, { SVGProps } from 'react';
export default interface Config {
appName: string;
logo: React.ComponentType<SVGProps<SVGSVGElement>>;
}

Просмотреть файл

@ -7,7 +7,8 @@ export interface BaseJointKey {
guardians: AssignedGuardian[];
}
export default interface JointKey extends BaseJointKey {
export interface JointKey extends BaseJointKey {
id: string;
dateCreated: Date;
}

Просмотреть файл

@ -0,0 +1,8 @@
import { Localization } from '@material-ui/core/locale';
export default interface Language {
name: string;
locale: string;
messages: { [key: string]: string };
mui: Localization;
}

Просмотреть файл

Просмотреть файл

Просмотреть файл

@ -0,0 +1,36 @@
import { JointKey } from '../models/jointKey';
import ManifestPreview from '../models/manifestPreview';
import { getAssignedGuardians } from './guardians';
export const getManifestPreview = (): ManifestPreview => {
const endDate = new Date();
endDate.setDate(endDate.getDate() + 2);
return {
name: 'Montgomery County Election',
numberOfContests: 5,
numberOfStyles: 3,
startDate: new Date(),
endDate,
fileHash: '1234lasdf98j3124klajksdflajsdfio',
fileName: 'manifest.json',
};
};
export const getJointKeys = (): JointKey[] => [
{
id: 'joint-key-1',
name: 'Joint Key 1',
numberOfGuardians: 3,
quorum: 2,
guardians: getAssignedGuardians(),
dateCreated: new Date(),
},
{
id: 'joint-key-2',
name: 'Joint Key 2',
numberOfGuardians: 3,
quorum: 2,
guardians: getAssignedGuardians(),
dateCreated: new Date(),
},
];

Просмотреть файл

@ -0,0 +1,16 @@
import ElectionRow from '../models/ElectionRow';
export const getElections = (): ElectionRow[] => {
const date = new Date();
const laterDate = new Date();
laterDate.setDate(date.getDate() + 10);
return [
new ElectionRow('election-1s', 'Election 1 server', 'Maryland', 'Montgomery County', date, true),
new ElectionRow('election-2s', 'Election 2 server', 'Maryland', 'Montgomery County', date),
new ElectionRow('election-3s', 'Election 3 server', 'Maryland', 'Montgomery County', date),
new ElectionRow('election-4s', 'Election 4 server', 'Maryland', 'Montgomery County', laterDate),
new ElectionRow('election-5s', 'Election 5 server', 'Maryland', 'Montgomery County', laterDate),
];
};
export default getElections;

Просмотреть файл

@ -0,0 +1,27 @@
import AssignedGuardian from '../models/assignedGuardian';
import { post } from '../utils/http';
export const getAssignedGuardians = (): AssignedGuardian[] => [
{ sequenceOrder: 1, id: '1', name: 'Snow server' },
{ sequenceOrder: 2, id: '2', name: 'Lannister server' },
{ sequenceOrder: 3, id: '3', name: 'Magic server' },
{ sequenceOrder: 4, id: '4', name: 'Stark server' },
{ sequenceOrder: 5, id: '5', name: 'Targaryen server' },
];
export const createGuardian = async (id: string, username: string, sequenceOrder: number): Promise<string> => {
const data = {
guardian_id: id,
sequence_order: sequenceOrder,
number_of_guardians: 3,
quorum: 2,
name: username,
key_name: ""
};
const path = `${process.env.REACT_APP_GUARDIAN_SERVICE}guardian`;
const response = await post<string>(path, data);
return response.arrayBuffer.toString();
}
export default getAssignedGuardians;

Просмотреть файл

@ -0,0 +1,18 @@
import { BaseJointKey } from '../models/jointKey';
import { put } from '../utils/http';
export const createJointKey = async (data: BaseJointKey): Promise<boolean> => {
const submitData = {
key_name: data.name,
number_of_guardians: data.numberOfGuardians,
quorum: data.quorum,
guardian_ids: data.guardians.map((g) => g.id),
};
const path = `${process.env.REACT_APP_MEDIATOR_SERVICE}key/ceremony`;
const response = await put<string>(path, submitData);
return response.ok;
};
export default createJointKey;

Просмотреть файл

@ -0,0 +1,54 @@
import KeyCeremonyStep from '../models/KeyCeremonyStep';
import { KeyCeremony, KeyCeremonyGuardian, KeyCeremonyStatus } from '../models/keyCeremony';
import TaskStatus from '../models/taskStatus';
import { getAssignedGuardians } from './guardians';
export const getKeyCeremonyGuardians = (): KeyCeremonyGuardian[] =>
getAssignedGuardians().map((guardian) => ({
...guardian,
keypairCreated: TaskStatus.Incomplete,
backupsCreated: TaskStatus.Incomplete,
publicKeyShared: TaskStatus.Incomplete,
backupsShared: TaskStatus.Incomplete,
backupsVerified: TaskStatus.Incomplete,
verifications: [],
}));
export const setKeyCeremonyGuardianToStep = (
guardian: KeyCeremonyGuardian,
step: KeyCeremonyStep
): KeyCeremonyGuardian => ({
...guardian,
keypairCreated:
step > KeyCeremonyStep.CreateKeyPair ? TaskStatus.Complete : TaskStatus.Incomplete,
backupsCreated:
step > KeyCeremonyStep.CreateBackups ? TaskStatus.Complete : TaskStatus.Incomplete,
publicKeyShared:
step > KeyCeremonyStep.SharePublicKey ? TaskStatus.Complete : TaskStatus.Incomplete,
backupsShared:
step > KeyCeremonyStep.ShareBackups ? TaskStatus.Complete : TaskStatus.Incomplete,
backupsVerified:
step > KeyCeremonyStep.VerifyBackups ? TaskStatus.Complete : TaskStatus.Incomplete,
verifications: getAssignedGuardians()
.filter((g) => g.id !== guardian.id)
.map((g) => ({
verifier: guardian,
owner: g,
verified: TaskStatus.Incomplete,
})),
});
export const getKeyCeremonyGuardiansByStep = (step: KeyCeremonyStep): KeyCeremonyGuardian[] =>
getKeyCeremonyGuardians().map((guardian) => setKeyCeremonyGuardianToStep(guardian, step));
export const getKeyCeremonies = (): KeyCeremony[] => [
{
id: 'key-ceremony-1',
status: KeyCeremonyStatus.Active,
name: 'Montgomery County Election',
numberOfGuardians: 5,
quorum: 3,
guardians: getKeyCeremonyGuardians(),
dateCreated: new Date(),
},
];

Просмотреть файл

@ -0,0 +1,19 @@
import User from '../models/user';
import { post } from '../utils/http';
export const getUsersWithGuardianRole = async (): Promise<User[]> => {
const users: User[] = [];
const data = {};
const path = `${process.env.REACT_APP_GUARDIAN_SERVICE}guardian/find?skip=0&limit=100`;
const response = await post<{status:string, message:string, guardians:{guardian_id:string, name:string}[]}>(path, data);
if(typeof response.parsedBody !== "undefined") {
response.parsedBody.guardians.forEach((item) => {
users.push({id: item.guardian_id, name: item.name});
});
}
return users;
};
export default getUsersWithGuardianRole;

Просмотреть файл

@ -0,0 +1,5 @@
type EnumDictionary<T extends string | symbol | number, U> = {
[K in T]: U;
};
export default EnumDictionary;

Просмотреть файл

@ -0,0 +1,3 @@
const delay = (ms: number): Promise<unknown> => new Promise((res) => setTimeout(res, ms));
export default delay;

Просмотреть файл

@ -0,0 +1,45 @@
interface HttpResponse<T> extends Response {
parsedBody?: T;
}
async function http<T>(
request: RequestInfo
): Promise<HttpResponse<T>> {
const response: HttpResponse<T> = await fetch(
request
);
try {
// may error if there is no body
response.parsedBody = await response.json();
} catch (ex) {}
if (!response.ok) {
throw new Error(response.statusText);
}
return response;
}
export async function get<T>(
path: string,
args: RequestInit = { method: "get" }
): Promise<HttpResponse<T>> {
return await http<T>(new Request(path, args));
};
export async function post<T>(
path: string,
body: any,
args: RequestInit = { method: "post", body: JSON.stringify(body) }
): Promise<HttpResponse<T>> {
return await http<T>(new Request(path, args));
};
export async function put<T>(
path: string,
body: any,
args: RequestInit = { method: "put", body: JSON.stringify(body) }
): Promise<HttpResponse<T>> {
return await http<T>(new Request(path, args));
};

Просмотреть файл

@ -2,7 +2,14 @@
"extends": "../../tsconfig",
"compilerOptions": {
"declaration": true,
"outDir": "./build"
"rootDir": "./src",
"outDir": "./dist",
"lib": ["dom", "ESNext"],
"declarationMap": true,
"sourceMap": true,
"composite": false,
"jsx": "react",
"noEmit": false
},
"include": ["src"],
"exclude": ["node_modules", "**/__tests__/*"]

Просмотреть файл

@ -0,0 +1,3 @@
REACT_APP_MEDIATOR_SERVICE=http://localhost:8000/api/v1/
REACT_APP_GUARDIAN_SERVICE=http://localhost:8001/api/v1/
REACT_APP_MOCK_ENABLED=false

Просмотреть файл

@ -0,0 +1,3 @@
REACT_APP_MEDIATOR_SERVICE=http://localhost:8000/api/v1/
REACT_APP_GUARDIAN_SERVICE=http://localhost:8001/api/v1/
REACT_APP_DATA=MOCK

Просмотреть файл

@ -0,0 +1,3 @@
REACT_APP_MEDIATOR_SERVICE=http://localhost:8000/api/v1/
REACT_APP_GUARDIAN_SERVICE=http://localhost:8001/api/v1/
REACT_APP_MOCK_ENABLED=false

Просмотреть файл

@ -2,20 +2,36 @@
"name": "@electionguard-ui/library",
"version": "0.0.0",
"dependencies": {
"@babel/core": "^7.13.15",
"@electionguard-ui/api": "0.2.0",
"@material-ui/core": "^4.11.3",
"@material-ui/data-grid": "^4.0.0-alpha.29",
"@material-ui/icons": "^4.11.2",
"@material-ui/lab": "^4.0.0-alpha.58",
"@storybook/addon-actions": "^6.2.7",
"@storybook/addon-essentials": "^6.2.7",
"@storybook/addon-links": "^6.2.7",
"@storybook/cli": "^6.2.7",
"@storybook/node-logger": "^6.2.7",
"@storybook/preset-create-react-app": "^3.1.7",
"@storybook/react": "^6.2.7",
"@types/jest": "^26.0.15",
"@types/node": "^12.0.0",
"@types/react": "^17.0.2",
"@types/react-dom": "^17.0.2",
"@types/react-intl": "^3.0.0",
"@types/react-router-dom": "^5.1.7",
"@typescript-eslint/eslint-plugin": "^4.22.0",
"@typescript-eslint/parser": "^4.22.0",
"clsx": "^1.1.1",
"eslint-plugin-formatjs": "^2.14.10",
"eslint-plugin-import": "^2.22.1",
"eslint-plugin-prettier": "^3.3.1",
"eslint-plugin-react": "^7.23.2",
"react": "^17.0.2",
"react-dom": "^17.0.2",
"react-intl": "^5.17.4",
"react-query": "^3.23.0",
"react-router-dom": "^5.2.0",
"react-scripts": "4.0.3",
"typescript": "^4.3.5",
@ -25,6 +41,7 @@
"trim": "^0.0.3"
},
"scripts": {
"local": "react-scripts start",
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
@ -61,41 +78,6 @@
"last 1 safari version"
]
},
"devDependencies": {
"@babel/core": "^7.13.15",
"@prettier/plugin-xml": "^0.13.1",
"@storybook/addon-actions": "^6.2.7",
"@storybook/addon-essentials": "^6.2.7",
"@storybook/addon-links": "^6.2.7",
"@storybook/cli": "^6.2.7",
"@storybook/node-logger": "^6.2.7",
"@storybook/preset-create-react-app": "^3.1.7",
"@storybook/react": "^6.2.7",
"@testing-library/dom": "^7.30.3",
"@testing-library/jest-dom": "^5.11.4",
"@testing-library/react": "^11.1.0",
"@testing-library/user-event": "^12.1.10",
"@typescript-eslint/eslint-plugin": "^4.22.0",
"@typescript-eslint/parser": "^4.22.0",
"babel-loader": "8.1.0",
"eslint": "^7.24.0",
"eslint-config-airbnb-typescript": "^12.3.1",
"eslint-config-prettier": "^8.2.0",
"eslint-plugin-formatjs": "^2.14.10",
"eslint-plugin-import": "^2.22.1",
"eslint-plugin-jest": "^24.3.5",
"eslint-plugin-jsx-a11y": "^6.4.1",
"eslint-plugin-prettier": "^3.3.1",
"eslint-plugin-react": "^7.23.2",
"eslint-plugin-react-hooks": "^4.2.0",
"husky": "^6.0.0",
"import-sort-style-module": "^6.0.0",
"jest": "26.6.0",
"prettier": "2.2.1",
"prettier-plugin-import-sort": "^0.0.6",
"pretty-quick": "^3.1.0",
"webpack": "4.44.2"
},
"husky": {
"hooks": {
"pre-commit": "pretty-quick --staged --pattern '{src,public}/**/*.{js,jsx,ts,tsx,css,html}'"

Просмотреть файл

@ -1,7 +1,7 @@
import { getApiClient } from '@electionguard-ui/api';
import { Meta, Story } from '@storybook/react';
import React from 'react';
import { getUsersWithGuardianRole } from '../../mocks/users';
import AssignmentTable, { AssignmentTableProps } from './AssignmentTable';
export default {
@ -11,9 +11,12 @@ export default {
} as Meta;
const Template: Story<AssignmentTableProps> = (props) => <AssignmentTable {...props} />;
const service = getApiClient();
export const Standard = Template.bind({});
Standard.storyName = 'Standard';
const userData = await service.getUsersWithGuardianRole();
Standard.args = {
data: getUsersWithGuardianRole(),
data: userData,
};

Просмотреть файл

@ -1,8 +1,8 @@
import { User } from '@electionguard-ui/api';
import { Box } from '@material-ui/core';
import { DataGrid, GridColDef, GridRowId } from '@material-ui/data-grid';
import * as React from 'react';
import User from '../../models/user';
import FilterToolbar from '../FilterToolbar';
export interface AssignmentTableProps {
@ -17,6 +17,7 @@ const columns: GridColDef[] = [
const AssignmentTable: React.FC<AssignmentTableProps> = ({ data, onChanged }) => {
const [selectionModel, setSelectionModel] = React.useState<GridRowId[]>([]);
const onSelectionChange = (rows: GridRowId[]) => {
setSelectionModel(rows);
onChanged(rows.map((rowId: GridRowId) => rowId.toString()));

Просмотреть файл

@ -0,0 +1,39 @@
import React from 'react';
import { AsyncResult } from '../../data/AsyncResult';
// import InternationalText from '../InternationalText';
// import { Spinner, SpinnerSize, StackItem } from '@fluentui/react';
export interface AsyncContentProps<T> {
children: (data: T) => React.ReactElement;
errorMessage?: string;
query: AsyncResult<T>;
}
function AsyncContent<T>({
children,
errorMessage = 'Something went wrong!',
query,
}: AsyncContentProps<T>): React.ReactElement {
const { data, isLoading, isError } = query;
if (isLoading) {
return (
<>
<p>Loading</p>
</>
);
}
if (isError || data === undefined) {
return (
<>
<p>Error {errorMessage}</p>
</>
);
}
return <>{children(data)}</>;
}
export default AsyncContent;

Просмотреть файл

@ -0,0 +1,5 @@
import AsyncContent from './AsyncContent';
export type { AsyncContentProps } from './AsyncContent';
export default AsyncContent;

Просмотреть файл

@ -1,10 +1,10 @@
import { TaskStatus } from '@electionguard-ui/api';
import { Chip } from '@material-ui/core';
import { GridCellParams } from '@material-ui/data-grid';
import React, { ReactElement } from 'react';
import { IntlShape } from 'react-intl';
import { Message } from '../../lang';
import TaskStatus from '../../models/taskStatus';
import { getColor } from '../../theme';
import FormattedButton from '../FormattedButton';
import GuardianIcon from '../GuardianIcon';
@ -19,7 +19,8 @@ export const NewCell = (params: GridCellParams): ReactElement => {
export const FormattedDateCell = (params: GridCellParams, intl: IntlShape): ReactElement => {
const { value } = params;
return <>{intl.formatDate(value?.toString())}</>;
const { formatDate } = intl;
return <>{formatDate(value?.toString())}</>;
};
export const GuardianIconCell = (params: GridCellParams): ReactElement => {

Просмотреть файл

@ -1,7 +1,7 @@
import { getApiClient } from '@electionguard-ui/api';
import { Meta, Story } from '@storybook/react';
import React from 'react';
import { getJointKeys } from '../../mocks/electionSetup';
import ElectionSetupWizard, { ElectionSetupWizardProps } from './ElectionSetupWizard';
export default {
@ -12,8 +12,9 @@ export default {
const Template: Story<ElectionSetupWizardProps> = (props) => <ElectionSetupWizard {...props} />;
const service = getApiClient();
export const Standard = Template.bind({});
Standard.storyName = 'Standard';
Standard.args = {
keys: getJointKeys(),
keys: service.getJointKeys(),
};

Просмотреть файл

@ -1,8 +1,7 @@
import { JointKey, getApiClient } from '@electionguard-ui/api';
import { Box } from '@material-ui/core';
import React, { useState } from 'react';
import { getManifestPreview } from '../../mocks/electionSetup';
import JointKey from '../../models/jointKey';
import { createEnumStepper } from '../../utils/EnumStepper';
import WizardStep from '../WizardStep';
import {
@ -37,6 +36,7 @@ const ElectionSetupWizard: React.FC<ElectionSetupWizardProps> = ({ keys }) => {
const [step, setStep] = useState(ElectionSetupStep.Instructions);
const { nextStep } = createEnumStepper(ElectionSetupStep);
const next = () => setStep(nextStep(step));
const service = getApiClient();
return (
<Box height="100%">
<WizardStep active={step === ElectionSetupStep.Instructions}>
@ -63,7 +63,7 @@ const ElectionSetupWizard: React.FC<ElectionSetupWizardProps> = ({ keys }) => {
<ManifestPreviewStep
onNext={next}
backToMenu={() => setStep(ElectionSetupStep.ManifestMenu)}
preview={getManifestPreview()}
preview={service.getManifestPreview()}
/>
</WizardStep>
<WizardStep active={step === ElectionSetupStep.SetupComplete}>

Просмотреть файл

@ -1,7 +1,7 @@
import { getApiClient } from '@electionguard-ui/api';
import { Meta, Story } from '@storybook/react';
import React from 'react';
import { getJointKeys } from '../../../mocks/electionSetup';
import JointKeySelectStep, { JointKeySelectStepProps } from './JointKeySelectStep';
export default {
@ -12,8 +12,9 @@ export default {
const Template: Story<JointKeySelectStepProps> = (props) => <JointKeySelectStep {...props} />;
const service = getApiClient();
export const Standard = Template.bind({});
Standard.storyName = 'Standard';
Standard.args = {
keys: getJointKeys(),
keys: service.getJointKeys(),
};

Просмотреть файл

@ -1,3 +1,4 @@
import { JointKey } from '@electionguard-ui/api';
import {
Box,
Button,
@ -14,7 +15,6 @@ import React, { useState } from 'react';
import { FormattedMessage } from 'react-intl';
import { Message, MessageId, loremIpsum } from '../../../lang';
import JointKey from '../../../models/jointKey';
import IconHeader from '../../IconHeader';
const useStyles = makeStyles((theme) => ({

Просмотреть файл

@ -1,7 +1,7 @@
import { getApiClient } from '@electionguard-ui/api';
import { Meta, Story } from '@storybook/react';
import React from 'react';
import { getManifestPreview } from '../../../mocks/electionSetup';
import ManifestPreviewStep, { ManifestPreviewStepProps } from './ManifestPreviewStep';
export default {
@ -12,8 +12,9 @@ export default {
const Template: Story<ManifestPreviewStepProps> = (props) => <ManifestPreviewStep {...props} />;
const service = getApiClient();
export const Standard = Template.bind({});
Standard.storyName = 'Standard';
Standard.args = {
preview: getManifestPreview(),
preview: service.getManifestPreview(),
};

Просмотреть файл

@ -1,3 +1,4 @@
import { ManifestPreview } from '@electionguard-ui/api';
import {
Box,
Button,
@ -13,7 +14,6 @@ import React from 'react';
import { FormattedMessage } from 'react-intl';
import { Message, MessageId } from '../../../lang';
import ManifestPreview from '../../../models/manifestPreview';
import IconHeader from '../../IconHeader';
export interface ManifestPreviewStepProps {

Просмотреть файл

@ -1,7 +1,7 @@
import { getApiClient } from '@electionguard-ui/api';
import { Meta, Story } from '@storybook/react';
import React from 'react';
import getElections from '../../mocks/elections';
import ElectionTable, { ElectionTableProps } from './ElectionTable';
export default {
@ -12,8 +12,9 @@ export default {
const Template: Story<ElectionTableProps> = (props) => <ElectionTable {...props} />;
const service = getApiClient();
export const Standard = Template.bind({});
Standard.storyName = 'Standard';
Standard.args = {
data: getElections(),
data: service.getElections(),
};

Просмотреть файл

@ -1,7 +1,7 @@
import { getApiClient } from '@electionguard-ui/api';
import { Meta, Story } from '@storybook/react';
import React from 'react';
import { getAssignedGuardians } from '../../mocks/guardians';
import GuardianTable, { GuardianTableProps } from './GuardianTable';
export default {
@ -12,8 +12,9 @@ export default {
const Template: Story<GuardianTableProps> = (props) => <GuardianTable {...props} />;
const service = getApiClient();
export const Standard = Template.bind({});
Standard.storyName = 'Standard';
Standard.args = {
data: getAssignedGuardians(),
data: service.getAssignedGuardians(),
};

Просмотреть файл

@ -1,8 +1,8 @@
import { AssignedGuardian } from '@electionguard-ui/api';
import { Box, makeStyles } from '@material-ui/core';
import { DataGrid, GridColDef } from '@material-ui/data-grid';
import * as React from 'react';
import AssignedGuardian from '../../models/assignedGuardian';
import { GuardianIconCell } from '../Cells';
import FilterToolbar from '../FilterToolbar';

Просмотреть файл

@ -1,7 +1,7 @@
import { getApiClient } from '@electionguard-ui/api';
import { Meta, Story } from '@storybook/react';
import React from 'react';
import { getJointKeys } from '../../mocks/electionSetup';
import JointKeyTable, { JointKeyTableProps } from './JointKeyTable';
export default {
@ -12,8 +12,9 @@ export default {
const Template: Story<JointKeyTableProps> = (props) => <JointKeyTable {...props} />;
const service = getApiClient();
export const Standard = Template.bind({});
Standard.storyName = 'Standard';
Standard.args = {
data: getJointKeys(),
data: service.getJointKeys(),
};

Просмотреть файл

@ -1,9 +1,9 @@
import { JointKey } from '@electionguard-ui/api';
import { Box, Button, makeStyles } from '@material-ui/core';
import { DataGrid, GridColDef } from '@material-ui/data-grid';
import * as React from 'react';
import { IntlShape, useIntl } from 'react-intl';
import JointKey from '../../models/jointKey';
import { FormattedDateCell } from '../Cells';
import FilterToolbar from '../FilterToolbar';

Просмотреть файл

@ -1,7 +1,7 @@
import { Meta, Story } from '@storybook/react';
import React from 'react';
import { getUsersWithGuardianRole } from '../../mocks/users';
import { useCreateJointKey, useGetUsersWithGuardianRole } from '../../data/queries';
import JointKeyWizard, { JointKeyWizardProps } from './JointKeyWizard';
export default {
@ -15,6 +15,6 @@ const Template: Story<JointKeyWizardProps> = (props) => <JointKeyWizard {...prop
export const Standard = Template.bind({});
Standard.storyName = 'Standard';
Standard.args = {
getGuardians: getUsersWithGuardianRole,
createJointKey: () => {},
getGuardians: useGetUsersWithGuardianRole,
createJointKey: useCreateJointKey,
};

Просмотреть файл

@ -1,8 +1,8 @@
import { BaseJointKey, User } from '@electionguard-ui/api';
import { Box } from '@material-ui/core';
import React, { useState } from 'react';
import { BaseJointKey } from '../../models/jointKey';
import User from '../../models/user';
import { AsyncResult } from '../../data/AsyncResult';
import { createEnumStepper } from '../../utils/EnumStepper';
import WizardStep from '../WizardStep';
import {
@ -20,7 +20,7 @@ export enum JointKeyStep {
}
export interface JointKeyWizardProps {
getGuardians: () => User[];
getGuardians: () => AsyncResult<User[]>;
createJointKey: (baseJointKey: BaseJointKey) => void;
onCancel: () => void;
}
@ -66,7 +66,7 @@ const JointKeyWizard: React.FC<JointKeyWizardProps> = ({
<WizardStep active={step === JointKeyStep.GuardianAssignment}>
<GuardianAssignmentStep
baseJointKey={baseJointKey}
possibleGuardians={getGuardians()}
getGuardians={getGuardians}
onSubmit={(key) => {
setBaseJointKey(key);
next();

Просмотреть файл

@ -1,7 +1,7 @@
import { getApiClient } from '@electionguard-ui/api';
import { Meta, Story } from '@storybook/react';
import React from 'react';
import { getAssignedGuardians } from '../../../mocks/guardians';
import GuardianAssignmentReviewStep, {
GuardianAssignmentReviewStepProps,
} from './GuardianAssignmentReviewStep';
@ -16,6 +16,7 @@ const Template: Story<GuardianAssignmentReviewStepProps> = (props) => (
<GuardianAssignmentReviewStep {...props} />
);
const service = getApiClient();
export const Standard = Template.bind({});
Standard.storyName = 'Standard';
Standard.args = {
@ -23,6 +24,6 @@ Standard.args = {
name: 'Montgomery School Board Key',
numberOfGuardians: 3,
quorum: 2,
guardians: getAssignedGuardians(),
guardians: service.getAssignedGuardians(),
},
};

Просмотреть файл

@ -1,10 +1,10 @@
import { BaseJointKey } from '@electionguard-ui/api';
import { Box, Button, Container, Typography, makeStyles } from '@material-ui/core';
import clsx from 'clsx';
import React from 'react';
import { FormattedMessage } from 'react-intl';
import { Message, MessageId } from '../../../lang';
import { BaseJointKey } from '../../../models/jointKey';
import GuardianTable from '../../GuardianTable';
import IconHeader from '../../IconHeader';
import InternationalText from '../../InternationalText';

Просмотреть файл

@ -1,7 +1,7 @@
import { Meta, Story } from '@storybook/react';
import React from 'react';
import { getUsersWithGuardianRole } from '../../../mocks/users';
import { useGetUsersWithGuardianRole } from '../../../data/queries';
import GuardianAssignmentStep, { GuardianAssignmentStepProps } from './GuardianAssignmentStep';
export default {
@ -15,6 +15,7 @@ const Template: Story<GuardianAssignmentStepProps> = (props) => (
);
export const Standard = Template.bind({});
Standard.storyName = 'Standard';
Standard.args = {
baseJointKey: {
@ -23,5 +24,5 @@ Standard.args = {
quorum: 2,
guardians: [],
},
possibleGuardians: getUsersWithGuardianRole(),
getGuardians: useGetUsersWithGuardianRole,
};

Просмотреть файл

@ -1,14 +1,14 @@
import { AssignedGuardian, BaseJointKey, User } from '@electionguard-ui/api';
import { Box, Button, Container, Typography, makeStyles } from '@material-ui/core';
import React, { useState } from 'react';
import { FormattedMessage } from 'react-intl';
import { QueryClient, QueryClientProvider } from 'react-query';
import { AsyncResult } from '../../../data/AsyncResult';
import { Message, MessageId } from '../../../lang';
import { getUsersWithGuardianRole } from '../../../mocks/users';
import AssignedGuardian from '../../../models/assignedGuardian';
import { BaseJointKey } from '../../../models/jointKey';
import User from '../../../models/user';
import { getColor } from '../../../theme';
import AssignmentTable from '../../AssignmentTable';
import AsyncContent from '../../AsyncContent';
import IconHeader from '../../IconHeader';
import InternationalText from '../../InternationalText';
@ -54,7 +54,7 @@ export interface GuardianAssignmentStepProps {
baseJointKey: BaseJointKey;
onSubmit: (baseJointKey: BaseJointKey) => void;
onCancel: () => void;
possibleGuardians: User[];
getGuardians: () => AsyncResult<User[]>;
}
/**
@ -64,13 +64,15 @@ const GuardianAssignmentStep: React.FC<GuardianAssignmentStepProps> = ({
baseJointKey,
onSubmit,
onCancel,
possibleGuardians,
getGuardians,
}) => {
const classes = useStyles();
const [assignedGuardians, setAssignedGuardians] = useState<AssignedGuardian[]>([]);
// const [foundGuardians, setFoundGuardians] = useState<User[]>([]);
let foundGuardians: User[] = [];
const validate = (): boolean => assignedGuardians.length === baseJointKey.numberOfGuardians;
const onAssign = (ids: string[]) => {
const selected = possibleGuardians.filter((user) => ids.includes(user.id));
const selected = foundGuardians.filter((user) => ids.includes(user.id));
const assigned: AssignedGuardian[] = selected.map((user, i) => ({
...user,
sequenceOrder: i + 1,
@ -87,90 +89,108 @@ const GuardianAssignmentStep: React.FC<GuardianAssignmentStepProps> = ({
});
};
const guardianQuery = getGuardians();
const queryClient = new QueryClient();
return (
<Container maxWidth="md">
<IconHeader title={new Message(MessageId.JointKeySetup_GuardianAssignment_Title)} />
<form className={classes.form} onSubmit={handleSubmit}>
<InternationalText
className={classes.description}
id={MessageId.JointKeySetup_GuardianAssignment_Description}
/>
<Box
className={classes.jointKeyDisplay}
display="flex"
flexDirection="column"
width="100%"
>
<Typography
className={classes.heading}
color="secondary"
variant="h5"
component="h2"
<QueryClientProvider client={queryClient}>
<IconHeader title={new Message(MessageId.JointKeySetup_GuardianAssignment_Title)} />
<form className={classes.form} onSubmit={handleSubmit}>
<InternationalText
className={classes.description}
id={MessageId.JointKeySetup_GuardianAssignment_Description}
/>
<Box
className={classes.jointKeyDisplay}
display="flex"
flexDirection="column"
width="100%"
>
{baseJointKey.name}
</Typography>
<Box display="flex" flexWrap="wrap">
<Box className={classes.numberContainer} display="flex">
<InternationalText
variant="h6"
noWrap
id={MessageId.JointKey_NumberOfGuardians}
/>
<Typography variant="h6">:</Typography>
<Typography
className={classes.numberDisplay}
color="primary"
variant="h6"
>
{baseJointKey.numberOfGuardians}
</Typography>
</Box>
<Box className={classes.numberContainer} display="flex">
<InternationalText noWrap variant="h6" id={MessageId.JointKey_Quorum} />
<Typography variant="h6">:</Typography>
<Typography
className={classes.numberDisplay}
color="primary"
variant="h6"
>
{baseJointKey.quorum}
</Typography>
</Box>
<Box className={classes.numberContainer} display="flex">
<InternationalText
noWrap
variant="h6"
id={MessageId.JointKeySetup_GuardianAssignment_AssignedLabel}
/>
<Typography variant="h6">:</Typography>
<Typography
className={classes.numberDisplay}
color={validate() ? 'primary' : 'error'}
variant="h6"
>
{assignedGuardians.length}
</Typography>
<Typography
className={classes.heading}
color="secondary"
variant="h5"
component="h2"
>
{baseJointKey.name}
</Typography>
<Box display="flex" flexWrap="wrap">
<Box className={classes.numberContainer} display="flex">
<InternationalText
variant="h6"
noWrap
id={MessageId.JointKey_NumberOfGuardians}
/>
<Typography variant="h6">:</Typography>
<Typography
className={classes.numberDisplay}
color="primary"
variant="h6"
>
{baseJointKey.numberOfGuardians}
</Typography>
</Box>
<Box className={classes.numberContainer} display="flex">
<InternationalText
noWrap
variant="h6"
id={MessageId.JointKey_Quorum}
/>
<Typography variant="h6">:</Typography>
<Typography
className={classes.numberDisplay}
color="primary"
variant="h6"
>
{baseJointKey.quorum}
</Typography>
</Box>
<Box className={classes.numberContainer} display="flex">
<InternationalText
noWrap
variant="h6"
id={MessageId.JointKeySetup_GuardianAssignment_AssignedLabel}
/>
<Typography variant="h6">:</Typography>
<Typography
className={classes.numberDisplay}
color={validate() ? 'primary' : 'error'}
variant="h6"
>
{assignedGuardians.length}
</Typography>
</Box>
</Box>
</Box>
</Box>
<Box className={classes.tableContainer} width="100%">
<AssignmentTable data={getUsersWithGuardianRole()} onChanged={onAssign} />
</Box>
<Box className={classes.buttonContainer}>
<Button
disabled={!validate()}
className={classes.button}
type="submit"
variant="contained"
color="secondary"
>
<FormattedMessage id={MessageId.Actions_Submit} />
</Button>
<Button className={classes.button} color="primary" onClick={onCancel}>
<FormattedMessage id={MessageId.Actions_Cancel} />
</Button>
</Box>
</form>
<Box className={classes.tableContainer} width="100%">
<AsyncContent query={guardianQuery} errorMessage="there was an error">
{(usersFound) => {
foundGuardians = usersFound;
return (
<>
<AssignmentTable data={usersFound} onChanged={onAssign} />
</>
);
}}
</AsyncContent>
</Box>
<Box className={classes.buttonContainer}>
<Button
disabled={!validate()}
className={classes.button}
type="submit"
variant="contained"
color="secondary"
>
<FormattedMessage id={MessageId.Actions_Submit} />
</Button>
<Button className={classes.button} color="primary" onClick={onCancel}>
<FormattedMessage id={MessageId.Actions_Cancel} />
</Button>
</Box>
</form>
</QueryClientProvider>
</Container>
);
};

Просмотреть файл

@ -1,9 +1,9 @@
import { BaseJointKey } from '@electionguard-ui/api';
import { Box, Button, Container, Typography, makeStyles } from '@material-ui/core';
import React from 'react';
import { FormattedMessage } from 'react-intl';
import { Message, MessageId } from '../../../lang';
import { BaseJointKey } from '../../../models/jointKey';
import IconHeader from '../../IconHeader';
import InternationalText from '../../InternationalText';

Просмотреть файл

@ -1,10 +1,10 @@
import { BaseJointKey } from '@electionguard-ui/api';
import { Box, Button, Container, TextField, makeStyles } from '@material-ui/core';
import clsx from 'clsx';
import React, { useState } from 'react';
import { FormattedMessage, useIntl } from 'react-intl';
import { Message, MessageId } from '../../../lang';
import { BaseJointKey } from '../../../models/jointKey';
import IconHeader from '../../IconHeader';
import InternationalText from '../../InternationalText';

Просмотреть файл

@ -1,7 +1,7 @@
import { getApiClient } from '@electionguard-ui/api';
import { Meta, Story } from '@storybook/react';
import React from 'react';
import { getKeyCeremonies } from '../../mocks/keyCeremony';
import KeyCeremonyTable, { KeyCeremonyTableProps } from './KeyCeremonyTable';
export default {
@ -12,8 +12,9 @@ export default {
const Template: Story<KeyCeremonyTableProps> = (props) => <KeyCeremonyTable {...props} />;
const service = getApiClient();
export const Standard = Template.bind({});
Standard.storyName = 'Standard';
Standard.args = {
data: getKeyCeremonies(),
data: service.getKeyCeremonies(),
};

Просмотреть файл

@ -1,9 +1,9 @@
import { KeyCeremony } from '@electionguard-ui/api';
import { Box, Button, makeStyles } from '@material-ui/core';
import { DataGrid, GridColDef } from '@material-ui/data-grid';
import * as React from 'react';
import { IntlShape, useIntl } from 'react-intl';
import { KeyCeremony } from '../../models/keyCeremony';
import { FormattedDateCell } from '../Cells';
import FilterToolbar from '../FilterToolbar';

Просмотреть файл

@ -1,7 +1,7 @@
import { BackupVerification } from '@electionguard-ui/api';
import { Box } from '@material-ui/core';
import React from 'react';
import { BackupVerification } from '../../models/keyCeremony';
import KeyCeremonyStep from './KeyCeremonyStep';
import {
CeremonyCompleteStep,

Просмотреть файл

@ -1,8 +1,8 @@
import { KeyCeremonyGuardian } from '@electionguard-ui/api';
import { Box, makeStyles } from '@material-ui/core';
import { DataGrid, GridColDef } from '@material-ui/data-grid';
import * as React from 'react';
import { KeyCeremonyGuardian } from '../../models/keyCeremony';
import { GuardianIconCell, TaskStatusCell } from '../Cells';
import FilterToolbar from '../FilterToolbar';
import InternationalText from '../InternationalText';

Просмотреть файл

@ -1,7 +1,7 @@
import { getApiClient } from '@electionguard-ui/api';
import { Meta, Story } from '@storybook/react';
import React from 'react';
import { getKeyCeremonyGuardiansByStep } from '../../mocks/keyCeremony';
import KeyCeremonyStep from './KeyCeremonyStep';
import KeyCeremonyVisualization, {
KeyCeremonyVisualizationProps,
@ -15,15 +15,17 @@ export default {
const Template: Story<KeyCeremonyVisualizationProps> = (props) => {
const { activeStep } = props;
const service = getApiClient();
return (
<div>
<KeyCeremonyVisualization
{...props}
guardians={getKeyCeremonyGuardiansByStep(activeStep)}
guardians={service.getKeyCeremonyGuardiansByStep(activeStep)}
/>
<KeyCeremonyVisualization
{...props}
guardians={getKeyCeremonyGuardiansByStep(activeStep + 1)}
guardians={service.getKeyCeremonyGuardiansByStep(activeStep + 1)}
/>
</div>
);

Просмотреть файл

@ -1,10 +1,9 @@
import { KeyCeremonyGuardian, TaskStatus } from '@electionguard-ui/api';
import { Box, Container, makeStyles, useTheme } from '@material-ui/core';
import { CheckCircle as CompleteIcon } from '@material-ui/icons';
import React from 'react';
import { MessageId } from '../../lang';
import { KeyCeremonyGuardian } from '../../models/keyCeremony';
import TaskStatus from '../../models/taskStatus';
import InternationalText from '../InternationalText';
import SequenceOrderProgress from '../SequenceOrderProgress';
import KeyCeremonyStep from './KeyCeremonyStep';

Просмотреть файл

@ -1,9 +1,12 @@
import {
BackupVerification,
KeyCeremonyGuardian,
TaskStatus,
getApiClient,
} from '@electionguard-ui/api';
import { Meta, Story } from '@storybook/react';
import React, { useState } from 'react';
import { getKeyCeremonies, setKeyCeremonyGuardianToStep } from '../../mocks/keyCeremony';
import { BackupVerification, KeyCeremonyGuardian } from '../../models/keyCeremony';
import TaskStatus from '../../models/taskStatus';
import delay from '../../utils/delay';
import KeyCeremonyStep from './KeyCeremonyStep';
import KeyCeremonyWizard, { KeyCeremonyWizardProps } from './KeyCeremonyWizard';
@ -14,8 +17,9 @@ export default {
parameters: { layout: 'fullscreen' },
} as Meta;
const service = getApiClient();
const Template: Story<KeyCeremonyWizardProps> = (props) => {
const [keyCeremony, setKeyCeremony] = useState(getKeyCeremonies()[0]);
const [keyCeremony, setKeyCeremony] = useState(service.getKeyCeremonies()[0]);
const loggedInGuardian = keyCeremony.guardians.find(
(i) => i.sequenceOrder === 1
) as KeyCeremonyGuardian;
@ -24,7 +28,7 @@ const Template: Story<KeyCeremonyWizardProps> = (props) => {
const guardian = keyCeremony.guardians.find(
(i) => i.sequenceOrder === 1
) as KeyCeremonyGuardian;
const updated = setKeyCeremonyGuardianToStep(guardian, step);
const updated = service.setKeyCeremonyGuardianToStep(guardian, step);
setKeyCeremony({
...keyCeremony,
guardians: [updated, ...keyCeremony.guardians.filter((i) => i.sequenceOrder !== 1)],
@ -32,7 +36,9 @@ const Template: Story<KeyCeremonyWizardProps> = (props) => {
};
const updateGuardians = (step: KeyCeremonyStep) => {
const updated = keyCeremony.guardians.map((g) => setKeyCeremonyGuardianToStep(g, step));
const updated = keyCeremony.guardians.map((g) =>
service.setKeyCeremonyGuardianToStep(g, step)
);
setKeyCeremony({
...keyCeremony,
guardians: updated,
@ -98,7 +104,7 @@ const Template: Story<KeyCeremonyWizardProps> = (props) => {
};
const completeCeremony = async () => {
setKeyCeremony(getKeyCeremonies()[0]);
setKeyCeremony(service.getKeyCeremonies()[0]);
};
return (

Просмотреть файл

@ -1,9 +1,12 @@
import {
AssignedGuardian,
KeyCeremony,
KeyCeremonyStatus,
TaskStatus,
} from '@electionguard-ui/api';
import { Box, Container } from '@material-ui/core';
import React, { useState } from 'react';
import AssignedGuardian from '../../models/assignedGuardian';
import { KeyCeremony, KeyCeremonyStatus } from '../../models/keyCeremony';
import TaskStatus from '../../models/taskStatus';
import { createEnumStepper } from '../../utils/EnumStepper';
import WizardStep from '../WizardStep';
import KeyCeremonyActiveStep from './KeyCeremonyActiveStep';

Просмотреть файл

@ -1,8 +1,8 @@
import { AssignedGuardian } from '@electionguard-ui/api';
import { Container, makeStyles } from '@material-ui/core';
import React from 'react';
import { Message, MessageId } from '../../../lang';
import AssignedGuardian from '../../../models/assignedGuardian';
import GuardianTable from '../../GuardianTable';
import InternationalText from '../../InternationalText';
import StepHeader from '../../StepHeader';

Просмотреть файл

@ -1,10 +1,9 @@
import { BackupVerification, TaskStatus } from '@electionguard-ui/api';
import { Box, makeStyles } from '@material-ui/core';
import { DataGrid, GridColDef, GridRowId, GridSortDirection } from '@material-ui/data-grid';
import React, { useState } from 'react';
import { Message, MessageId } from '../../../lang';
import { BackupVerification } from '../../../models/keyCeremony';
import TaskStatus from '../../../models/taskStatus';
import { GuardianIconCell, TaskStatusCell } from '../../Cells';
import FilterToolbar from '../../FilterToolbar';
import StepHeader from '../../StepHeader';

Просмотреть файл

@ -59,6 +59,11 @@ export const getPreset = (type: MenuOptionType): MenuOptionPreset => {
title: new Message(MessageId.MenuOption_ManageElections),
Icon: BallotOutlined,
};
case MenuOptionType.SetupJointKeys:
return {
title: new Message(MessageId.MenuOption_SetupJointKey),
Icon: VpnKeyOutlined,
};
default:
return {
title: new Message(OverloadMessageId, 'Unknown'),

Просмотреть файл

@ -7,6 +7,7 @@ export enum MenuOptionType {
BuildManifest = 'build-manifest',
ManageJointKeys = 'manage-joint-key',
ManageElections = 'manage-elections',
SetupJointKeys = 'setup-joint-key',
}
export default MenuOptionType;

Просмотреть файл

@ -1,9 +1,9 @@
import { TaskStatus } from '@electionguard-ui/api';
import { BallotOutlined } from '@material-ui/icons';
import { Meta, Story } from '@storybook/react';
import React, { useState } from 'react';
import { Message, loremIpsum } from '../../lang';
import TaskStatus from '../../models/taskStatus';
import delay from '../../utils/delay';
import Processor, { ProcessorProps } from './Processor';

Просмотреть файл

@ -1,3 +1,4 @@
import { TaskStatus } from '@electionguard-ui/api';
import { Box, CircularProgress, Container, SvgIconProps, makeStyles } from '@material-ui/core';
import {
CheckCircle as DefaultCompleteIcon,
@ -8,7 +9,6 @@ import clsx from 'clsx';
import React, { useState } from 'react';
import { Message, MessageId } from '../../lang';
import TaskStatus from '../../models/taskStatus';
import FormattedButton from '../FormattedButton';
import InternationalText from '../InternationalText';
import StepHeader from '../StepHeader';

Просмотреть файл

@ -1,8 +1,8 @@
import { TaskStatus } from '@electionguard-ui/api';
import { Box } from '@material-ui/core';
import { Meta, Story } from '@storybook/react';
import React from 'react';
import TaskStatus from '../../models/taskStatus';
import TaskStatusIcon, { TaskStatusIconProps } from './TaskStatusIcon';
export default {

Просмотреть файл

@ -1,3 +1,4 @@
import { TaskStatus } from '@electionguard-ui/api';
import { makeStyles } from '@material-ui/core';
import {
CheckCircleOutlined as CompleteIcon,
@ -6,7 +7,6 @@ import {
import React from 'react';
import { Message, MessageId } from '../../lang';
import TaskStatus from '../../models/taskStatus';
import FormattedButton from '../FormattedButton';
import InternationalText from '../InternationalText';

Просмотреть файл

@ -0,0 +1,6 @@
export interface AsyncResult<T> {
data: T | undefined;
isError: boolean;
isLoading: boolean;
isIdle: boolean;
}

Просмотреть файл

@ -0,0 +1,17 @@
import { BaseJointKey, User, getApiClient } from '@electionguard-ui/api';
import { useQuery } from 'react-query';
import { AsyncResult } from './AsyncResult';
import { QUERY_NAMES } from './query_names';
export function useGetUsersWithGuardianRole(): AsyncResult<User[]> {
const service = getApiClient();
return useQuery(QUERY_NAMES.GUARDIANS, () => service.getUsersWithGuardianRole());
}
export function useCreateJointKey(data: BaseJointKey): Promise<boolean> {
const service = getApiClient();
return service.createJointKey(data);
}
export default useGetUsersWithGuardianRole;

Просмотреть файл

@ -0,0 +1,7 @@
export const QUERY_NAMES = {
GUARDIANS: 'GUARDIANS',
ELECTIONS: 'ELECTIONS',
CREATE_KEY: 'CREATE_KEY',
};
export default QUERY_NAMES;

Просмотреть файл

@ -2,6 +2,7 @@ import { CssBaseline, MuiThemeProvider } from '@material-ui/core';
import React from 'react';
import ReactDOM from 'react-dom';
import { IntlProvider } from 'react-intl';
import { QueryClient, QueryClientProvider } from 'react-query';
import App from './App';
import { ConfigContext, defaultConfig } from './contexts/config';
@ -10,21 +11,25 @@ import Language from './models/language';
import reportWebVitals from './reportWebVitals';
import theme from './theme';
const queryClient = new QueryClient();
ReactDOM.render(
<React.StrictMode>
<ConfigContext.Provider value={defaultConfig}>
<LanguageContext.Provider value={defaultLanguage}>
<LanguageContext.Consumer>
{(language: Language) => (
<IntlProvider locale={language.locale} messages={language.messages}>
<MuiThemeProvider theme={theme(language.mui)}>
<CssBaseline />
<App />
</MuiThemeProvider>
</IntlProvider>
)}
</LanguageContext.Consumer>
</LanguageContext.Provider>
<QueryClientProvider client={queryClient}>
<LanguageContext.Provider value={defaultLanguage}>
<LanguageContext.Consumer>
{(language: Language) => (
<IntlProvider locale={language.locale} messages={language.messages}>
<MuiThemeProvider theme={theme(language.mui)}>
<CssBaseline />
<App />
</MuiThemeProvider>
</IntlProvider>
)}
</LanguageContext.Consumer>
</LanguageContext.Provider>
</QueryClientProvider>
</ConfigContext.Provider>
</React.StrictMode>,
document.getElementById('root')

Просмотреть файл

@ -20,6 +20,7 @@ enum MessageId {
MenuOption_BuildManifest = 'menu.option.build_manifest',
MenuOption_ManageJointKeys = 'menu.option.manage_joint_keys',
MenuOption_ManageElections = 'menu.option.manage_elections',
MenuOption_SetupJointKey = 'menu.option.setup_joint_keys',
// Login Form
LoginFormUsername = 'login_form.username',

Просмотреть файл

@ -15,6 +15,7 @@
"menu.option.build_manifest": "Build Manifest",
"menu.option.manage_joint_keys": "Manage Joint Keys",
"menu.option.manage_elections": "Manage Elections",
"menu.option.setup_joint_keys": "Setup Joint Keys",
"login_form.username": "Username",
"login_form.password": "Password",

Просмотреть файл

@ -15,6 +15,7 @@
"menu.option.build_manifest": "Build Manifest",
"menu.option.manage_joint_keys": "Manage Joint Keys",
"menu.option.manage_elections": "Manage Elections",
"menu.option.setup_joint_keys": "Setup Joint Keys",
"login_form.username": "Username",
"login_form.password": "Password",

Просмотреть файл

@ -15,6 +15,7 @@
"menu.option.build_manifest": "Build Manifest",
"menu.option.manage_joint_keys": "Manage Joint Keys",
"menu.option.manage_elections": "Manage Elections",
"menu.option.setup_joint_keys": "Setup Joint Keys",
"login_form.username": "Username",
"login_form.password": "Password",
@ -141,7 +142,6 @@
"key_ceremony.complete.description": "You have successfully completed the key ceremony. Thanks for participating!",
"key_ceremony.complete.button": "Return to Home",
"key_ceremony.steps.instructions": "Steps",
"key_ceremony.visualization_complete": "Joint Key Created",
"key_ceremony.steps.meet_guardians": "Meet the Guardians",

Некоторые файлы не были показаны из-за слишком большого количества измененных файлов Показать больше