Skip to content

Commit

Permalink
Merge pull request #1 from RPA-US/develop
Browse files Browse the repository at this point in the history
Public experiments, custom GUI components, profile info
  • Loading branch information
a8081 authored May 27, 2022
2 parents ce2b357 + 24ecf38 commit c154557
Show file tree
Hide file tree
Showing 28 changed files with 417 additions and 102 deletions.
11 changes: 9 additions & 2 deletions src/app/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,6 @@ import CssBaseline from '@mui/material/CssBaseline';
import Typography from '@mui/material/Typography';
import Container from '@mui/material/Container';
import Button from '@mui/material/Button';
import IconButton from '@mui/material/IconButton';
import LogoutIcon from '@mui/icons-material/Logout';
import { styled } from '@mui/system';
import { useTranslation } from "react-i18next";
import 'infrastructure/i18n/config';
Expand All @@ -35,12 +33,17 @@ import UserProfile from 'features/user/Profile';
import Theme from 'styles/theme';
import PrivateRoute from './helpers/PrivateRoute';
import PublicExperimentsList from 'features/experiment/PublicExperimentsList';
import LanguageSelector from 'features/language/languageSelector';

const StyledContainer = styled(Container)(({ theme }) => ({
backgroundColor: theme.palette.background.default,
marginTop: theme.spacing(3)
}))

const LanguageSelectorContainer = styled('div')(({ theme }) => ({
marginRight: theme.spacing(2)
}))

