Skip to content
This repository has been archived by the owner on Sep 5, 2024. It is now read-only.

[#740] Waiting for available conversion hosts #807

Merged
merged 5 commits into from
Nov 28, 2018
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
1 change: 1 addition & 0 deletions .jest-setup.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,4 @@ global.n__ = str => str;
global.sprintf = str => str;
global.Jed = { sprintf: str => str };
global.API.get = jest.fn(() => Promise.resolve());
global.API.post = jest.fn(() => Promise.resolve());
15 changes: 13 additions & 2 deletions app/javascript/react/screens/App/Overview/Overview.js
Original file line number Diff line number Diff line change
Expand Up @@ -272,7 +272,10 @@ class Overview extends React.Component {
setMigrationsFilterAction,
initialMigrationsFilterSet,
acknowledgeDeniedPlanRequestAction,
isEditingPlanRequest
isEditingPlanRequest,
cancelPlanRequestAction,
isCancellingPlanRequest,
requestsProcessingCancellation
} = this.props;

const mainContent = (
Expand Down Expand Up @@ -313,7 +316,9 @@ class Overview extends React.Component {
fetchTransformationPlansAction={fetchTransformationPlansAction}
fetchTransformationPlansUrl={fetchTransformationPlansUrl}
fetchArchivedTransformationPlansUrl={fetchArchivedTransformationPlansUrl}
isFetchingTransformationPlans={isFetchingTransformationPlans}
isFetchingArchivedTransformationPlans={isFetchingArchivedTransformationPlans}
isFetchingAllRequestsWithTasks={isFetchingAllRequestsWithTasks}
archiveTransformationPlanAction={archiveTransformationPlanAction}
archiveTransformationPlanUrl={archiveTransformationPlanUrl}
deleteTransformationPlanAction={deleteTransformationPlanAction}
Expand All @@ -329,6 +334,9 @@ class Overview extends React.Component {
showEditPlanNameModalAction={showEditPlanNameModalAction}
acknowledgeDeniedPlanRequestAction={acknowledgeDeniedPlanRequestAction}
isEditingPlanRequest={isEditingPlanRequest}
cancelPlanRequestAction={cancelPlanRequestAction}
isCancellingPlanRequest={isCancellingPlanRequest}
requestsProcessingCancellation={requestsProcessingCancellation}
/>
) : (
<ShowWizardEmptyState
Expand Down Expand Up @@ -477,7 +485,10 @@ Overview.propTypes = {
openMappingWizardOnTransitionAction: PropTypes.func,
initialMigrationsFilterSet: PropTypes.bool,
acknowledgeDeniedPlanRequestAction: PropTypes.func,
isEditingPlanRequest: PropTypes.bool
isEditingPlanRequest: PropTypes.bool,
cancelPlanRequestAction: PropTypes.func,
isCancellingPlanRequest: PropTypes.bool,
requestsProcessingCancellation: PropTypes.array
};

Overview.defaultProps = {
Expand Down
19 changes: 18 additions & 1 deletion app/javascript/react/screens/App/Overview/OverviewActions.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@ import {
V2V_SET_MIGRATIONS_FILTER,
V2V_TOGGLE_SCHEDULE_MIGRATION_MODAL,
SHOW_PLAN_WIZARD_EDIT_MODE,
V2V_EDIT_PLAN_REQUEST
V2V_EDIT_PLAN_REQUEST,
V2V_CANCEL_PLAN_REQUEST
} from './OverviewConstants';

import { OPEN_V2V_MAPPING_WIZARD_ON_MOUNT } from '../Mappings/MappingsConstants';
Expand Down Expand Up @@ -288,3 +289,19 @@ export const acknowledgeDeniedPlanRequestAction = ({ plansUrl, planRequest }) =>
}
}
});

const _cancelPlanRequestActionCreator = url => dispatch =>
dispatch({
type: V2V_CANCEL_PLAN_REQUEST,
payload: {
promise: new Promise((resolve, reject) => {
API.post(url, { action: 'cancel' })
.then(response => resolve(response))
.catch(e => reject(e));
}),
data: url,
meta: { url }
}
});

export const cancelPlanRequestAction = (url, id) => _cancelPlanRequestActionCreator(new URI(`${url}/${id}`).toString());
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ export const V2V_RETRY_MIGRATION = 'V2V_RETRY_MIGRATION';
export const V2V_TOGGLE_SCHEDULE_MIGRATION_MODAL = 'V2V_TOGGLE_SCHEDULE_MIGRATION_MODAL';
export const V2V_SCHEDULE_MIGRATION = 'V2V_SCHEDULE_MIGRATION';
export const V2V_EDIT_PLAN_REQUEST = 'V2V_EDIT_PLAN_REQUEST';
export const V2V_CANCEL_PLAN_REQUEST = 'V2V_CANCEL_PLAN_REQUEST';
export const SHOW_CONFIRM_MODAL = 'SHOW_CONFIRM_MODAL';
export const HIDE_CONFIRM_MODAL = 'HIDE_CONFIRM_MODAL';
export const ARCHIVE_TRANSFORMATION_PLAN = 'ARCHIVE_TRANSFORMATION_PLAN';
Expand Down Expand Up @@ -67,3 +68,5 @@ export const FETCH_ARCHIVED_TRANSFORMATION_PLANS_URL =
'&attributes=name,description,miq_requests,options,created_at,transformation_mapping' +
'&sort_by=updated_at' +
'&sort_order=desc';

export const TRANSFORMATION_PLAN_REQUESTS_URL = '/api/requests';
30 changes: 28 additions & 2 deletions app/javascript/react/screens/App/Overview/OverviewReducer.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,8 @@ import {
V2V_TOGGLE_SCHEDULE_MIGRATION_MODAL,
V2V_SCHEDULE_MIGRATION,
SHOW_PLAN_WIZARD_EDIT_MODE,
V2V_EDIT_PLAN_REQUEST
V2V_EDIT_PLAN_REQUEST,
V2V_CANCEL_PLAN_REQUEST
} from './OverviewConstants';

import { planTransmutation, sufficientProviders } from './helpers';
Expand Down Expand Up @@ -92,7 +93,11 @@ export const initialState = Immutable({
initialMigrationsFilterSet: false,
isEditingPlanRequest: false,
isRejectedEditingPlanRequest: false,
errorEditingPlanRequest: null
errorEditingPlanRequest: null,
requestsProcessingCancellation: [],
isCancellingPlanRequest: false,
isRejectedCancelPlanRequest: false,
errorCancelPlanRequest: null
});

export default (state = initialState, action) => {
Expand Down Expand Up @@ -324,6 +329,27 @@ export default (state = initialState, action) => {
.set('isRejectedEditingPlanRequest', true)
.set('errorEditingPlanRequest', action.payload);

case `${V2V_CANCEL_PLAN_REQUEST}_PENDING`:
return state
.set('isCancellingPlanRequest', true)
.set('isRejectedCancelPlanRequest', false)
.set('requestsProcessingCancellation', [...state.requestsProcessingCancellation, action.payload]);
case `${V2V_CANCEL_PLAN_REQUEST}_FULFILLED`: {
return state
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Avoid too many return statements within this function.

.set('isCancellingPlanRequest', false)
.set('isRejectedCancelPlanRequest', false)
.set('errorCancelPlanRequest', null);
}
case `${V2V_CANCEL_PLAN_REQUEST}_REJECTED`:
return state
.set(
'requestsProcessingCancellation',
state.requestsProcessingCancellation.filter(url => action.meta.url !== url)
)
.set('isCancellingPlanRequest', false)
.set('isRejectedCancelPlanRequest', true)
.set('errorCancelPlanRequest', action.payload);

default:
return state;
}
Expand Down
18 changes: 18 additions & 0 deletions app/javascript/react/screens/App/Overview/OverviewSelectors.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import getMostRecentRequest from '../common/getMostRecentRequest';
import { urlBuilder } from './components/Migrations/helpers';
import { TRANSFORMATION_PLAN_REQUESTS_URL } from './OverviewConstants';

export const notStartedTransformationPlansFilter = transformationPlans =>
transformationPlans.filter(transformationPlan => transformationPlan.miq_requests.length === 0);
Expand Down Expand Up @@ -43,3 +45,19 @@ export const finishedWithErrorTransformationPlansFilter = transformationPlans =>
}
return false;
});

