Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: improved data fetching for execution details page #131

Merged
merged 13 commits into from
Jan 6, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions .eslintignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
src/generated/
.dist/
dist/
node_modules/
.vscode/
tsd/
package.json
webpack.config.ts
30 changes: 22 additions & 8 deletions .eslintrc.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,27 @@
// We are using eslint with the typescript parser plugin only to validate things
// which are not supported in tslint, such as react hooks
module.exports = {
parser: '@typescript-eslint/parser',
parserOptions: {
jsx: true,
sourceType: 'module'
root: true,
env: {
browser: true,
es6: true,
node: true
},
plugins: ['react-hooks'],
parser: '@typescript-eslint/parser',
plugins: ['@typescript-eslint', 'react-hooks', 'jest'],
extends: [
'eslint:recommended',
'plugin:@typescript-eslint/recommended',
'plugin:react-hooks/recommended',
'plugin:jest/recommended',
'prettier',
'prettier/@typescript-eslint'
],
rules: {
'react-hooks/rules-of-hooks': 'error'
'no-case-declarations': 'warn',
'jest/no-mocks-import': 'off',
'jest/valid-title': 'off',
'@typescript-eslint/no-var-requires': 'off',
'@typescript-eslint/ban-types': 'warn',
'@typescript-eslint/no-empty-function': 'warn',
'@typescript-eslint/explicit-module-boundary-types': 'off'
}
};
23 changes: 15 additions & 8 deletions .storybook/StorybookContainer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@ import { CssBaseline } from '@material-ui/core';
import { makeStyles, Theme } from '@material-ui/core/styles';
import { ThemeProvider } from '@material-ui/styles';
import * as React from 'react';
import { QueryClientProvider } from 'react-query';
import { ErrorBoundary } from '../src/components/common';
import { createQueryClient } from '../src/components/data/queryCache';
import { muiTheme } from '../src/components/Theme/muiTheme';

const useStyles = makeStyles((theme: Theme) => ({
Expand All @@ -12,11 +14,16 @@ const useStyles = makeStyles((theme: Theme) => ({
}
}));

export const StorybookContainer: React.StatelessComponent = ({ children }) => (
<ThemeProvider theme={muiTheme}>
<CssBaseline />
<ErrorBoundary>
<div className={useStyles().container}>{children}</div>
</ErrorBoundary>
</ThemeProvider>
);
export const StorybookContainer: React.FC = ({ children }) => {
const [queryClient] = React.useState(() => createQueryClient());
return (
<ThemeProvider theme={muiTheme}>
<CssBaseline />
<ErrorBoundary>
<QueryClientProvider client={queryClient}>
<div className={useStyles().container}>{children}</div>
</QueryClientProvider>
</ErrorBoundary>
</ThemeProvider>
);
};
25 changes: 0 additions & 25 deletions .storybook/config.js

This file was deleted.

4 changes: 4 additions & 0 deletions .storybook/main.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
module.exports = {
stories: ['../src/components/**/*.stories.tsx'],
addons: ['@storybook/addon-knobs']
};
22 changes: 22 additions & 0 deletions .storybook/preview.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import '../src/protobuf';
import { withKnobs } from '@storybook/addon-knobs';
import { addDecorator } from '@storybook/react';
import * as React from 'react';
import StoryRouter from 'storybook-react-router';
import { StorybookContainer } from './StorybookContainer';

addDecorator(withKnobs);
addDecorator(StoryRouter());
addDecorator(Story => <StorybookContainer><Story /></StorybookContainer>);

// Storybook executes this module in both bootstap phase (Node)
// and a story's runtime (browser). However, cannot call `setupWorker`
// in Node environment, so need to check if we're in a browser.
if (typeof global.process === 'undefined') {
const { worker } = require('./worker');

// Start the mocking when each story is loaded.
// Repetitive calls to the `.start()` method will check for an existing
// worker and reuse it.
worker.start();
}
241 changes: 241 additions & 0 deletions .storybook/public/mockServiceWorker.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,241 @@
/**
* Mock Service Worker.
* @see https://github.com/mswjs/msw
* - Please do NOT modify this file.
* - Please do NOT serve this file on production.
*/
/* eslint-disable */
/* tslint:disable */

const INTEGRITY_CHECKSUM = '65d33ca82955e1c5928aed19d1bdf3f9'
const bypassHeaderName = 'x-msw-bypass'

let clients = {}

self.addEventListener('install', function () {
return self.skipWaiting()
})

self.addEventListener('activate', async function (event) {
return self.clients.claim()
})

self.addEventListener('message', async function (event) {
const clientId = event.source.id
const client = await event.currentTarget.clients.get(clientId)
const allClients = await self.clients.matchAll()
const allClientIds = allClients.map((client) => client.id)

switch (event.data) {
case 'KEEPALIVE_REQUEST': {
sendToClient(client, {
type: 'KEEPALIVE_RESPONSE',
})
break
}

case 'INTEGRITY_CHECK_REQUEST': {
sendToClient(client, {
type: 'INTEGRITY_CHECK_RESPONSE',
payload: INTEGRITY_CHECKSUM,
})
break
}

case 'MOCK_ACTIVATE': {
clients = ensureKeys(allClientIds, clients)
clients[clientId] = true

sendToClient(client, {
type: 'MOCKING_ENABLED',
payload: true,
})
break
}

case 'MOCK_DEACTIVATE': {
clients = ensureKeys(allClientIds, clients)
clients[clientId] = false
break
}

case 'CLIENT_CLOSED': {
const remainingClients = allClients.filter((client) => {
return client.id !== clientId
})

// Unregister itself when there are no more clients
if (remainingClients.length === 0) {
self.registration.unregister()
}

break
}
}
})

self.addEventListener('fetch', function (event) {
const { clientId, request } = event
const requestClone = request.clone()
const getOriginalResponse = () => fetch(requestClone)

// Bypass navigation requests.
if (request.mode === 'navigate') {
return
}

// Bypass mocking if the current client isn't present in the internal clients map
// (i.e. has the mocking disabled).
if (!clients[clientId]) {
return
}

// Opening the DevTools triggers the "only-if-cached" request
// that cannot be handled by the worker. Bypass such requests.
if (request.cache === 'only-if-cached' && request.mode !== 'same-origin') {
return
}

event.respondWith(
new Promise(async (resolve, reject) => {
const client = await event.target.clients.get(clientId)

// Bypass mocking when the request client is not active.
if (!client) {
return resolve(getOriginalResponse())
}

// Bypass requests with the explicit bypass header
if (requestClone.headers.get(bypassHeaderName) === 'true') {
const modifiedHeaders = serializeHeaders(requestClone.headers)

// Remove the bypass header to comply with the CORS preflight check
delete modifiedHeaders[bypassHeaderName]

const originalRequest = new Request(requestClone, {
headers: new Headers(modifiedHeaders),
})

return resolve(fetch(originalRequest))
}

const reqHeaders = serializeHeaders(request.headers)
const body = await request.text()

const rawClientMessage = await sendToClient(client, {
type: 'REQUEST',
payload: {
url: request.url,
method: request.method,
headers: reqHeaders,
cache: request.cache,
mode: request.mode,
credentials: request.credentials,
destination: request.destination,
integrity: request.integrity,
redirect: request.redirect,
referrer: request.referrer,
referrerPolicy: request.referrerPolicy,
body,
bodyUsed: request.bodyUsed,
keepalive: request.keepalive,
},
})

const clientMessage = rawClientMessage

switch (clientMessage.type) {
case 'MOCK_SUCCESS': {
setTimeout(
resolve.bind(this, createResponse(clientMessage)),
clientMessage.payload.delay,
)
break
}

case 'MOCK_NOT_FOUND': {
return resolve(getOriginalResponse())
}

case 'NETWORK_ERROR': {
const { name, message } = clientMessage.payload
const networkError = new Error(message)
networkError.name = name

// Rejecting a request Promise emulates a network error.
return reject(networkError)
}

case 'INTERNAL_ERROR': {
const parsedBody = JSON.parse(clientMessage.payload.body)

console.error(
`\
[MSW] Request handler function for "%s %s" has thrown the following exception:

${parsedBody.errorType}: ${parsedBody.message}
(see more detailed error stack trace in the mocked response body)

This exception has been gracefully handled as a 500 response, however, it's strongly recommended to resolve this error.
If you wish to mock an error response, please refer to this guide: https://mswjs.io/docs/recipes/mocking-error-responses\
`,
request.method,
request.url,
)

return resolve(createResponse(clientMessage))
}
}
}).catch((error) => {
console.error(
'[MSW] Failed to mock a "%s" request to "%s": %s',
request.method,
request.url,
error,
)
}),
)
})

function serializeHeaders(headers) {
const reqHeaders = {}
headers.forEach((value, name) => {
reqHeaders[name] = reqHeaders[name]
? [].concat(reqHeaders[name]).concat(value)
: value
})
return reqHeaders
}

function sendToClient(client, message) {
return new Promise((resolve, reject) => {
const channel = new MessageChannel()

channel.port1.onmessage = (event) => {
if (event.data && event.data.error) {
reject(event.data.error)
} else {
resolve(event.data)
}
}

client.postMessage(JSON.stringify(message), [channel.port2])
})
}

function createResponse(clientMessage) {
return new Response(clientMessage.payload.body, {
...clientMessage.payload,
headers: clientMessage.payload.headers,
})
}

function ensureKeys(keys, obj) {
return Object.keys(obj).reduce((acc, key) => {
if (keys.includes(key)) {
acc[key] = obj[key]
}

return acc
}, {})
}
19 changes: 19 additions & 0 deletions .storybook/worker.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { setupWorker, SetupWorkerApi } from 'msw';
import { AdminServer, createAdminServer } from '../src/mocks/createAdminServer';
import { basicPythonWorkflow } from '../src/mocks/data/fixtures/basicPythonWorkflow';
import { dynamicExternalSubWorkflow } from '../src/mocks/data/fixtures/dynamicExternalSubworkflow';
import { dynamicPythonNodeExecutionWorkflow } from '../src/mocks/data/fixtures/dynamicPythonWorkflow';
import { oneFailedTaskWorkflow } from '../src/mocks/data/fixtures/oneFailedTaskWorkflow';
import { insertFixture } from '../src/mocks/data/insertFixture';
import { insertDefaultData } from '../src/mocks/insertDefaultData';

const { handlers, server } = createAdminServer();
export type MockServer = SetupWorkerApi & AdminServer;
const worker: MockServer = { ...setupWorker(...handlers), ...server };
insertDefaultData(worker);
insertFixture(worker, basicPythonWorkflow.generate());
insertFixture(worker, dynamicExternalSubWorkflow.generate());
insertFixture(worker, dynamicPythonNodeExecutionWorkflow.generate());
insertFixture(worker, oneFailedTaskWorkflow.generate());

export { worker };
Loading