const NotificationsContainer = styled('div')(({ theme }) => ({
position: 'absolute',
top: '64px', // under header bar
Expand Down Expand Up @@ -76,6 +79,10 @@ function App() {
</Button>
<Spacer />

<LanguageSelectorContainer>
<LanguageSelector />
</LanguageSelectorContainer>

{
isAuth ? (
<UserMenu>
Expand Down
Binary file added src/assets/lang-thumbnails/english.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added src/assets/lang-thumbnails/spanish.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
25 changes: 24 additions & 1 deletion src/features/auth/slice.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,11 +51,14 @@ export const authSlice = createSlice({
setChecked: (state, { payload }: PayloadAction<boolean>) => {
state.checked = payload;
},
updateAuthUser: (state, { payload }: PayloadAction<User>) => {
state.currentUser = payload;
}
}
})


const { setAuthSuccess, setLogOut, setLoading, setAuthFailed, setRedirectPath, setChecked } = authSlice.actions
const { setAuthSuccess, setLogOut, setLoading, setAuthFailed, setRedirectPath, setChecked, updateAuthUser } = authSlice.actions

export const authSelector = (state: RootState) => state.auth;

Expand Down Expand Up @@ -142,6 +145,26 @@ export const logout = (redirectPath?: string): AppThunk => async (dispatch: AppD
localStorage.removeItem(SESSION_TOKEN_ITEM);
}

export const updateUser = (userData: FormData): AppThunk => async (dispatch: AppDispatch, getState) => {
if (userData == null) {
throw new Error('payload data cannot be null or undefined');
}
const { auth } = getState();
try {
const { currentUser } = auth;

userData.set('username', currentUser?.displayName ?? '');

dispatch(setLoading(true));
const response = await repository.saveUser(userData, auth.token ?? '');
dispatch(updateAuthUser(userDTOToUserType(response)));
} catch (error) {
console.error('error updating user data', error);
} finally {
dispatch(setLoading(false));
}
}

export const updateRedirectPath = (redirectPath: string = ''): AppThunk => async (dispatch: AppDispatch) => {
dispatch(setRedirectPath(redirectPath));
}
Expand Down
25 changes: 22 additions & 3 deletions src/features/experiment/Details.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,11 @@ import { authSelector } from 'features/auth/slice';

import BackButton from 'components/BackButton';
import { experimentsSelector, addExperiment, saveExperiment, experimentRepository, experimentsSlice} from './slice';
import { experimentDTOToExperimentType } from './utils';
import { experimentDTOToExperimentType, downloadFile, copyTextToClipboard, experimentToFormData } from './utils';
import ExperimentFormComponent from './Form';
import { ExperimentState } from './types';
import { downloadFile, copyTextToClipboard } from './utils';
import NotificationFactory from 'features/notifications/notification';
import { showNotification } from 'features/notifications/slice';

const downloadResults = async (experimentId: number, token: string) => {
try {
Expand Down Expand Up @@ -136,7 +137,25 @@ const ExperimentDetails: React.FC = () => {
<Switch
color="secondary"
checked={experiment.isPublic}
onChange={() => alert('publishExp()')}
onChange={() => {
const experimentData: any = experimentToFormData(experiment);
const newValue = !experiment.isPublic;
experimentData.set('public', newValue);
console.log('experimentToFormData', [...experimentData.entries()]);
dispatch(saveExperiment(experimentData, () => {
const notification = NotificationFactory.success(`Experiment "${experiment.name}" successfully ${newValue ? 'published' : 'unpublished'}`)
.dismissible()
.build();

setTimeout(() => {
dispatch(showNotification(notification));
setExperimentInList({
...experiment,
isPublic: newValue
})
}, 0)
}))
}}
inputProps={{ 'aria-label': t('features.experiment.details.publish') }}
/>
</Grid>
Expand Down
6 changes: 5 additions & 1 deletion src/features/experiment/ExperimentStatusChecker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ type QueuedChecker = {
id: number;
checkFunction: Function;
updaterFunction?: Function;
nCalls: number;
}

export default class ExperimentStatusChecker {
Expand All @@ -23,8 +24,11 @@ export default class ExperimentStatusChecker {
const { queue } = this;
queuedChecker = {
id: experiment.id,
nCalls: 0,
checkFunction: function () {
const checkStatus = (cb: Function) => {
this.nCalls ++;
const delay = Math.ceil(this.nCalls / 4);
setTimeout(async () => {
const newData = await retrievalByIdFn();
if (this.updaterFunction != null) {
Expand All @@ -37,7 +41,7 @@ export default class ExperimentStatusChecker {
if (index !== -1) queue.splice(index, 1);
onFinish != null && onFinish(newData);
}
}, configuration.CREATING_EXPERIMENT_RELOAD_TIME);
}, configuration.CREATING_EXPERIMENT_RELOAD_TIME * delay);
};

checkStatus(checkStatus);
Expand Down
2 changes: 1 addition & 1 deletion src/features/experiment/ExperimentsList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ const ExperimentsList: React.FC = () => {

<List
loadMoreFn={ () => loadExperiments() }
loadMoreDisabled={ experiments.length > 0 && pagination.hasNext }
loadMoreDisabled={ experiments.length > 0 && !pagination.hasNext }
isLoading={ isLoading }
experiments={ experiments }
downloadFn = { (id: number) => downloadResults(id, token ?? '') }
Expand Down
2 changes: 1 addition & 1 deletion src/features/experiment/PublicExperimentsList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ const PublicExperimentsList: React.FC = () => {
dispatch({ type: 'load' });
const page: number = state.hasNext ? state.page + 1 : state.page;
try {
const response = await experimentRepository.list(page, token ?? '');
const response = await experimentRepository.findPublic(page, token ?? '');
const list = response.results.map((exp: ExperimentDTO) => experimentDTOToExperimentType(exp));
dispatch({
type: 'load_success',
Expand Down
1 change: 1 addition & 0 deletions src/features/experiment/slice.ts
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,7 @@ export const saveExperiment = (experimentData: any, actionFinishedCallback: Func
const typedExperiment = experimentDTOToExperimentType(savedExperimentData);

if (executeMode === 'true') {
experimentData.set('id', typedExperiment.id);
experimentData.set('execute_mode', 'true');
try {
experimentRepository.save(experimentData, auth.token ?? '');
Expand Down
31 changes: 29 additions & 2 deletions src/features/experiment/utils.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { ExperimentDTO } from 'infrastructure/http/dto/experiment';
import { Experiment, ExperimentState } from './types';
import Papa from 'papaparse';
import { objectToFormData } from 'infrastructure/util/form';

export const downloadFile = function downloadFile(filename: string, blob: Blob) {
const url = window.URL.createObjectURL(new Blob([blob]));
Expand Down Expand Up @@ -47,11 +48,37 @@ export function experimentDTOToExperimentType(experiment: ExperimentDTO): Experi
sizeBalance: experiment.size_balance ?? undefined,
specialColnames: experiment.special_colnames ?? '',
status: '',
isPublic: true,
author: 'First Second LastName'
isPublic: experiment.public,
author: experiment.user != null ? `${experiment.user.first_name} ${experiment.user.last_name}` : undefined
}
}

export function experimentToFormData(experiment: Experiment): FormData {
if (experiment != null) {
return objectToFormData({
'id': `${experiment.id}`,
// 'created_at': experiment.creationDate,
'description': experiment.description,
// 'execution_finish': experiment.executionEnd,
// 'execution_start': experiment.executionStart,
'foldername': experiment.foldername,
// 'is_active': experiment.isActive ?? 'true',
// 'is_being_processed': experiment.isBeingProcessed ?? '0',
// 'last_edition': experiment.lastEditionDate,
'name': experiment.name,
// 'number_scenarios': experiment.numberScenarios ?? '0',
'scenarios_conf': experiment.scenariosConf,
'screenshot_name_generation_function': experiment.screenshotNameGenerationFunction,
'screenshots_path': experiment.screenshotsPath,
'size_balance': experiment.sizeBalance,
'special_colnames': experiment.specialColnames,
'status': experiment.status ?? '',
'variability_conf': experiment.variabilityConf
}, {})
}
return new FormData();
}

export function csvLogToJSON(seed: any, specialColnames: any): any {
const colnames = JSON.parse(specialColnames);
const screenshot_col_name = colnames["Screenshot"];
Expand Down
38 changes: 38 additions & 0 deletions src/features/language/languageSelector.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import React from 'react';
import { useTranslation } from 'react-i18next'

import { Avatar, MenuItem, Select } from '@mui/material';

import esThumbnail from 'assets/lang-thumbnails/spanish.png';
import enThumbnail from 'assets/lang-thumbnails/english.png';

const LanguageSelector: React.FC = () => {
const { i18n } = useTranslation();
const changeLanguage = (evt: any) => {
i18n.changeLanguage(evt.target.value);
};

console.log('i18n language', i18n.language)
return (
<Select
labelId="demo-simple-select-label"
id="demo-simple-select"
value={ i18n.language }
label="Age"
onChange={ changeLanguage }
sx={{
'&, & .MuiOutlinedInput-notchedOutline': {
border: 'none',
}
}}
>
<MenuItem value="es">
<Avatar alt="Cindy Baker" src={ esThumbnail } />
</MenuItem>
<MenuItem value="en">
<Avatar alt="Cindy Baker" src={ enThumbnail } />
</MenuItem>
</Select>
)
}
export default LanguageSelector;
4 changes: 2 additions & 2 deletions src/features/notifications/NotificationsBoard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ import { Theme, styled, Alert, AlertTitle, AlertColor, Collapse, IconButton, Pap
import { ThemeContext } from '@emotion/react';
import CloseIcon from '@mui/icons-material/Close';

import { notificationsSelector, hideNotification, removeNotification } from './slice';
import { NotificationType, Notification } from './types';
import { notificationsSelector, removeNotification } from './slice';
import { NotificationType } from './types';
import { NotificationAlert } from './notification';
import { Link } from 'react-router-dom';

Expand Down
27 changes: 7 additions & 20 deletions src/features/user/Form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,22 +27,9 @@ const UserForm: React.FC<UserFormProperties> = ({ onSubmit, disabled = false, in
valid = false;
setError(field, error);
}
/*// if (fileContents.seedLog == null) setError({ type: 'required', message: t('features.experiment.form.errors.seedLogRequired') as string });
if ((data.screenshots == null || data.screenshots.length < 1) && (initialValues.screenshotsPath == null || initialValues.screenshotsPath === '')) setFormError('screenshots', { type: 'required', message: t('features.experiment.form.errors.screenShotsRequired') as string });
if (fileContents.variability_conf == null && initialValues.variabilityConf == null) setFormError('variability_conf', { type: 'required', message: t('features.experiment.form.errors.variabilityRequired') as string });
if (Validations.isPositiveInteger(data.number_scenarios) && data.number_scenarios > 0 && fileContents.scenarios_conf == null && initialValues.scenariosConf == null) setFormError('scenarios_conf', { type: 'required', message: t('features.experiment.form.errors.scenarioRequired') as string });
if (!Validations.isPositiveInteger(data.number_scenarios)) setFormError('number_scenarios', { type: 'required', message: t('features.experiment.form.errors.scenariosNumberRequired') as string });
if (Validations.isBlank(data.logSize)) setFormError('logSize', { type: 'required', message: t('features.experiment.form.errors.logSizeRequired') as string });
if (Validations.isBlank(data.imbalancedCase)) {
setFormError('imbalancedCase', { type: 'required', message: t('features.experiment.form.errors.imbalancedCaseRequired') as string });
} else {
const tokenizedImbalanced = data.imbalancedCase.split(',');
if (tokenizedImbalanced.length === 1) setFormError('imbalancedCase', { type: 'required', message: t('features.experiment.form.errors.imbalancedCaseInvalidLength') as string });
const sum = tokenizedImbalanced.reduce((tot: number, curr: string) => tot + parseFloat(curr), 0);
if (sum != 1.0) setFormError('imbalancedCase', { type: 'required', message: t('features.experiment.form.errors.imbalancedCaseSumDistinctOfOne') as string });
}
if (Validations.isBlank(data.name)) setFormError('name', { type: 'required', message: t('features.experiment.form.errors.nameRequired') as string })
console.log('is form valid', valid, ', evalued data', data);*/

if (Validations.isBlank(data.email)) setFormError('email', { type: 'required', message: t('features.user.form.errors.emailRequired') as string });
// TODO FIX MAX LENGTH?
return valid;
}

Expand Down Expand Up @@ -106,7 +93,7 @@ const UserForm: React.FC<UserFormProperties> = ({ onSubmit, disabled = false, in
fullWidth
placeholder={t('features.user.form.firstName.placeholder')}
inputProps={
register('firstName', {
register('first_name', {
// required: t('features.experiment.form.errors.firstNameRequired') as string,
value: initialValues.firstName
})
Expand All @@ -129,7 +116,7 @@ const UserForm: React.FC<UserFormProperties> = ({ onSubmit, disabled = false, in
fullWidth
placeholder={t('features.user.form.lastName.placeholder')}
inputProps={
register('lastName', {
register('last_name', {
// required: t('features.experiment.form.errors.lastNameRequired') as string,
value: initialValues.lastName
})
Expand Down Expand Up @@ -164,7 +151,7 @@ const UserForm: React.FC<UserFormProperties> = ({ onSubmit, disabled = false, in
</TextInputContainer>
</FormInput>

<Typography variant="h6" style={{ margin: theme.spacing(2) }}>
{ /*<Typography variant="h6" style={{ margin: theme.spacing(2) }}>
Cambio de contraseña
</Typography>
<Typography variant="body1" style={{ marginLeft: theme.spacing(2) }}>
Expand Down Expand Up @@ -238,8 +225,8 @@ const UserForm: React.FC<UserFormProperties> = ({ onSubmit, disabled = false, in
</TextInputContainer>
</FormInput>
*/}
</CardContent>

<CardActions style={{ marginRight: theme.spacing(2) }}>
<Spacer />

Expand Down
3 changes: 1 addition & 2 deletions src/features/user/GUIComponentsCatalog/Component.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,7 @@ const GUIComponent: React.FC<GUIComponentProps> = (props) => {
try {
let blob;
if (thumbnail == null) {
let tmpSrc = `${path?.split('/').pop()}/${filename}`;
blob = await screenshotRepository.get(tmpSrc, token ?? '');
blob = await screenshotRepository.get(path ?? '', token ?? '');
} else {
blob = thumbnail;
}
Expand Down
Loading

0 comments on commit c154557

Please sign in to comment.