diff --git a/frontend/app/duck/index.js b/frontend/app/duck/index.js index 5ad487c93..051ec6933 100644 --- a/frontend/app/duck/index.js +++ b/frontend/app/duck/index.js @@ -7,12 +7,8 @@ import issues from './issues'; import assignments from './assignments'; import target from './target'; import targetCustom from './targetCustom'; -import runs from './runs'; import filters from './filters'; import funnelFilters from './funnelFilters'; -import tests from './tests'; -import steps from './steps'; -import schedules from './schedules'; import events from './events'; import environments from './environments'; import variables from './variables'; @@ -46,12 +42,9 @@ export default combineReducers({ assignments, target, targetCustom, - runs, filters, funnelFilters, - tests, - steps, - schedules, + events, environments, variables, diff --git a/frontend/app/duck/runs.js b/frontend/app/duck/runs.js deleted file mode 100644 index 30b8051ac..000000000 --- a/frontend/app/duck/runs.js +++ /dev/null @@ -1,7 +0,0 @@ -import Run from 'Types/run'; -import crudDuckGenerator from './tools/crudDuck'; - -const crudDuck = crudDuckGenerator('run', Run); -export const { fetchList, fetch, init, edit, save, remove } = crudDuck.actions; - -export default crudDuck.reducer; diff --git a/frontend/app/duck/schedules.js b/frontend/app/duck/schedules.js deleted file mode 100644 index 3f13e9188..000000000 --- a/frontend/app/duck/schedules.js +++ /dev/null @@ -1,14 +0,0 @@ -import Schedule from 'Types/schedule'; -import crudDuckGenerator from './tools/crudDuck'; - -const crudDuck = crudDuckGenerator('scheduler', Schedule); -export const { fetchList, fetch, init, edit, remove } = crudDuck.actions; - -export function save(instance) { // TODO: fix the crudDuckGenerator - return { - types: crudDuck.actionTypes.SAVE.toArray(), - call: client => client.post(`/schedulers${!!instance.schedulerId ? '/' + instance.schedulerId : '' }`, instance), - }; -} - -export default crudDuck.reducer; diff --git a/frontend/app/duck/steps.js b/frontend/app/duck/steps.js deleted file mode 100644 index 02bfdbb90..000000000 --- a/frontend/app/duck/steps.js +++ /dev/null @@ -1,77 +0,0 @@ -import { List, Map } from 'immutable'; -import { RequestTypes } from 'Duck/requestStateCreator'; -import Step from 'Types/step'; -import Event from 'Types/filter/event'; -import { getRE } from 'App/utils'; -import Test from 'Types/appTest'; -import { countries } from 'App/constants'; -import { KEYS } from 'Types/filter/customFilter'; - -const countryOptions = Object.keys(countries).map(c => ({filterKey: KEYS.USER_COUNTRY, label: KEYS.USER_COUNTRY, type: KEYS.USER_COUNTRY, value: c, actualValue: countries[c], isFilter: true })); - -const INIT = 'steps/INIT'; -const EDIT = 'steps/EDIT'; - -const SET_TEST = 'steps/SET_TEST'; -const FETCH_LIST = new RequestTypes('steps/FETCH_LIST'); - -const initialState = Map({ - list: List(), - test: Test(), - instance: Step(), - editingIndex: null, -}); - -const reducer = (state = initialState, action = {}) => { - switch (action.type) { - case FETCH_LIST.SUCCESS: { - return state.set('list', List(action.data).map(i => { - const type = i.type === 'navigate' ? i.type : 'location'; - return {...i, type: type.toUpperCase()} - })) - } - case INIT: - return state - .set('instance', Step(action.instance)) - .set('editingIndex', action.index) - .set('test', Test()); - case EDIT: - return state.mergeIn([ 'instance' ], action.instance); - case SET_TEST: - return state.set('test', Test(action.test)); - } - return state; -}; - -export default reducer; - -export function init(instance, index) { - return { - type: INIT, - instance, - index, - }; -} - -export function edit(instance) { - return { - type: EDIT, - instance, - }; -} - -export function setTest(test) { - return { - type: SET_TEST, - test, - }; -} - - -export function fetchList(params) { - return { - types: FETCH_LIST.toArray(), - call: client => client.get('/tests/steps/search', params), - params, - }; -} diff --git a/frontend/app/duck/tests/index.js b/frontend/app/duck/tests/index.js deleted file mode 100644 index eda7edc43..000000000 --- a/frontend/app/duck/tests/index.js +++ /dev/null @@ -1,186 +0,0 @@ -import { List, Map, Set } from 'immutable'; -import Test from 'Types/appTest'; -import stepFromJS from 'Types/step'; -import crudDuckGenerator from 'Duck/tools/crudDuck'; -import { reduceDucks } from 'Duck/tools'; -import runsDuck from './runs'; -import Run from 'Types/run'; - -const sampleRun = Run({"runId":8,"testId":7,"name":"test import","createdAt":1601481986264,"createdBy":283,"starter":"on-demand","state":"failed","steps":[{"label":"Open URL","order":0,"title":"navigate","status":"passed","startedAt":1601647536513,"finishedAt":1601647546211,"screenshot":"https://parrot-tests.s3.eu-central-1.amazonaws.com/115/7/8/screenshots/1601647546211.jpg","executionTime":9698},{"label":"Open URL","order":1,"title":"Visit OpenReplay","status":"passed","startedAt":1601647548354,"finishedAt":1601647556991,"screenshot":"https://parrot-tests.s3.eu-central-1.amazonaws.com/115/7/8/screenshots/1601647556991.jpg","executionTime":8637},{"info":"Unhandled promise rejection: TimeoutError: waiting for selector \"[name=\"email\"]\" failed: timeout 30000ms exceeded","input":"failed","label":"Send Keys to Element","order":2,"title":"input","status":"failed","startedAt":1601647559091,"finishedAt":1601647589099,"screenshot":"https://parrot-tests.s3.eu-central-1.amazonaws.com/115/7/8/screenshots/1601647589099.jpg","executionTime":30008}],"browser":"chrome","meta":{"startedAt":1601487715818},"location":"FR","startedAt":1601647524205,"finishedAt":1601647591217,"network":[{"url":"http://yahoo.fr/","method":"GET","duration":1760,"requestID":"769C483871CB3D35DF4BB1CD7D3258C4","timestamp":1601647537533,"requestHeaders":{"cookie":"key1=myvalue1","user-agent":"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/83.0.4103.0 Safari/537.36","upgrade-insecure-requests":"1"},"responseHeaders":null,"response":{"data":[{"key":"user_id","index":1},{"key":"virtual_number","index":2}]}},{"url":"http://fr.yahoo.com/","method":"GET","duration":1112,"requestID":"769C483871CB3D35DF4BB1CD7D3258C4","timestamp":1601647539293,"requestHeaders":{"cookie":"key1=myvalue1","user-agent":"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/83.0.4103.0 Safari/537.36","upgrade-insecure-requests":"1"},"responseHeaders":null,"payload":{"data":[{"key":"user_id","index":1},{"key":"virtual_number","index":2}]}},{"url":"https://fr.yahoo.com/","method":"GET","duration":1204,"requestID":"769C483871CB3D35DF4BB1CD7D3258C4","timestamp":1601647540405,"requestHeaders":{"cookie":"key1=myvalue1","user-agent":"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/83.0.4103.0 Safari/537.36","upgrade-insecure-requests":"1"},"responseHeaders":null},{"url":"https://guce.yahoo.com/consent?brandType=eu&gcrumb=bxFB6Ac&lang=fr-FR&done=https%3A%2F%2Ffr.yahoo.com%2F","method":"GET","duration":1173,"requestID":"769C483871CB3D35DF4BB1CD7D3258C4","timestamp":1601647541609,"requestHeaders":{"cookie":"key1=myvalue1","user-agent":"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/83.0.4103.0 Safari/537.36","upgrade-insecure-requests":"1"},"responseHeaders":null},{"url":"https://consent.yahoo.com/v2/collectConsent?sessionId=3_cc-session_fab600d0-8323-4b52-88c1-5698e6288f48","method":"GET","duration":1169,"requestID":"769C483871CB3D35DF4BB1CD7D3258C4","timestamp":1601647542782,"requestHeaders":{"cookie":"key1=myvalue1","user-agent":"Mozilla/5.0 (X11; Linux x86_64)AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/83.0.4103.0 Safari/537.36","upgrade-insecure-requests":"1"},"responseHeaders":null},{"url":"https://s.yimg.com/oa/build/css/site-ltr-b1aa14b0.css","method":"GET","duration":1179,"requestID":"56.2","timestamp":1601647543958,"requestHeaders":{"cookie":"key1=myvalue1","referer":"https://consent.yahoo.com/","user-agent":"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/83.0.4103.0 Safari/537.36"},"responseHeaders":null},{"url":"https://s.yimg.com/rz/p/yahoo_frontpage_en-US_s_f_p_bestfit_frontpage.png","method":"GET","duration":1189,"requestID":"56.3","timestamp":1601647543959,"requestHeaders":{"cookie":"key1=myvalue1","referer":"https://consent.yahoo.com/","user-agent":"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/83.0.4103.0 Safari/537.36"},"responseHeaders":null},{"url":"https://s.yimg.com/oa/build/js/site-ee81be05.js","method":"GET","duration":1194,"requestID":"56.5","timestamp":1601647543961,"requestHeaders":{"cookie":"key1=myvalue1","referer":"https://consent.yahoo.com/","user-agent":"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/83.0.4103.0 Safari/537.36"},"responseHeaders":null},{"url":"https://s.yimg.com/rz/p/yahoo_frontpage_en-US_s_f_w_bestfit_frontpage.png","method":"GET","duration":1189,"requestID":"56.4","timestamp":1601647543961,"requestHeaders":{"cookie":"key1=myvalue1","referer":"https://consent.yahoo.com/","user-agent":"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/83.0.4103.0 Safari/537.36"},"responseHeaders":null},{"url":"https://s.yimg.com/oa/build/images/fr-FR-home_11f60c18d02223c8.jpeg","method":"GET","duration":1068,"requestID":"56.7","timestamp":1601647545141,"requestHeaders":{"cookie":"key1=myvalue1","referer":"https://s.yimg.com/oa/build/css/site-ltr-b1aa14b0.css","user-agent":"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/83.0.4103.0 Safari/537.36"},"responseHeaders":null},{"url":"http://yahoo.fr/","method":"GET","duration":1312,"requestID":"7CA8FE6239B07643872BF48C30D639D3","timestamp":1601647549363,"requestHeaders":{"cookie":"key1=myvalue1","user-agent":"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/83.0.4103.0 Safari/537.36","upgrade-insecure-requests":"1"},"responseHeaders":null},{"url":"http://fr.yahoo.com/","method":"GET","duration":1005,"requestID":"7CA8FE6239B07643872BF48C30D639D3","timestamp":1601647550675,"requestHeaders":{"cookie":"key1=myvalue1","user-agent":"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/83.0.4103.0 Safari/537.36","upgrade-insecure-requests":"1"},"responseHeaders":null},{"url":"https://fr.yahoo.com/","method":"GET","duration":1037,"requestID":"7CA8FE6239B07643872BF48C30D639D3","timestamp":1601647551680,"requestHeaders":{"cookie":"key1=myvalue1","user-agent":"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/83.0.4103.0 Safari/537.36","upgrade-insecure-requests":"1"},"responseHeaders":null},{"url":"https://guce.yahoo.com/consent?brandType=eu&gcrumb=ESjhlqw&lang=fr-FR&done=https%3A%2F%2Ffr.yahoo.com%2F","method":"GET","duration":1045,"requestID":"7CA8FE6239B07643872BF48C30D639D3","timestamp":1601647552717,"requestHeaders":{"cookie":"key1=myvalue1","user-agent":"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/83.0.4103.0 Safari/537.36","upgrade-insecure-requests":"1"},"responseHeaders":null},{"url":"https://consent.yahoo.com/v2/collectConsent?sessionId=3_cc-session_3b367c91-9f88-498b-96a5-728947dda245","method":"GET","duration":1115,"requestID":"7CA8FE6239B07643872BF48C30D639D3","timestamp":1601647553762,"requestHeaders":{"cookie":"key1=myvalue1","user-agent":"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/83.0.4103.0 Safari/537.36","upgrade-insecure-requests":"1"},"responseHeaders":null},{"url":"https://s.yimg.com/oa/build/css/site-ltr-b1aa14b0.css","method":"GET","duration":1052,"requestID":"56.14","timestamp":1601647554885,"requestHeaders":{"cookie":"key1=myvalue1","referer":"https://consent.yahoo.com/","user-agent":"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/83.0.4103.0 Safari/537.36"},"responseHeaders":null},{"url":"https://s.yimg.com/rz/p/yahoo_frontpage_en-US_s_f_p_bestfit_frontpage.png","method":"GET","duration":1060,"requestID":"56.15","timestamp":1601647554886,"requestHeaders":{"cookie":"key1=myvalue1","referer":"https://consent.yahoo.com/","user-agent":"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/83.0.4103.0 Safari/537.36"},"responseHeaders":null},{"url":"https://s.yimg.com/oa/build/js/site-ee81be05.js","method":"GET","duration":1065,"requestID":"56.17","timestamp":1601647554886,"requestHeaders":{"cookie":"key1=myvalue1","referer":"https://consent.yahoo.com/","user-agent":"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/83.0.4103.0 Safari/537.36"},"responseHeaders":null},{"url":"https://s.yimg.com/rz/p/yahoo_frontpage_en-US_s_f_w_bestfit_frontpage.png","method":"GET","duration":1063,"requestID":"56.16","timestamp":1601647554886,"requestHeaders":{"cookie":"key1=myvalue1","referer":"https://consent.yahoo.com/","user-agent":"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/83.0.4103.0 Safari/537.36"},"responseHeaders":null},{"url":"https://s.yimg.com/oa/build/images/fr-FR-home_11f60c18d02223c8.jpeg","method":"GET","duration":1046,"requestID":"56.19","timestamp":1601647555944,"requestHeaders":{"cookie":"key1=myvalue1","referer":"https://s.yimg.com/oa/build/css/site-ltr-b1aa14b0.css","user-agent":"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/83.0.4103.0 Safari/537.36"},"responseHeaders":null}],"environmentId":null,"tenantId":115,"consoleLogs":[{"_type":"warning","_text":"A cookie associated with a resource at http://openreplay.com/ was set with `SameSite=None` but without `Secure`. It has been blocked, as Chrome now only delivers cookies marked `SameSite=None` if they are also marked `Secure`. You can review cookies in developer tools under Application>Storage>Cookies and see more details at https://www.chromestatus.com/feature/5633521622188032.","_args":[],"_location":{"url":"https://app.openreplay.com/"},"timestamp":1602089840909},{"_type":"warning","_text":"A cookie associated with a resource at http://app.openreplay.com/ was set with `SameSite=None` but without `Secure`. It has been blocked, as Chrome now only delivers cookies marked `SameSite=None` if they are also marked `Secure`. You can review cookies in developer tools under Application>Storage>Cookies and see more details at https://www.chromestatus.com/feature/5633521622188032.","_args":[],"_location":{"url":"https://app.openreplay.com/"},"timestamp":1602089840918}]}); - -const ADD_STEPS = 'tests/ADD_STEPS'; -const MOVE_STEP = 'tests/MOVE_STEP'; -const REMOVE_STEP = 'tests/REMOVE_STEP'; -const COPY_STEP = 'tests/COPY_STEP'; -const EDIT_STEP = 'tests/EDIT_STEP'; -const TOGGLE_STEP = 'tests/TOGGLE_STEP'; -const ADD_TAG = 'tests/ADD_TAG'; -const REMOVE_TAG = 'tests/REMOVE_TAG'; -const TOGGLE_TAG = 'tests/TOGGLE_TAG'; -const SET_MODIFIED = 'tests/SET_MODIFIED'; -const SET_QUERY = 'tests/SET_QUERY'; - -const MOVE_TEST = 'tests/MOVE_TEST'; - -const initialState = Map({ - tags: Set(), - query: '', - modified: false, - sampleRun: sampleRun, -}); - -const reducer = (state = initialState, action = {}) => { - switch (action.type) { - case SET_MODIFIED: - return state.set('modified', action.state); - case SET_QUERY: - return state.set('query', action.query); - case ADD_STEPS: - // TODO check frameworks - return state - .updateIn([ 'instance', 'steps' ], list => list.concat(action.steps.map(stepFromJS))).set('modified', true); - case MOVE_STEP: { - const { fromI, toI } = action; - return state - .updateIn([ 'instance', 'steps' ], list => - list.remove(fromI).insert(toI, list.get(fromI))).set('modified', true); - } - case REMOVE_STEP: - return state.removeIn([ 'instance', 'steps', action.index ]).set('modified', true); - case COPY_STEP: { - // Use fromJS to make another key. - const copiedStep = stepFromJS(state - .getIn([ 'instance', 'steps', action.index ]) - .set('imported', false)); - return state - .updateIn([ 'instance', 'steps' ], steps => - steps.insert(action.index + 1, copiedStep)).set('modified', true); - } - case EDIT_STEP: - return state.mergeIn([ 'instance', 'steps', action.index ], action.step).set('modified', true); - case TOGGLE_STEP: - return state.updateIn([ 'instance', 'steps', action.index, 'isDisabled' ], isDisabled => !isDisabled).set('modified', true); - case ADD_TAG: - return state.updateIn([ 'instance', 'tags' ], tags => tags.add(action.tag)).set('modified', true); - case REMOVE_TAG: - return state.updateIn([ 'instance', 'tags' ], tags => tags.remove(action.tag)).set('modified', true); - case TOGGLE_TAG: { - const { tag, flag } = action; - const adding = typeof flag === 'boolean' - ? flag - : !state.hasIn([ 'tags', tag ]); - return state.update('tags', tags => (adding - ? tags.add(tag) - : tags.remove(tag))); - } - case MOVE_TEST: { - const { fromI, toI } = action; - return state - .updateIn([ 'list' ], list => - list.remove(fromI).insert(toI, list.get(fromI))); - } - } - return state; -}; - -const crudDuck = crudDuckGenerator('test', Test); -export const { fetchList, fetch, init, edit, save, remove } = crudDuck.actions; -export { runTest, stopRun, checkRun, generateTest, stopAllRuns, resetErrors } from './runs'; -export default reduceDucks(crudDuck, { reducer, initialState }, runsDuck).reducer; - -export function addSteps(stepOrSteps) { - const steps = Array.isArray(stepOrSteps) || List.isList(stepOrSteps) - ? stepOrSteps - : [ stepOrSteps ]; - return { - type: ADD_STEPS, - steps, - }; -} - -export function moveStep(fromI, toI) { - return { - type: MOVE_STEP, - fromI, - toI, - }; -} - -export function removeStep(index) { - return { - type: REMOVE_STEP, - index, - }; -} - -export function copyStep(index) { - return { - type: COPY_STEP, - index, - }; -} - -export function editStep(index, step) { - return { - type: EDIT_STEP, - index, - step, - }; -} - -export function setModified(state) { - return { - type: SET_MODIFIED, - state, - }; -} - -export function toggleStep(index) { - return { - type: TOGGLE_STEP, - index, - }; -} - -export const addTag = (tag) => (dispatch) => { - return new Promise((resolve) => { - dispatch({ - type: ADD_TAG, - tag, - }) - resolve() - }) -} - -export const removeTag = (tag) => (dispatch) => { - return new Promise((resolve) => { - dispatch({ - type: REMOVE_TAG, - tag, - }); - resolve() - }) -} - -export function toggleTag(tag, flag) { - return { - type: TOGGLE_TAG, - tag, - flag, - }; -} - -export function setQuery(query) { - return { - type: SET_QUERY, - query - }; -} - -export function moveTest(fromI, toI) { - return { - type: MOVE_TEST, - fromI, - toI, - }; -} diff --git a/frontend/app/duck/tests/runs.js b/frontend/app/duck/tests/runs.js deleted file mode 100644 index e5f1ecc3d..000000000 --- a/frontend/app/duck/tests/runs.js +++ /dev/null @@ -1,118 +0,0 @@ -import { Map } from 'immutable'; -import Test from 'Types/appTest'; -import Run, { RUNNING, STOPPED } from 'Types/run'; -import requestDuckGenerator, { RequestTypes } from 'Duck/tools/requestDuck'; -import { reduceDucks } from 'Duck/tools'; - -const GEN_TEST = new RequestTypes('tests/GEN_TEST'); -const RUN_TEST = new RequestTypes('tests/RUN_TEST'); -const STOP_RUN = new RequestTypes('tests/STOP_RUN'); -const STOP_ALL_RUNS = new RequestTypes('tests/STOP_ALL_RUNS'); -const CHECK_RUN = new RequestTypes('tests/CHECK_RUN'); -const RESET_ERRORS = 'tests/RESET_ERRORS'; - -const updateRunInTest = run => (test) => { - const runIndex = test.runHistory - .findLastIndex(({ runId }) => run.runId === runId); - return runIndex === -1 - ? test.update('runHistory', list => list.push(run)) - : test.mergeIn([ 'runHistory', runIndex ], run); -}; - -const updateRun = (state, testId, run) => { - const testIndex = state.get('list').findIndex(test => test.testId === testId); - if (testIndex === -1) return state; - const updater = updateRunInTest(run); - return state - .updateIn([ 'list', testIndex ], updater) - .updateIn([ 'instance' ], test => (test.testId === testId - ? updater(test) - : test)); -}; - -const initialState = Map({}); - -const reducer = (state = initialState, action = {}) => { - switch (action.type) { - case GEN_TEST.SUCCESS: - return state.set('instance', Test(action.data).set('generated', true)); - case RUN_TEST.SUCCESS: { - const test = state.get('list').find(({ testId }) => testId === action.testId); - const run = Run({ - runId: action.data.id, state: RUNNING, testId: action.testId, name: test.name - }); - return updateRun(state, action.testId, run); - } - case STOP_RUN.SUCCESS: { - const { testId, runId } = action; - return updateRun(state, testId, { runId, state: STOPPED }); - } - case STOP_ALL_RUNS.SUCCESS: - return state.update('list', list => list.map(test => { - test.runHistory.map(run => run.state === RUNNING ? run.set('state', STOPPED) : run.state); - return test; - })).setIn(['runRequest', 'errors'], null); - case CHECK_RUN.SUCCESS: - return updateRun(state, action.testId, Run(action.data)); - case RESET_ERRORS: - return state.setIn(['runRequest', 'errors'], null); - } - return state; -}; - -const requestDuck = requestDuckGenerator({ - runRequest: RUN_TEST, - stopRunRequest: STOP_RUN, - stopAllRunsRequest: STOP_ALL_RUNS, - genTestRequest: GEN_TEST, -}); - -export default reduceDucks({ reducer, initialState }, requestDuck); - - -export function generateTest(sessionId, params) { - return { - types: GEN_TEST.toArray(), - call: client => client.post(`/sessions/${ sessionId }/gentest`, params), - }; -} - - -export function runTest(testId, params) { - return { - testId, - types: RUN_TEST.toArray(), - call: client => client.post(`/tests/${ testId }/execute`, params), - }; -} - -export function stopRun(testId, runId) { - return { - runId, - testId, - types: STOP_RUN.toArray(), - call: client => client.get(`/runs/${ runId }/stop`), - }; -} - -export function stopAllRuns() { - return { - types: STOP_ALL_RUNS.toArray(), - call: client => client.get(`/runs/all/stop`), - }; -} - -export function resetErrors() { - return { - type: RESET_ERRORS, - } -} - -export function checkRun(testId, runId) { - return { - runId, - testId, - types: CHECK_RUN.toArray(), - call: client => client.get(`/runs/${ runId }`), - }; -} diff --git a/frontend/app/types/run/index.js b/frontend/app/types/run/index.js deleted file mode 100644 index 658043461..000000000 --- a/frontend/app/types/run/index.js +++ /dev/null @@ -1,4 +0,0 @@ -import fromJS from './run'; - -export * from './run'; -export default fromJS; \ No newline at end of file diff --git a/frontend/app/types/run/run.js b/frontend/app/types/run/run.js deleted file mode 100644 index cfe35488e..000000000 --- a/frontend/app/types/run/run.js +++ /dev/null @@ -1,183 +0,0 @@ -import { Record, List, Map } from 'immutable'; -import { DateTime } from 'luxon'; -import Environment from 'Types/environment'; -import stepFromJS from './step'; -import seleniumStepFromJS from './seleniumStep'; -import Resource from '../session/resource'; - -export const NOT_FETCHED = undefined; -export const QUEUED = 'queued'; -export const INITIALIZING = 'initializing'; -export const RUNNING = 'running'; -export const COMPLETED = 'completed'; -export const PASSED = 'passed'; -export const FAILED = 'failed'; -export const STOPPED = 'stopped'; -export const CRASHED = 'crashed'; -export const EXPIRED = 'expired'; - -export const STATUS = { - NOT_FETCHED, - QUEUED, - INITIALIZING, - RUNNING, - COMPLETED, - PASSED, - FAILED, - STOPPED, - CRASHED, - EXPIRED, -} - -class Run extends Record({ - runId: undefined, - testId: undefined, - name: '', - tags: List(), - environment: Environment(), - scheduled: false, - schedulerId: undefined, - browser: undefined, - sessionId: undefined, - startedAt: undefined, - url_video: undefined, - finishedAt: undefined, - steps: List(), - resources: [], - seleniumSteps: List(), - url_browser_logs: undefined, - url_logs: undefined, - url_selenium_project: undefined, - sourceCode: undefined, - screenshotUrl: undefined, - clientId: undefined, - state: NOT_FETCHED, - baseRunId: undefined, - lastExecutedString: undefined, - durationString: undefined, - hour: undefined, // TODO: fine API - day: undefined, - location: undefined, - deviceType: undefined, - advancedOptions: undefined, - harfile: undefined, - lighthouseHtmlFile: undefined, - resultsFile: undefined, - lighthouseJsonFile: undefined, - totalStepsCount: undefined, - auditsPerformance: Map(), - auditsAd: Map(), - transferredSize: undefined, - resourcesSize: undefined, - domBuildingTime: undefined, - domContentLoadedTime: undefined, - loadTime: undefined, - starter: undefined, - // { - // "id": '', - // "title": '', - // "description": '', - // "score": 0, - // "scoreDisplayMode": '', - // "numericValue": 0, - // "numericUnit": '', - // "displayValue": '' - // } -}) { - idKey = 'runId'; - isRunning() { - return this.state === RUNNING; - } - isQueued() { - return this.state === QUEUED; - } - isPassed() { - return this.state === PASSED; - } -} - -// eslint-disable-next-line complexity -function fromJS(run = {}) { - if (run instanceof Run) return run; - - const startedAt = run.startedAt && DateTime.fromMillis(run.startedAt); - const finishedAt = run.finishedAt && DateTime.fromMillis(run.finishedAt); - let durationString; - let lastExecutedString; - if (run.state === 'running') { - durationString = 'Running...'; - lastExecutedString = 'Now'; - } else if (startedAt && finishedAt) { - const _duration = Math.floor(finishedAt - startedAt); - if (_duration > 10000) { - const min = Math.floor(_duration / 60000); - durationString = `${ min < 1 ? 1 : min } min`; - } else { - durationString = `${ Math.floor(_duration / 1000) } secs`; - } - const diff = startedAt.diffNow([ 'days', 'hours', 'minutes', 'seconds' ]).negate(); - if (diff.days > 0) { - lastExecutedString = `${ Math.round(diff.days) } day${ diff.days > 1 ? 's' : '' } ago`; - } else if (diff.hours > 0) { - lastExecutedString = `${ Math.round(diff.hours) } hrs ago`; - } else if (diff.minutes > 0) { - lastExecutedString = `${ Math.round(diff.minutes) } min ago`; - } else { - lastExecutedString = `${ Math.round(diff.seconds) } sec ago`; - } - } - - const steps = List(run.steps).map(stepFromJS); - const seleniumSteps = List(run.seleniumSteps).map(seleniumStepFromJS); - const tags = List(run.tags); - const environment = Environment(run.environment); - - let resources = List(run.network) - .map(i => Resource({ - ...i, - // success: 1, - // time: i.timestamp, - // type: 'xhr', - // headerSize: 1200, - // timings: {}, - })); - const firstResourceTime = resources.map(r => r.time).reduce((a,b)=>Math.min(a,b), Number.MAX_SAFE_INTEGER); - resources = resources - .map(r => r.set("time", r.time - firstResourceTime)) - .sort((r1, r2) => r1.time - r2.time).toArray() - - const screenshotUrl = run.screenshot_url || - seleniumSteps.find(({ screenshotUrl }) => !!screenshotUrl, null, {}).screenshotUrl; - - const state = run.state === 'completed' ? PASSED : run.state; - const networkOverview = run.networkOverview || {}; - - return new Run({ - ...run, - startedAt, - finishedAt, - durationString, - lastExecutedString, - steps, - resources, - seleniumSteps, - tags, - environment, - screenshotUrl, - state, - deviceType: run.device || run.deviceType, - auditsPerformance: run.lighthouseJson && run.lighthouseJson.performance, - auditsAd: run.lighthouseJson && run.lighthouseJson.ad, - transferredSize: networkOverview.transferredSize, - resourcesSize: networkOverview.resourcesSize, - domBuildingTime: networkOverview.domBuildingTime, - domContentLoadedTime: networkOverview.domContentLoadedTime, - loadTime: networkOverview.loadTime, - }); -} - -Run.prototype.exists = function () { - return this.runId !== undefined; -}; - -export default fromJS; diff --git a/frontend/app/types/run/seleniumStep.js b/frontend/app/types/run/seleniumStep.js deleted file mode 100644 index 5178240c7..000000000 --- a/frontend/app/types/run/seleniumStep.js +++ /dev/null @@ -1,29 +0,0 @@ -import { Record, List } from 'immutable'; -import { DateTime, Duration } from 'luxon'; - -const Step = Record({ - duration: undefined, - startedAt: undefined, - label: undefined, - input: undefined, - info: undefined, - order: undefined, - screenshotUrl: undefined, - steps: List(), -}); - -function fromJS(step = {}) { - const startedAt = step.startedAt && DateTime.fromMillis(step.startedAt * 1000); - const duration = step.executionTime && Duration.fromMillis(step.executionTime); - const steps = List(step.steps).map(Step); - const screenshotUrl = step.screenshot_url; - return new Step({ - ...step, - steps, - startedAt, - duration, - screenshotUrl, - }); -}; - -export default fromJS; \ No newline at end of file diff --git a/frontend/app/types/run/step.js b/frontend/app/types/run/step.js deleted file mode 100644 index 5358c0985..000000000 --- a/frontend/app/types/run/step.js +++ /dev/null @@ -1,31 +0,0 @@ -import { Record, List } from 'immutable'; -import { DateTime, Duration } from 'luxon'; - -const Step = Record({ - duration: undefined, - startedAt: undefined, - label: undefined, - input: undefined, - info: undefined, - order: undefined, - status: undefined, - title: undefined, - screenshotUrl: undefined, - steps: List(), -}); - -function fromJS(step = {}) { - const startedAt = step.startedAt && DateTime.fromMillis(step.startedAt); - const duration = step.executionTime && Duration.fromMillis(step.executionTime); - const steps = List(step.steps).map(Step); - const screenshotUrl = step.screenshot; - return new Step({ - ...step, - steps, - startedAt, - duration, - screenshotUrl, - }); -}; - -export default fromJS; \ No newline at end of file diff --git a/frontend/app/types/schedule.js b/frontend/app/types/schedule.js deleted file mode 100644 index 87b38eb6d..000000000 --- a/frontend/app/types/schedule.js +++ /dev/null @@ -1,228 +0,0 @@ -import { Record, List, Map } from 'immutable'; -import { DateTime } from 'luxon'; -import { - CHANNEL, - DAYS, - HOURS, - EMAIL, - SLACK, - WEBHOOK -} from 'App/constants/schedule'; -// import runFromJS from './run'; -import { validateEmail } from 'App/validate'; - -export const DEFAULT_ENV_VALUE = '_'; -const Schedule = Record({ - minutes: 30, - hour: 0, - day: -2, - testId: '', - sourceCode: '', - name: '', - nextExecutionTime: undefined, - numberOFExecutions: undefined, - schedulerId: undefined, - environmentId: DEFAULT_ENV_VALUE, - device: 'desktop', - locations: [], - - advancedOptions: false, - headers: [{}], - cookies: [{}], - basicAuth: {}, - network: 'wifi', - bypassCSP: false, - - slack: false, - slackInput: [], - webhook: false, - webhookInput: [], - email: false, - emailInput: [], - hasNotification: false, - options: Map({ message: [], device: 'desktop' }), - - extraCaps: {}, - - validateEvery() { - if (this.day > -2) return true; - return this.minutes >= 5 && this.minutes <= 1440; - }, - validateWebhookEmail() { - if (this.channel !== EMAIL) return true; - return validateEmail(this.webhookEmail); - }, - validateWebhook() { - if (this.channel !== WEBHOOK) return true; - return this.webhookId !== ''; - } -}); - -function fromJS(schedule = {}) { - if (schedule instanceof Schedule) return schedule; - const options = schedule.options || { message: [] }; - const extraCaps = options.extraCaps || { }; - - let channel = ''; - if (schedule.webhookEmail) { - channel = EMAIL; - } else if (schedule.webhookId && schedule.webhook) { - channel = schedule.webhook.type === 'slack' ? SLACK : WEBHOOK; - } - - const nextExecutionTime = schedule.nextExecutionTime ? - DateTime.fromMillis(schedule.nextExecutionTime) : undefined; - - - let { day, minutes } = schedule; - let hour; - if (day !== -2) { - const utcOffset = new Date().getTimezoneOffset(); - minutes = minutes - utcOffset - minutes = minutes >= 1440 ? (minutes - 1440) : minutes; - hour = Math.floor(minutes / 60); - } - // if (day !== -2) { - // const utcOffset = new Date().getTimezoneOffset(); - // const hourOffset = Math.floor(utcOffset / 60); - // const minuteOffset = utcOffset - 60*hourOffset; - - // minutes -= minuteOffset; - // hour -= hourOffset; - // if (day !== -1) { - // const dayOffset = Math.floor(hour/24); // +/-1 - // day = (day + dayOffset + 7) % 7; - // } - // hour = (hour + 24) % 24; - // } - - const slack = List(options.message).filter(i => i.type === 'slack'); - const email = List(options.message).filter(i => i.type === 'email'); - const webhook = List(options.message).filter(i => i.type === 'webhook'); - - const headers = extraCaps.headers ? Object.keys(extraCaps.headers).map(k => ({ name: k, value: extraCaps.headers[k] })) : [{}]; - const cookies = extraCaps.cookies ? Object.keys(extraCaps.cookies).map(k => ({ name: k, value: extraCaps.cookies[k] })) : [{}]; - - return new Schedule({ - ...schedule, - day, - minutes, - hour, - channel, - nextExecutionTime, - device: options.device, - options, - advancedOptions: !!options.extraCaps, - bypassCSP: options.bypassCSP, - network: options.network, - headers, - cookies, - basicAuth: extraCaps.basicAuth, - - slack: slack.size > 0, - slackInput: slack.map(i => parseInt(i.value)).toJS(), - - email: email.size > 0, - emailInput: email.map(i => i.value).toJS(), - - webhook: webhook.size > 0, - webhookInput: webhook.map(i => parseInt(i.value)).toJS(), - - hasNotification: !!slack || !!email || !!webhook - }); -} - -function getObjetctFromArr(arr) { - const obj = {} - for (var i = 0; i < arr.length; i++) { - const temp = arr[i]; - obj[temp.name] = temp.value - } - return obj; -} - -Schedule.prototype.toData = function toData() { - const { - name, schedulerId, environmentId, device, options, bypassCSP, network, headers, cookies, basicAuth - } = this; - - const js = this.toJS(); - options.device = device; - options.bypassCSP = bypassCSP; - options.network = network; - - options.extraCaps = { - headers: getObjetctFromArr(headers), - cookies: getObjetctFromArr(cookies), - basicAuth - }; - - if (js.slack && js.slackInput) - options.message = js.slackInput.map(i => ({ type: 'slack', value: i })) - if (js.email && js.emailInput) - options.message = options.message.concat(js.emailInput.map(i => ({ type: 'email', value: i }))) - if (js.webhook && js.webhookInput) - options.message = options.message.concat(js.webhookInput.map(i => ({ type: 'webhook', value: i }))) - - let day = this.day; - let hour = undefined; - let minutes = this.minutes; - if (day !== -2) { - const utcOffset = new Date().getTimezoneOffset(); - minutes = (this.hour * 60) + utcOffset; - // minutes += utcOffset; - minutes = minutes < 0 ? minutes + 1440 : minutes; - } - // if (day !== -2) { - // const utcOffset = new Date().getTimezoneOffset(); - // const hourOffset = Math.floor(utcOffset / 60); - // const minuteOffset = utcOffset - 60*hourOffset; - - // minutes = minuteOffset; - // hour = this.hour + hourOffset; - // if (day !== -1) { - // const dayOffset = Math.floor(hour/24); // +/-1 - // day = (day + dayOffset + 7) % 7; - // } - // hour = (hour + 24) % 24; - // } - - delete js.slack; - delete js.webhook; - delete js.email; - delete js.slackInput; - delete js.webhookInput; - delete js.emailInput; - delete js.hasNotification; - delete js.headers; - delete js.cookies; - - delete js.device; - delete js.extraCaps; - - // return { - // day, hour, name, minutes, schedulerId, environment, - // }; - return { ...js, day, hour, name, minutes, schedulerId, environmentId, options: options }; -}; - -Schedule.prototype.exists = function exists() { - return this.schedulerId !== undefined; -}; - -Schedule.prototype.valid = function validate() { - return this.validateEvery; -}; - -Schedule.prototype.getInterval = function getInterval() { - const DAY = List(DAYS).filter(item => item.value === this.day).first(); - - if (DAY.value === -2) { - return DAY.text + ' ' + this.minutes + ' Minutes'; // Every 30 minutes - } - - const HOUR = List(HOURS).filter(item => item.value === this.hour).first(); - return DAY.text + ' ' + HOUR.text; // Everyday/Sunday 2 AM; -}; - -export default fromJS; diff --git a/frontend/app/types/step.js b/frontend/app/types/step.js deleted file mode 100644 index 438b403d8..000000000 --- a/frontend/app/types/step.js +++ /dev/null @@ -1,152 +0,0 @@ -import { Record, List, Set, isImmutable } from 'immutable'; -import { TYPES as EVENT_TYPES } from 'Types/session/event'; - -export const CUSTOM = 'custom'; -export const CLICK = 'click'; -export const INPUT = 'input'; -export const NAVIGATE = 'navigate'; -export const TEST = 'test'; - -export const TYPES = { - CLICK, - INPUT, - CUSTOM, - NAVIGATE, - TEST, -}; - - -const Step = defaultValues => class extends Record({ - key: undefined, - name: '', - imported: false, - isDisabled: false, - importTestId: undefined, - ...defaultValues, -}) { - hasTarget() { - return this.type === CLICK || this.type === INPUT; - } - - isTest() { - return this.type === TEST; - } - - getEventType() { - switch (this.type) { - case INPUT: - return EVENT_TYPES.INPUT; - case CLICK: - return EVENT_TYPES.CLICK; - case NAVIGATE: - return EVENT_TYPES.LOCATION; - default: - return null; - } - } - - validate() { - const selectorsOK = this.selectors && this.selectors.size > 0; - const valueOK = this.value && this.value.trim().length > 0; - switch (this.type) { - case INPUT: - return selectorsOK; - case CLICK: - return selectorsOK; - case NAVIGATE: - return valueOK; - case CUSTOM: - // if (this.name.length === 0) return false; - /* if (window.JSHINT) { - window.JSHINT(this.code, { esversion: 6 }); - const noErrors = window.JSHINT.errors.every(({ code }) => code && code.startsWith('W')); - return noErrors; - } */ - return this.code && this.code.length > 0; - default: - return true; - } - } - - toData() { - const { - value, - ...step - } = this.toJS(); - delete step.key; - return { - values: value && [ value ], - ...step, - }; - } -}; - -const Custom = Step({ - type: CUSTOM, - code: '', - framework: 'any', - template: '', -}); - -const Click = Step({ - type: CLICK, - selectors: List(), - customSelector: true, -}); - -const Input = Step({ - type: INPUT, - selectors: List(), - value: '', - customSelector: true, -}); - -const Navigate = Step({ - type: NAVIGATE, - value: '', -}); - -const TestAsStep = Step({ - type: TEST, - testId: '', - name: '', - stepsCount: '', - steps: List(), -}); - -const EmptyStep = Step(); - -let uniqueKey = 0xff; -function nextKey() { - uniqueKey += 1; - return `${ uniqueKey }`; -} - -function fromJS(initStep = {}) { - // TODO: more clear - if (initStep.importTestId) return new TestAsStep(initStep).set('steps', List(initStep.steps ? initStep.steps : initStep.test.steps).map(fromJS)); - // todo: ? - if (isImmutable(initStep)) return initStep.set('key', nextKey()); - - const values = initStep.values && initStep.values.length > 0 && initStep.values[ 0 ]; - - // bad code - const step = { - ...initStep, - selectors: Set(initStep.selectors).toList(), // to List not nrcrssary. TODO: check - value: initStep.value ? [initStep.value] : values, - key: nextKey(), - isDisabled: initStep.disabled - }; - // bad code - - if (step.type === CUSTOM) return new Custom(step); - if (step.type === CLICK) return new Click(step); - if (step.type === INPUT) return new Input(step); - if (step.type === NAVIGATE) return new Navigate(step); - - return new EmptyStep(); - // throw new Error(`Unknown step type: ${step.type}`); -} - -export default fromJS;