export const requestsProcessingCancellationFilter = transformationPlans =>
transformationPlans.reduce((requests, plan) => {
if (plan.miq_requests.length) {
const mostRecentRequest = getMostRecentRequest(plan.miq_requests);

if (mostRecentRequest.cancelation_status) {
return [...requests, urlBuilder(TRANSFORMATION_PLAN_REQUESTS_URL, mostRecentRequest.id)];
}
}
return requests;
}, []);

export const combineRequestsProcessingCancellation = (requestsFromMemory, requestsFromDb) => [
...new Set([...requestsFromMemory, ...requestsFromDb])
];
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import configureMockStore from 'redux-mock-store';
import thunk from 'redux-thunk';
import promiseMiddleware from 'redux-promise-middleware';
import { mockRequest, mockReset } from '../../../../../common/mockRequests';

import { fetchTransformationPlansAction, setMigrationsFilterAction } from '../OverviewActions';
import { mockRequest, mockReset } from '../../../../../common/mockRequests';
import { fetchTransformationPlansAction, setMigrationsFilterAction, cancelPlanRequestAction } from '../OverviewActions';
import { requestTransformationPlansData } from '../overview.transformationPlans.fixtures';
import { cancelRequestResponse } from '../overview.cancelRequest.fixtures';
import { TRANSFORMATION_PLAN_REQUESTS_URL } from '../OverviewConstants';

