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

DSEGOG-129 save sessions #249

Merged
merged 16 commits into from
Aug 3, 2023
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
2 changes: 1 addition & 1 deletion .yarn/sdks/eslint/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "eslint",
"version": "8.34.0-sdk",
"version": "8.39.0-sdk",
"main": "./lib/api.js",
"type": "commonjs"
}
21 changes: 14 additions & 7 deletions cypress/integration/table/search.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -778,7 +778,8 @@ describe('Search', () => {
// the shot number, timeframe and experiment id

cy.findByLabelText('open experiment search box').click();
cy.findByRole('combobox').type('221').type('{downArrow}{enter}');
cy.findByRole('combobox').type('221');
cy.findByRole('combobox').type('{downArrow}{enter}');
cy.findByLabelText('close experiment search box').click();
cy.findByLabelText('open experiment search box')
.contains('ID 22110007')
Expand Down Expand Up @@ -908,7 +909,8 @@ describe('Search', () => {

it('searches within an experiment timeframe without the experiment id clearing', () => {
cy.findByLabelText('open experiment search box').click();
cy.findByRole('combobox').type('221').type('{downArrow}{enter}');
cy.findByRole('combobox').type('221');
cy.findByRole('combobox').type('{downArrow}{enter}');
cy.findByLabelText('close experiment search box').click();
cy.findByLabelText('open experiment search box')
.contains('ID 22110007')
Expand All @@ -933,7 +935,8 @@ describe('Search', () => {

it('clears experiment id when it searches outside the given experiment id experiment timeframe', () => {
cy.findByLabelText('open experiment search box').click();
cy.findByRole('combobox').type('221').type('{downArrow}{enter}');
cy.findByRole('combobox').type('221');
cy.findByRole('combobox').type('{downArrow}{enter}');
cy.findByLabelText('close experiment search box').click();
cy.findByLabelText('open experiment search box')
.contains('ID 22110007')
Expand All @@ -948,7 +951,8 @@ describe('Search', () => {
.should('not.exist');

cy.findByLabelText('open experiment search box').click();
cy.findByRole('combobox').type('221').type('{downArrow}{enter}');
cy.findByRole('combobox').type('221');
cy.findByRole('combobox').type('{downArrow}{enter}');
cy.findByLabelText('close experiment search box').click();
cy.findByLabelText('open experiment search box')
.contains('ID 22110007')
Expand All @@ -965,7 +969,8 @@ describe('Search', () => {

it('searches within an experiment shot number range without the experiment id clearing', () => {
cy.findByLabelText('open experiment search box').click();
cy.findByRole('combobox').type('221').type('{downArrow}{enter}');
cy.findByRole('combobox').type('221');
cy.findByRole('combobox').type('{downArrow}{enter}');
cy.findByLabelText('close experiment search box').click();
cy.findByLabelText('open experiment search box')
.contains('ID 22110007')
Expand Down Expand Up @@ -1008,7 +1013,8 @@ describe('Search', () => {

it('clears experiment id when it searches outside the given experiment id shot number range', () => {
cy.findByLabelText('open experiment search box').click();
cy.findByRole('combobox').type('221').type('{downArrow}{enter}');
cy.findByRole('combobox').type('221');
cy.findByRole('combobox').type('{downArrow}{enter}');
cy.findByLabelText('close experiment search box').click();
cy.findByLabelText('open experiment search box')
.contains('ID 22110007')
Expand All @@ -1032,7 +1038,8 @@ describe('Search', () => {
.should('not.exist');

cy.findByLabelText('open experiment search box').click();
cy.findByRole('combobox').type('221').type('{downArrow}{enter}');
cy.findByRole('combobox').type('221');
cy.findByRole('combobox').type('{downArrow}{enter}');
cy.findByLabelText('close experiment search box').click();
cy.findByLabelText('open experiment search box')
.contains('ID 22110007')
Expand Down
50 changes: 50 additions & 0 deletions cypress/integration/table/sessions.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
function getParamsFromUrl(url: string) {
const paramsString = url.split('?')[1];
const paramMap = new Map();
paramsString.split('&').forEach(function (part) {
const keyValPair = part.split('=');
const key = keyValPair[0];
const val = decodeURIComponent(keyValPair[1]);
paramMap.set(key, val);
});
return paramMap;
}

describe('Sessions', () => {
afterEach(() => {
cy.clearMocks();
});
it('sends a posts a request when a user session is created', () => {
cy.visit('/');

cy.findByTestId('AddCircleIcon').click();
cy.findByLabelText('Name *').type('Session');
cy.findByLabelText('Summary').type('Summary');

cy.startSnoopingBrowserMockedRequest();

cy.findByRole('button', { name: 'Save' }).click();

cy.findBrowserMockedRequests({ method: 'POST', url: '/sessions' }).should(
(patchRequests) => {
expect(patchRequests.length).equal(1);
const request = patchRequests[0];
expect(JSON.stringify(request.body)).equal(
'{"table":{"columnStates":{},"selectedColumnIds":["timestamp"],"page":0,"resultsPerPage":25,"sort":{}},"search":{"searchParams":{"dateRange":{},"shotnumRange":{},"maxShots":50,"experimentID":null}},"plots":{},"filter":{"appliedFilters":[[]]},"windows":{}}'
);

expect(request.url.toString()).to.contain('name=');
expect(request.url.toString()).to.contain('summary=');
expect(request.url.toString()).to.contain('auto_saved=');

const paramMap: Map<string, string> = getParamsFromUrl(
request.url.toString()
);

expect(paramMap.get('name')).equal('Session');
expect(paramMap.get('summary')).equal('Summary');
expect(paramMap.get('auto_saved')).equal('false');
}
);
});
});
40 changes: 40 additions & 0 deletions src/api/sessions.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { renderHook, waitFor } from '@testing-library/react';
import { useSaveSession } from './sessions';
import { Session } from '../app.types';
import { hooksWrapperWithProviders } from '../setupTests';

describe('session api functions', () => {
let mockData: Session;
beforeEach(() => {
mockData = {
name: 'test',
summary: 'test',
session_data: '{}',
auto_saved: false,
};
});
afterEach(() => {
jest.clearAllMocks();
});

describe('useSaveSession', () => {
it('posts a request to add a user session and returns successful response', async () => {
const { result } = renderHook(() => useSaveSession(), {
wrapper: hooksWrapperWithProviders(),
});
expect(result.current.isIdle).toBe(true);

result.current.mutate(mockData);

await waitFor(() => {
expect(result.current.isSuccess).toBeTruthy();
});

expect(result.current.data).toEqual('1');
});

it.todo(
'sends axios request to add a user session and throws an appropriate error on failure'
);
});
});
35 changes: 35 additions & 0 deletions src/api/sessions.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import axios, { AxiosError } from 'axios';
import { useMutation, UseMutationResult } from '@tanstack/react-query';
import { Session } from '../app.types';
import { useAppSelector } from '../state/hooks';
import { selectUrls } from '../state/slices/configSlice';
import { readSciGatewayToken } from '../parseTokens';

const saveSession = (apiUrl: string, session: Session): Promise<string> => {
const queryParams = new URLSearchParams();
queryParams.append('name', session.name);
queryParams.append('summary', session.summary);
queryParams.append('auto_saved', session.auto_saved.toString());

return axios
.post<string>(`${apiUrl}/sessions`, session.session_data, {
params: queryParams,
headers: {
Authorization: `Bearer ${readSciGatewayToken()}`,
},
})
.then((response) => response.data);
};

export const useSaveSession = (): UseMutationResult<
string,
AxiosError,
Session
> => {
const { apiUrl } = useAppSelector(selectUrls);
return useMutation((session: Session) => saveSession(apiUrl, session), {
onError: (error) => {
console.log('Got error ' + error.message);
},
});
};
8 changes: 8 additions & 0 deletions src/app.types.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { AccessTime, Numbers, Place, Science } from '@mui/icons-material';
import type { CartesianScaleTypeRegistry } from 'chart.js';
import { ImportSessionType } from './state/store';

export const MicroFrontendId = 'scigateway';
export const MicroFrontendToken = `${MicroFrontendId}:token`;
Expand Down Expand Up @@ -228,3 +229,10 @@ export const columnIconMappings = new Map()
.set('shotnum', <Numbers />)
.set('activeArea', <Place />)
.set('activeExperiment', <Science />);

export interface Session {
name: string;
summary: string;
auto_saved: boolean;
session_data: ImportSessionType;
}
10 changes: 10 additions & 0 deletions src/mocks/handlers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,16 @@ export const handlers = [
)
);
}),
rest.post('/sessions', async (req, res, ctx) => {
const sessionsParams = new URLSearchParams(req.url.search);
const sessionName = sessionsParams.get('name');

if (sessionName === 'test_dup') {
return res(ctx.status(409), ctx.json(''));
}
const sessionID = '1';
return res(ctx.status(200), ctx.json(sessionID));
}),
rest.get('/channels', (req, res, ctx) => {
return res(ctx.status(200), ctx.json(channelsJson));
}),
Expand Down
20 changes: 20 additions & 0 deletions src/search/searchBar.component.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -263,6 +263,26 @@ const SearchBar = (props: SearchBarProps): React.ReactElement => {
timeframeRange,
]);

// Updates the search fields when a session is loaded
React.useEffect(() => {
if (shotnumRange) {
setSearchParameterShotnumMax(shotnumRange.max);
setSearchParameterShotnumMin(shotnumRange.min);
}
if (experimentID) {
setSearchParameterExperiment(experimentID);
}

if (dateRange.fromDate && dateRange.toDate) {
const reduxSessionFromDate = new Date(dateRange.fromDate);
const reduxSessionToDate = new Date(dateRange.toDate);
setSearchParameterFromDate(reduxSessionFromDate);
setSearchParameterToDate(reduxSessionToDate);
}
if (maxShotsParam) {
setMaxShots(maxShotsParam);
}
}, [dateRange, experimentID, maxShotsParam, shotnumRange]);
// ##################################################
// Check for vaild Date Ranges and Shot Number Ranges
// ##################################################
Expand Down
30 changes: 30 additions & 0 deletions src/session/__snapshots__/sessionButtons.component.test.tsx.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`session buttons renders correctly 1`] = `
<DocumentFragment>
<div
class="MuiBox-root css-brvi3t"
>
<button
class="MuiButtonBase-root MuiButton-root MuiButton-outlined MuiButton-outlinedPrimary MuiButton-sizeMedium MuiButton-outlinedSizeMedium MuiButton-root MuiButton-outlined MuiButton-outlinedPrimary MuiButton-sizeMedium MuiButton-outlinedSizeMedium css-1rwt2y5-MuiButtonBase-root-MuiButton-root"
tabindex="0"
type="button"
>
Save
<span
class="MuiTouchRipple-root css-8je8zh-MuiTouchRipple-root"
/>
</button>
<button
class="MuiButtonBase-root MuiButton-root MuiButton-outlined MuiButton-outlinedPrimary MuiButton-sizeMedium MuiButton-outlinedSizeMedium MuiButton-root MuiButton-outlined MuiButton-outlinedPrimary MuiButton-sizeMedium MuiButton-outlinedSizeMedium css-1rwt2y5-MuiButtonBase-root-MuiButton-root"
tabindex="0"
type="button"
>
Save as
<span
class="MuiTouchRipple-root css-8je8zh-MuiTouchRipple-root"
/>
</button>
</div>
</DocumentFragment>
`;
56 changes: 56 additions & 0 deletions src/session/__snapshots__/sessionDrawer.component.test.tsx.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`session Drawer renders correctly 1`] = `
<DocumentFragment>
<div>
<div
class="MuiDrawer-root MuiDrawer-docked css-thgegd-MuiDrawer-docked"
>
<div
class="MuiPaper-root MuiPaper-elevation MuiPaper-elevation0 MuiDrawer-paper MuiDrawer-paperAnchorLeft MuiDrawer-paperAnchorDockedLeft css-1imhn6c-MuiPaper-root-MuiDrawer-paper"
>
<div
class="MuiBox-root css-1gsv261"
>
<div
class="MuiBox-root css-upq6er"
>
<p
class="MuiTypography-root MuiTypography-body1 css-rfi1dl-MuiTypography-root"
>
Workspaces
</p>
<div
class="MuiBox-root css-zdpt2t"
>
<button
class="MuiButtonBase-root MuiIconButton-root MuiIconButton-sizeMedium css-78trlr-MuiButtonBase-root-MuiIconButton-root"
tabindex="0"
type="button"
>
<svg
aria-hidden="true"
class="MuiSvgIcon-root MuiSvgIcon-fontSizeMedium css-i4bv87-MuiSvgIcon-root"
data-testid="AddCircleIcon"
focusable="false"
viewBox="0 0 24 24"
>
<path
d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm5 11h-4v4h-2v-4H7v-2h4V7h2v4h4v2z"
/>
</svg>
<span
class="MuiTouchRipple-root css-8je8zh-MuiTouchRipple-root"
/>
</button>
</div>
</div>
</div>
<div
class="MuiBox-root css-0"
/>
</div>
</div>
</div>
</DocumentFragment>
`;
Loading
Loading