Skip to content

Commit

Permalink
feat(ui): Use Context API to provide userContext and add ACL (centreo…
Browse files Browse the repository at this point in the history
  • Loading branch information
bdauria authored May 20, 2020
1 parent 5f0cf11 commit ec7e0ba
Show file tree
Hide file tree
Showing 13 changed files with 631 additions and 340 deletions.
12 changes: 9 additions & 3 deletions features/bootstrap/LanguageSelectionContext.php
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
<?php

use Centreon\Test\Behat\Administration\ParametersCentreonUiPage;
use Centreon\Test\Behat\Administration\ParametersMyAccountPage;
use Centreon\Test\Behat\Configuration\CurrentUserConfigurationPage;
use Centreon\Test\Behat\CentreonContext;

class LanguageSelectionContext extends CentreonContext
Expand All @@ -24,7 +22,15 @@ public function theUserWithAutologinEnabled()
public function selectTheLanguageDropdown()
{
$this->currentPage = new ParametersMyAccountPage($this);
$this->assertFind('css', 'select[name="contact_lang"]');

/* Wait for select2 returned values */
$this->spin(
function ($context) {
return (!empty($this->assertFind('css', 'select[name="contact_lang"]')));
},
'Cannot retrieve language list from select2',
5
);
}

/**
Expand Down
592 changes: 315 additions & 277 deletions package-lock.json

Large diffs are not rendered by default.

34 changes: 17 additions & 17 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,10 @@
"@babel/preset-typescript": "^7.9.0",
"@centreon/frontend-core": "centreon/frontend-core",
"@svgr/webpack": "^5.4.0",
"@testing-library/jest-dom": "^5.5.0",
"@testing-library/jest-dom": "^5.7.0",
"@testing-library/react": "^10.0.4",
"@testing-library/user-event": "^10.1.0",
"@types/ramda": "^0.27.4",
"@testing-library/user-event": "^10.3.1",
"@types/ramda": "^0.27.5",
"@types/react-router": "^5.1.7",
"babel-jest": "^26.0.1",
"babel-loader": "^8.1.0",
Expand All @@ -42,11 +42,11 @@
"eslint-config-prettier": "^6.11.0",
"eslint-import-resolver-alias": "1.1.2",
"eslint-plugin-babel": "^5.3.0",
"eslint-plugin-jest": "^23.9.0",
"eslint-plugin-jest": "^23.13.1",
"eslint-plugin-prefer-arrow-functions": "^3.0.1",
"eslint-plugin-prettier": "^3.1.3",
"file-loader": "^6.0.0",
"fork-ts-checker-webpack-plugin": "^4.1.3",
"fork-ts-checker-webpack-plugin": "^4.1.4",
"html-webpack-harddisk-plugin": "^1.0.1",
"html-webpack-plugin": "^4.3.0",
"jest": "^26.0.1",
Expand All @@ -64,10 +64,10 @@
"redux-mock-store": "^1.5.4",
"resolve-url-loader": "^3.1.1",
"sass-loader": "^8.0.2",
"terser-webpack-plugin": "^3.0.0",
"terser-webpack-plugin": "^3.0.1",
"thread-loader": "^2.1.3",
"ts-loader": "^7.0.2",
"typescript": "^3.8.3",
"ts-loader": "^7.0.4",
"typescript": "3.8.3",
"url-loader": "^4.1.0",
"webpack": "^4.43.0",
"webpack-bundle-analyzer": "^3.7.0",
Expand All @@ -78,26 +78,26 @@
"@centreon/ui": "github:centreon/centreon-ui",
"@date-io/moment": "1.3.13",
"@hot-loader/react-dom": "^16.13.0",
"@material-ui/core": "^4.9.13",
"@material-ui/core": "^4.9.14",
"@material-ui/icons": "^4.9.1",
"@material-ui/lab": "^4.0.0-alpha.52",
"@material-ui/lab": "^4.0.0-alpha.53",
"@material-ui/pickers": "^3.2.10",
"@material-ui/styles": "^4.9.13",
"@material-ui/styles": "^4.9.14",
"axios": "^0.19.2",
"classnames": "^2.2.6",
"clsx": "^1.1.0",
"connected-react-router": "^6.8.0",
"date-fns": "^2.12.0",
"dom-serializer": "^0.2.2",
"dompurify": "^2.0.10",
"date-fns": "^2.14.0",
"dom-serializer": "^1.0.1",
"dompurify": "^2.0.11",
"filesize": "^6.1.0",
"formik": "^2.1.4",
"html-react-parser": "^0.10.3",
"install": "^0.13.0",
"jsdom": "^16.2.2",
"loaders.css": "^0.1.2",
"moment": "2.25.3",
"moment-timezone": "^0.5.28",
"moment-timezone": "^0.5.31",
"numeral": "^2.0.6",
"query-string": "^6.12.1",
"ramda": "^0.27.0",
Expand All @@ -107,7 +107,7 @@
"react-fullscreen-crossbrowser": "^1.0.9",
"react-redux": "^7.2.0",
"react-redux-i18n": "^1.9.3",
"react-router-dom": "^5.1.2",
"react-router-dom": "^5.2.0",
"react-scripts": "^3.4.1",
"react-select": "^3.1.0",
"recharts": "^2.0.0-beta.2",
Expand All @@ -118,7 +118,7 @@
"redux-saga": "^1.1.3",
"redux-thunk": "^2.3.0",
"reselect": "^4.0.0",
"systemjs": "^6.3.1",
"systemjs": "^6.3.2",
"systemjs-plugin-css": "^0.1.37",
"yup": "^0.28.5"
},
Expand Down
37 changes: 0 additions & 37 deletions www/front_src/src/Provider.js

This file was deleted.

36 changes: 36 additions & 0 deletions www/front_src/src/Provider/UserContext.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import * as React from 'react';
import { UserContext } from './models';

const defaultUser = {
username: '',
locale: navigator.language,
timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
};

const defaultAcl = {
actions: {
host: {
check: false,
acknowledgement: false,
downtime: false,
},
service: {
check: false,
acknowledgement: false,
downtime: false,
},
},
};

const defaultContext = {
...defaultUser,
acl: defaultAcl,
};

const Context = React.createContext<UserContext>(defaultContext);

const useUserContext = (): UserContext => React.useContext(Context);

export default Context;

export { useUserContext, defaultUser, defaultAcl };
7 changes: 7 additions & 0 deletions www/front_src/src/Provider/endpoint.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
const legacyEndpoint = './api/internal.php';
const translationEndpoint = `${legacyEndpoint}?object=centreon_i18n&action=translation`;
const userEndpoint = `${legacyEndpoint}?object=centreon_topcounter&action=user`;
const baseEndpoint = './api/beta';
const aclEndpoint = `${baseEndpoint}/users/acl/actions`;

export { userEndpoint, translationEndpoint, aclEndpoint };
86 changes: 86 additions & 0 deletions www/front_src/src/Provider/index.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import * as React from 'react';

import axios from 'axios';

import { render, RenderResult, waitFor } from '@testing-library/react';
import AppProvider from '.';
import { useUserContext as mockUseUserContext } from './UserContext';
import { UserContext } from './models';

const mockedAxios = axios as jest.Mocked<typeof axios>;

const retrievedUser = {
username: 'admin',
timezone: 'Europe/Paris',
locale: 'en-EN',
};

const retrievedActionsAcl = {
host: {
check: true,
acknowledgement: true,
downtime: true,
},
service: {
check: true,
acknowledgement: true,
downtime: true,
},
};

const retrievedTranslations = {
en: {
hello: 'Hello',
},
};

let userContext: UserContext | null = null;

jest.mock('../App', () => {
const ComponentWithUserContext = (): JSX.Element => {
userContext = mockUseUserContext();

return <></>;
};

return {
__esModule: true,
default: ComponentWithUserContext,
};
});

const renderComponent = (): RenderResult => {
return render(<AppProvider />);
};

describe(AppProvider, () => {
beforeEach(() => {
mockedAxios.get
.mockResolvedValueOnce({
data: retrievedUser,
})
.mockResolvedValueOnce({
data: retrievedTranslations,
})
.mockResolvedValueOnce({
data: retrievedActionsAcl,
});
});

afterEach(() => {
userContext = null;
});

it('populates the userContext', async () => {
renderComponent();

await waitFor(() =>
expect(userContext).toEqual({
...retrievedUser,
acl: {
actions: retrievedActionsAcl,
},
}),
);
});
});
81 changes: 81 additions & 0 deletions www/front_src/src/Provider/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import * as React from 'react';

import { Provider } from 'react-redux';
import { pick, pathEq } from 'ramda';

import { useRequest, getData, Loader } from '@centreon/ui';
import {
loadTranslations,
setLocale,
syncTranslationWithStore,
} from 'react-redux-i18n';

import App from '../App';
import createStore from '../store';
import Context from './UserContext';
import { userEndpoint, translationEndpoint, aclEndpoint } from './endpoint';
import { User, Translations, Actions } from './models';
import useUser from './useUser';
import useAcl from './useAcl';

const store = createStore();

const AppProvider = (): JSX.Element | null => {
const { user, setUser } = useUser();
const { actionAcl, setActionAcl } = useAcl();
const [dataLoaded, setDataLoaded] = React.useState(false);

const { sendRequest: getUser } = useRequest<User>({
request: getData,
});
const { sendRequest: getTranslations } = useRequest<Translations>({
request: getData,
});
const { sendRequest: getAcl } = useRequest<Actions>({
request: getData,
});

React.useEffect(() => {
Promise.all([
getUser(userEndpoint),
getTranslations(translationEndpoint),
getAcl(aclEndpoint),
])
.then(([retrievedUser, retrievedTranslations, retrievedAcl]) => {
setUser(pick(['username', 'locale', 'timezone'], retrievedUser));
setActionAcl(retrievedAcl);

syncTranslationWithStore(store);
store.dispatch(loadTranslations(retrievedTranslations));
store.dispatch(setLocale(retrievedUser.locale?.slice(0, 2)));

setDataLoaded(true);
})
.catch((error) => {
if (pathEq(['response', 'status'], 401)(error)) {
window.location.href = 'index.php?disconnect=1';
}
});
}, []);

if (!dataLoaded) {
return <Loader fullContent />;
}

return (
<Context.Provider
value={{
...user,
acl: {
actions: actionAcl,
},
}}
>
<Provider store={store}>
<App />
</Provider>
</Context.Provider>
);
};

export default AppProvider;
Loading

0 comments on commit ec7e0ba

Please sign in to comment.