const middlewares = [thunk, promiseMiddleware()];
const mockStore = configureMockStore(middlewares);
Expand Down Expand Up @@ -78,3 +80,35 @@ describe('setMigrationsFilterAction', () => {
expect(store.getActions()).toMatchSnapshot();
});
});

describe('cancelPlanRequestAction', () => {
const request = {
method: 'POST',
data: { action: 'cancel' }
};

const action = cancelPlanRequestAction(TRANSFORMATION_PLAN_REQUESTS_URL, '1');

test('dispatches PENDING and FULFILLED actions', () => {
mockRequest({
...request,
status: 200,
response: { data: cancelRequestResponse }
});

return store.dispatch(action).then(() => {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Similar blocks of code found in 2 locations. Consider refactoring.

expect(store.getActions()).toMatchSnapshot();
});
});

test('dispatched PENDING and REJECTED actions', () => {
mockRequest({
...request,
status: 404
});

return store.dispatch(action).catch(() => {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Similar blocks of code found in 2 locations. Consider refactoring.

expect(store.getActions()).toMatchSnapshot();
});
});
});
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
import Immutable from 'seamless-immutable';

import { FETCH_V2V_TRANSFORMATION_PLANS, V2V_SET_MIGRATIONS_FILTER } from '../OverviewConstants';
import {
FETCH_V2V_TRANSFORMATION_PLANS,
V2V_SET_MIGRATIONS_FILTER,
V2V_CANCEL_PLAN_REQUEST
} from '../OverviewConstants';
import overviewReducer, { initialState } from '../OverviewReducer';
import { transformationPlans } from '../overview.transformationPlans.fixtures';
import { cancelRequestResponse } from '../overview.cancelRequest.fixtures';

