diff --git a/conda/js/src/helpers/api.js b/conda/js/src/helpers/api.js index 60935f59dd15e56cfcb57c9b589a8da1ed698bf3..3afcb2ad3658045b3cf8f3055b4ceb46030910b9 100644 --- a/conda/js/src/helpers/api.js +++ b/conda/js/src/helpers/api.js @@ -1,4 +1,5 @@ // @flow +// API helpers not tied to any specific library import { pluralize } from './beat.js'; import type { BeatEntity } from './beat.js'; import type { StringObject } from '.'; @@ -13,6 +14,9 @@ let port = '5000'; const toUrl = (str) => `http://127.0.0.1:${ port }/${ str }`; +// curried func for calling the REST API +// basically: +// <response> = genApiCall(<http rest verb>)(<endpoint>)(<payload if any>) const genApiCall = (method) => (be: Fetchable) => async (obj: any) => { const validMethods = ['GET', 'POST', 'PUT', 'DELETE']; if(!validMethods.includes(method)) @@ -36,10 +40,12 @@ const genApiCall = (method) => (be: Fetchable) => async (obj: any) => { return json; }; +// sets the port number for api calls export const setPort = (newPort: number) => { port = newPort; }; +// gens all the default API funcs for an endpoint export const genModuleApiFuncs = (be: Fetchable) => { return { get: genApiCall('GET')(be), @@ -49,6 +55,7 @@ export const genModuleApiFuncs = (be: Fetchable) => { }; }; +// fetches layout info from graphviz for a toolchain const layoutUrl = toUrl('layout'); export const fetchLayout = async (tcName: string) => { const layoutConfig = { @@ -64,6 +71,7 @@ export const fetchLayout = async (tcName: string) => { return JSON.parse(json); }; +// generates a python file from a template given some args payload const templatesUrl = toUrl('templates'); const generateTemplate = async (be: string, args: {[string]: any}) => { const templatesConfig = { diff --git a/conda/js/src/helpers/beat.js b/conda/js/src/helpers/beat.js index 41979813acc6bec9422e893cc828082dda43bfe1..228643f06d27d1716527386ea33e93e0b4f14f17 100644 --- a/conda/js/src/helpers/beat.js +++ b/conda/js/src/helpers/beat.js @@ -1,7 +1,10 @@ // @flow +// BEAT-specific helpers import { jsonClone } from '.'; +// all the BEAT entities export type BeatEntity = 'database' | 'library' | 'dataformat' | 'algorithm' | 'toolchain' | 'experiment' | 'plotter' | 'plotterparameter'; +// format of an instance of a beat entity in redux store export type BeatObject = {| // name of the object name: string, @@ -11,10 +14,12 @@ export type BeatObject = {| extraContents?: any, |}; +// format for the settings object export type BeatSettings = {| prefix: string |}; +// format for an environment object (info about the available docker envs) export type BeatEnvironment = {| name: string, packages: { [string]: string }, @@ -44,6 +49,7 @@ export const BEAT_ENTITIES: BeatEntity[] = [ 'plotterparameter', ]; +// types auto-recognized by beat.core export const BUILTIN_TYPES = [ 'int8', 'int16', @@ -63,6 +69,7 @@ export const BUILTIN_TYPES = [ 'object', ]; +// result types for analyzers export const ANALYZER_RESULT_TYPES = [ 'int32', 'float32', @@ -74,12 +81,16 @@ Object.freeze(BEAT_ENTITIES); Object.freeze(BUILTIN_TYPES); Object.freeze(ANALYZER_RESULT_TYPES); +// get toolchain name from experiment name export const getTcFromExpName = (expName: string) => expName.split('/').slice(1, 4).join('/'); +// default object for beat entity instances export const getDefaultEntityObject = () => ({name: '', contents: {}}); +// most fields/name of/in beat objects need to conform to this regex export const rxField = /^[a-zA-Z_][a-zA-Z0-9_-]*$/; +// how many segments (separated by '/') are in an entity's name export const nameSegmentsForEntity = (entity: BeatEntity) => { if(entity === 'database') return 2; @@ -88,6 +99,7 @@ export const nameSegmentsForEntity = (entity: BeatEntity) => { return 3; }; +// pluralizes an entity export const pluralize = (be: BeatEntity): string => { switch(be) { case 'database': @@ -111,6 +123,7 @@ export const pluralize = (be: BeatEntity): string => { } }; +// validates that a name for beat object of an entity is fine, given the names for all objects of that entity export const nameValidator = (be: BeatEntity, allNames?: string[] = []) => (name: string): boolean => { if(allNames.includes(name)){ return false; @@ -140,12 +153,16 @@ export const nameValidator = (be: BeatEntity, allNames?: string[] = []) => (name return true; }; +// some editors are prime targets for better/additional validation + export type ValidatorObjectField = boolean | boolean[]; +// only algs & libs have this extra validation right now export type ValidatorObject = AlgorithmValidatorObject | LibraryValidatorObject; export type ValidatorFunc = (obj: BeatObject, allObjs: BeatObject[]) => ValidatorObject; +// algorithm validator info export type AlgorithmValidatorObject = { // is algorithm name valid? name: ValidatorObjectField, @@ -161,6 +178,7 @@ export type AlgorithmValidatorObject = { result0Exists: ValidatorObjectField, }; +// lots of checks for algs export const algorithmValidator = (obj: BeatObject, allObjs: BeatObject[]): AlgorithmValidatorObject => { let name = false; @@ -205,6 +223,7 @@ export const algorithmValidator = (obj: BeatObject, allObjs: BeatObject[]): Algo return valid; }; +// extra validation for libs export type LibraryValidatorObject = { // is library name valid? name: ValidatorObjectField, @@ -231,6 +250,10 @@ export const libraryValidator = (obj: BeatObject, allObjs: BeatObject[]): Librar }; }; +// these next several funcs, the "getValid***Obj", returns a properly-formed JSON obj, +// optionally filling in fields with an existing obj. +// basically they exist to keep malformed JSON from breaking the editors. +// export const getValidAlgorithmObj = (data: BeatObject = {name: '', contents: {}}) => { const getObj = { name: '', diff --git a/conda/js/src/helpers/index.js b/conda/js/src/helpers/index.js index 18b4ccd76477619aaf1a06086f556b5411a70b3b..6c12503d6c56ea52424587f1a55ba1bbd8265c02 100644 --- a/conda/js/src/helpers/index.js +++ b/conda/js/src/helpers/index.js @@ -1,22 +1,15 @@ // @flow -export const objToArr = (data: any): any[] => { - if(!data) - throw new Error(`Invalid data "${ data }"`); - - if(Array.isArray(data)) - return data; - return Object.entries(data) - .map(([k, obj]) => objToArr(obj)) - .reduce((a: any[], objs: any[]) => [...a, ...objs], []) - ; -}; +// basic helpers - common bits of code refactored to DRY +// changes a field in an object to a different name +// only required because keys are often mutable export const changeObjFieldName = (obj: {}, oldName: string, newName: string): {} => { return Object.entries(obj) .map(([name, val]) => [name === oldName ? newName : name, val]) .reduce((o, [name, val]) => ({...o, [name]: val}), {}); }; +// generates a new unique key given a prefix (like "value" or "param") and possibly conflicting keys export const generateNewKey = (keyPrefix: string = '', existingKeys: string[]) => { if(!existingKeys.includes(keyPrefix)) return keyPrefix; @@ -49,7 +42,8 @@ export const getRandomBrightColor = () => { }; // recursively sorts an object's keys in alphabetical order -// useful for comparing (deeply nested) objects in stringified form +// useful for comparing (deeply nested) objects in stringified form. +// probably would be better to use another dep specialized in deeply comparing objects? export const sortObject = (o: any) => Object.entries(o) .sort(([n1, v1], [n2, v2]) => n1 > n2 ? 1 : -1) .reduce((o, [n, v]) => ({...o, [n]: Object((v: any)) === v ? sortObject(v) : v}), {}); diff --git a/conda/js/src/store/actionTypes.js b/conda/js/src/store/actionTypes.js index 34c94a26d134f15d3095c5b41e2f3a6014447016..025b0004e8a98caa28bce12985400e2f0e65a2a7 100644 --- a/conda/js/src/store/actionTypes.js +++ b/conda/js/src/store/actionTypes.js @@ -1,4 +1,7 @@ // @flow +// the action types (constants) are defined here +// if you want to add one, youll need to add the constant string to the ActionType below +// and also add a line for the constant export type ActionType = 'FETCH_ALL' | 'SAVE_DATABASE' | 'ADD_DATABASE' | 'DELETE_DATABASE' | 'UPDATE_DATABASE' | 'SAVE_LIBRARY' | 'ADD_LIBRARY' | 'DELETE_LIBRARY' | 'UPDATE_LIBRARY' diff --git a/conda/js/src/store/actions.js b/conda/js/src/store/actions.js index 46e89a758fd0f527edf7a55dd8793fd3cc9a3bd5..c6cc7510af9e17a2e805d1a6b74f46af5fb6b4cd 100644 --- a/conda/js/src/store/actions.js +++ b/conda/js/src/store/actions.js @@ -1,4 +1,11 @@ // @flow +/* Actions for Redux + * all the actions for redux are defined here + * some actions are actually async action creators for async stuff (API calls) + * most are pretty simple and tied to a specific BEAT entity + * each entity has the same actions available, allowing lots of templating stuff + * these actions specify a type, using the constants in the ActionTypes +*/ import type { ActionType, } from './actionTypes'; @@ -7,7 +14,6 @@ import * as Types from './actionTypes.js'; import type { Dispatch } from 'redux'; import { genModuleApiFuncs } from '@helpers/api'; import { BEAT_ENTITIES } from '@helpers/beat'; -import { objToArr } from '@helpers'; import type { BeatEntity, BeatObject, BeatSettings, BeatEnvironment } from '@helpers/beat'; @@ -22,6 +28,19 @@ export type ActionCreator = (any, any) => Action; export type ActionThunkCreator = (any) => (dispatch: Dispatch<*>) => Promise<*>; +// helper to sort of flatten api response objects to arrays +const objToArr = (data: any): any[] => { + if(!data) + throw new Error(`Invalid data "${ data }"`); + + if(Array.isArray(data)) + return data; + return Object.entries(data) + .map(([k, obj]) => objToArr(obj)) + .reduce((a: any[], objs: any[]) => [...a, ...objs], []) + ; +}; + export const fetchAllObjects: ActionThunkCreator = () => async (dispatch: Dispatch<*>) => { //console.log(`fetchAllObjects`); const getFuncs = [...BEAT_ENTITIES].map(e => genModuleApiFuncs(e).get); diff --git a/conda/js/src/store/index.js b/conda/js/src/store/index.js index 1787e6975b70d1d52499f74ba23bdaaf0542758b..9ddcb5fdae43b736ef36660ee8eeb82667cfd071 100644 --- a/conda/js/src/store/index.js +++ b/conda/js/src/store/index.js @@ -1,4 +1,5 @@ // @flow +// builds the store and fetches the objects import { createStore, applyMiddleware, compose } from 'redux'; import reducer from './reducers'; import thunk from 'redux-thunk'; diff --git a/conda/js/src/store/reducers.js b/conda/js/src/store/reducers.js index a60ba5da808f1bdb62706b10eea555bf1ed7f77a..9e8ca439c9a8861c9debe69458e29e559df1598b 100644 --- a/conda/js/src/store/reducers.js +++ b/conda/js/src/store/reducers.js @@ -1,4 +1,8 @@ // @flow +/* Reducers for Redux + * all the reducers are here. + * since most actions are similar (all entities have the same basic actions available) there's alot of templating that happens. +*/ import { combineReducers } from 'redux'; import type { Action } from './actions.js'; diff --git a/conda/js/src/store/selectors.js b/conda/js/src/store/selectors.js index b0f3463b7242fc3936cf0b40ef4eac209170c863..7277cbb1a7e9183e772dc0f6292aec4ac69916d0 100644 --- a/conda/js/src/store/selectors.js +++ b/conda/js/src/store/selectors.js @@ -1,4 +1,7 @@ // @flow +/* State selectors for our Redux store via Reselect + * basically a bunch of memoized shortcuts to certain state/derived state +*/ import { createSelector } from 'reselect'; import type { State } from './reducers.js'; import { getTcFromExpName } from '@helpers/beat';