const initialStateWithInfraMapping = Immutable({
...initialState,
Expand Down Expand Up @@ -73,3 +78,62 @@ test('sets the active migration filter', () => {

expect(state.migrationsFilter).toBe(activeFilter);
});

describe('cancelling a request', () => {
const url = '/api/requests/1';

test('is pending', () => {
const action = {
type: `${V2V_CANCEL_PLAN_REQUEST}_PENDING`,
payload: url
};

const state = overviewReducer(initialState, action);

expect(state).toEqual({
...initialState,
isCancellingPlanRequest: true,
requestsProcessingCancellation: [url]
});
});

test('is successful', () => {
const payload = {
data: cancelRequestResponse
};
const action = {
type: `${V2V_CANCEL_PLAN_REQUEST}_FULFILLED`,
payload
};
const prevState = Immutable({
...initialState,
isCancellingPlanRequest: true,
isRejectedCancelPlanRequest: true,
errorCancelPlanRequest: 'error'
});
const state = overviewReducer(prevState, action);

expect(state).toEqual(initialState);
});

test('is rejected', () => {
const action = {
type: `${V2V_CANCEL_PLAN_REQUEST}_REJECTED`,
payload: 'error',
meta: { url }
};
const prevState = Immutable({
...initialState,
isCancellingPlanRequest: true,
requestsProcessingCancellation: [url]
});
const state = overviewReducer(prevState, action);

expect(state).toEqual({
...initialState,
isRejectedCancelPlanRequest: true,
errorCancelPlanRequest: 'error',
requestsProcessingCancellation: []
});
});
});
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
import {
notStartedTransformationPlansFilter,
activeTransformationPlansFilter,
finishedTransformationPlansFilter
finishedTransformationPlansFilter,
requestsProcessingCancellationFilter
} from '../OverviewSelectors';
import { urlBuilder } from '../components/Migrations/helpers';

import { transformationPlans } from '../overview.transformationPlans.fixtures';
import { TRANSFORMATION_PLAN_REQUESTS_URL } from '../OverviewConstants';

const { resources: plans } = transformationPlans;

Expand All @@ -19,10 +22,10 @@ describe('notStartedTransformationPlansFilter', () => {

describe('activeTransformationPlansFilter', () => {
test('returns all active transformation plans', () => {
const [, activePlanOne, activePlanTwo, , , activePlanThree] = plans;
const [, activePlanOne, activePlanTwo, , , activePlanThree, activePlanFour] = plans;
const result = activeTransformationPlansFilter(plans);

expect(result).toEqual([activePlanOne, activePlanTwo, activePlanThree]);
expect(result).toEqual([activePlanOne, activePlanTwo, activePlanThree, activePlanFour]);
});
});

Expand All @@ -34,3 +37,14 @@ describe('finishedTransformationPlansFilter', () => {
expect(result).toEqual([finishedPlanOne, finishedPlanTwo]);
});
});

describe('requestsProcessingCancellationFilter', () => {
test('returns all requests processing cancellation', () => {
const plan = plans[6];
const [requestProcessingCancellation] = plan.miq_requests;
const url = urlBuilder(TRANSFORMATION_PLAN_REQUESTS_URL, requestProcessingCancellation.id);
const result = requestsProcessingCancellationFilter(plans);

expect(result).toEqual([url]);
});
});
Original file line number Diff line number Diff line change
@@ -1,5 +1,17 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`cancelPlanRequestAction dispatches PENDING and FULFILLED actions 1`] = `
Array [
Object {
"payload": "/api/requests/1",
"type": "V2V_CANCEL_PLAN_REQUEST_PENDING",
},
Object {
"type": "V2V_CANCEL_PLAN_REQUEST_FULFILLED",
},
]
`;

exports[`fetchTransformationPlansAction dispatches PENDING and FULFILLED actions 1`] = `
Array [
Object {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ Object {
"archiveTransformationPlanAction": [Function],
"archiveTransformationPlanUrl": "/api/service_templates",
"archivedTransformationPlans": Array [],
"cancelPlanRequestAction": [Function],
"clusters": Array [],
"createTransformationPlanRequestAction": [Function],
"datastores": Array [],
Expand Down Expand Up @@ -50,6 +51,7 @@ Object {
"planWizardVisible": false,
"redirectTo": [Function],
"removeNotificationAction": [Function],
"requestsProcessingCancellation": Array [],
"retryMigrationAction": [Function],
"scheduleMigration": [Function],
"setMigrationsFilterAction": [Function],
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import React from 'react';
import PropTypes from 'prop-types';
import { EmptyState, Spinner } from 'patternfly-react';

const CardEmptyState = ({ iconType, iconName, emptyStateInfo, emptyStateInfoStyles, showSpinner, spinnerStyles }) => (
<EmptyState>
{showSpinner && <Spinner loading size="lg" style={spinnerStyles} />}
{iconType && iconName && <EmptyState.Icon type={iconType} name={iconName} />}
{emptyStateInfo && <EmptyState.Info style={emptyStateInfoStyles}>{emptyStateInfo}</EmptyState.Info>}
</EmptyState>
);

CardEmptyState.propTypes = {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Similar blocks of code found in 11 locations. Consider refactoring.

emptyStateInfo: PropTypes.node,
emptyStateInfoStyles: PropTypes.object,
iconName: PropTypes.string,
iconType: PropTypes.string,
showSpinner: PropTypes.bool,
spinnerStyles: PropTypes.object
};

export default CardEmptyState;
Loading