diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 3e3f73d0f..000000000 --- a/.travis.yml +++ /dev/null @@ -1,17 +0,0 @@ -cache: - directories: - - node_modules -sudo: false -language: node_js -node_js: -- '6' -before_install: -- npm install -script: -- npm run build-css -- npm test -- npm run build -notifications: - slack: - rooms: - - secure: hLmNwol2KZ9D/2tvpVH6cpa0Ri07XthTc+92gyAIUnHKi8bOlLSuwUV28JumHxwMqcOeas5K1lq16nLt6K8eZzcwHVuUZRTPIwKJm96bnwS/z3TeTun2egnWxfnXotj+Bjrxjid9UtmZ9EnGUd1MfavVOn4q3O7u2kQHmBKbTSMOimmrjWcVjA1XBNNrj4Ec8I/9NZFmLmCzkzA2oyjEMfZ/RLvFSJz6JAdpomjMScIJ0W3abanZwXl6pN7ertfv6YSoPvmBKzjr28pdepDKHfRrQyN66OE+mLhVA2xFXrTAIVdeghN6F7U1XBMMRqvmgb2V8i449FVQc9e9Sjem7fYyOvZgg392Nef6xZ02zS/pNGZhXQbpnFrJ6HAl5ueFJGYaTAz0WGX+RQzzuTF78QIdg1Ye0sOXNpZeF+mnclQJvMVawq34F7NAWK0u+DGxLyhtGEx87wS/wVScseMD7dmBYHkBHGkznGJl+s+Uwsg0s+uK6beL9QgW3xifWbOnUzCif8BiPEU3xBUKpTdBFstSS96Ju1t+bltR6VGn/7OpQpoio1vwFmCdYxxlKrLGLP6A0Ybi7rkbVkXnSAjY6vW6/MJoE4W16otZiT65CB0qUH1TEhnD2+XhNkTnHbddRfzdqzd8T2DZNqk5V3IrtxnHjHtMbuLvvcdOYHKSAUc= \ No newline at end of file diff --git a/README.md b/README.md index 241a40706..70737ecee 100644 --- a/README.md +++ b/README.md @@ -105,8 +105,8 @@ You can find a guide to using it [here](https://github.com/facebookincubator/cre - [SASS](http://sass-lang.com/) - [React-i18nnext](https://github.com/i18next/react-i18next) -[build-badge]: https://img.shields.io/travis/Azure/pcs-remote-monitoring-webui.svg -[build-url]: https://travis-ci.org/Azure/pcs-remote-monitoring-webui +[build-badge]: https://solutionaccelerators.visualstudio.com/RemoteMonitoring/_apis/build/status/pcs-remote-monitoring-webui +[build-url]: https://solutionaccelerators.visualstudio.com/RemoteMonitoring/_build/latest?definitionId=32 [issues-badge]: https://img.shields.io/github/issues/azure/pcs-remote-monitoring-webui.svg [issues-url]: https://github.com/Azure/pcs-remote-monitoring-webui/issues/new [gitter-badge]: https://img.shields.io/gitter/room/azure/iot-solutions.js.svg diff --git a/docs/walkthrough/addNewDashboardPanel.md b/docs/walkthrough/addNewDashboardPanel.md index 01ddd0111..92db46777 100644 --- a/docs/walkthrough/addNewDashboardPanel.md +++ b/docs/walkthrough/addNewDashboardPanel.md @@ -5,10 +5,10 @@ The following is for creating a new panel called "**examplePanel**." 1. Create a folder named `examplePanel` inside the `components/pages/dashboard/panels` folder. 1. Create 3 files in the new folder. See the individual example files for more details and comments inline. - - [examplePanel.js](/src/components/pages/dashboard/panels/_examplePanel/examplePanel.js) - main component for the panel - - [examplePanel.scss](/src/components/pages/dashboard/panels/_examplePanel/examplePanel.scss) - styles for the new panel - - [index.js](/src/components/pages/dashboard/panels/_examplePanel/index.js) - exports for the new panel -1. Add the new panel to the main panel export file: [dashboard/panels/index.js](/src/components/pages/dashboard/panels/index.js). + - [examplePanel.js](/src/walkthrough/components/pages/dashboard/panels/examplePanel/examplePanel.js) - main component for the panel + - [examplePanel.scss](/src/walkthrough/components/pages/dashboard/panels/examplePanel/examplePanel.scss) - styles for the new panel + - [index.js](/src/walkthrough/components/pages/dashboard/panels/examplePanel/index.js) - exports for the new panel +1. Add the new panel to the main panel export file: [dashboard/panels/index.js](/src/walkthrough/components/pages/dashboard/panels/index.js). ```js export * from './examplePanel'; ``` @@ -18,7 +18,7 @@ The following is for creating a new panel called "**examplePanel**." "header": "Example Panel", }, ``` -1. In the [examplePanel.js](/src/components/pages/dashboard/panels/_examplePanel/examplePanel.js), import the `Panel` components. +1. In the [examplePanel.js](/src/walkthrough/components/pages/dashboard/panels/examplePanel/examplePanel.js), import the `Panel` components. ```js import { Panel, @@ -38,7 +38,7 @@ The following is for creating a new panel called "**examplePanel**." ``` -1. Add your panel to the [dashboard.js](/src/components/pages/dashboard/dashboard.js) page. Size the `Cell` for the panel according to how much space it will need. See [grid.scss](/src/components/pages/dashboard/grid/grid.scss) for the available grid-cell styles. +1. Add your panel to the [dashboard.js](/src/walkthrough/components/pages/dashboard/dashboard.js) page. Size the `Cell` for the panel according to how much space it will need. See [grid.scss](/src/components/pages/dashboard/grid/grid.scss) for the available grid-cell styles. ```jsx diff --git a/docs/walkthrough/addNewFlyout.md b/docs/walkthrough/addNewFlyout.md index a66ffa1c5..d217f4d6b 100644 --- a/docs/walkthrough/addNewFlyout.md +++ b/docs/walkthrough/addNewFlyout.md @@ -11,16 +11,16 @@ The following is for creating a new flyout called "**exampleFlyout**." ### Create the new flyout 1. Create a folder named `exampleFlyout` inside your page's `flyouts` folder. 1. Create 4 files in the new folder. See the individual example files for more details and comments inline. - - [exampleFlyout.container.js](/src/components/pages/_flyoutExample/flyouts/exampleFlyout/exampleFlyout.container.js) - maps redux and epic actions and selectors to the props for the flyout - - [exampleFlyout.js](/src/components/pages/_flyoutExample/flyouts/exampleFlyout/exampleFlyout.js) - main component for the flyout - - [exampleFlyout.scss](/src/components/pages/_flyoutExample/flyouts/exampleFlyout/exampleFlyout.scss) - styles for the flyout - - [index.js](/src/components/pages/_flyoutExample/flyouts/exampleFlyout/index.js) - exports for the new flyout + - [exampleFlyout.container.js](/src/walkthrough/components/pages/pageWithFlyout/flyouts/exampleFlyout/exampleFlyout.container.js) - maps redux and epic actions and selectors to the props for the flyout + - [exampleFlyout.js](/src/walkthrough/components/pages/pageWithFlyout/flyouts/exampleFlyout/exampleFlyout.js) - main component for the flyout + - [exampleFlyout.scss](/src/walkthrough/components/pages/pageWithFlyout/flyouts/exampleFlyout/exampleFlyout.scss) - styles for the flyout + - [index.js](/src/walkthrough/components/pages/pageWithFlyout/flyouts/exampleFlyout/index.js) - exports for the new flyout ### Setup the flyout -1. Open your flyout's container file [exampleFlyout.container.js](/src/components/pages/_flyoutExample/flyouts/exampleFlyout/exampleFlyout.container.js) so the data and actions can be connected to the page props. +1. Open your flyout's container file [exampleFlyout.container.js](/src/walkthrough/components/pages/pageWithFlyout/flyouts/exampleFlyout/exampleFlyout.container.js) so the data and actions can be connected to the page props. - To keep our example simple, no actions are mapped. But in a real world scenario, you would very likely need this. See the [Add a New Grid walkthrough](addNewGrid.md) for more information on mapping data and actions via a `container.js.` -1. Open your flyout's file [exampleFlyout.js](/src/components/pages/_flyoutExample/flyouts/exampleFlyout/exampleFlyout.js). Use the various Flyout components to ensure consistency with others. Then, add whatever components are needed inside `FlyoutContent`. Notice that the FlyoutCloseBtn provides a way to close the flyout. +1. Open your flyout's file [exampleFlyout.js](/src/walkthrough/components/pages/pageWithFlyout/flyouts/exampleFlyout/exampleFlyout.js). Use the various Flyout components to ensure consistency with others. Then, add whatever components are needed inside `FlyoutContent`. Notice that the FlyoutCloseBtn provides a way to close the flyout. ```jsx @@ -38,7 +38,7 @@ The following is for creating a new flyout called "**exampleFlyout**." ``` ### Open the flyout from a page -1. Open your page's file [flyoutExample.js](/src/components/pages/_flyoutExample/flyoutExample.js). +1. Open your page's file [pageWithFlyout.js](/src/walkthrough/components/pages/pageWithFlyout/pageWithFlyout.js). 1. In the render method, add a context button to open the flyout. ```jsx @@ -66,7 +66,7 @@ The following is for creating a new flyout called "**exampleFlyout**." ## More Advanced Topics ### Do some action and show progress -Often, a flyout will be used to call a service to create/update/delete something. See the [exampleFlyout.js](/src/components/pages/_flyoutExample/flyouts/exampleFlyout/exampleFlyout.js) file for an example of +Often, a flyout will be used to call a service to create/update/delete something. See the [exampleFlyout.js](/src/walkthrough/components/pages/pageWithFlyout/flyouts/exampleFlyout/exampleFlyout.js) file for an example of - using `SummarySection` to indicate how many items will be affected - showing an `Indicator` (progress spinner) while an action is in progress - showing an `Svg` checkmark when the action is complete diff --git a/docs/walkthrough/addNewGrid.md b/docs/walkthrough/addNewGrid.md index c153a4108..ebe4c4df2 100644 --- a/docs/walkthrough/addNewGrid.md +++ b/docs/walkthrough/addNewGrid.md @@ -15,12 +15,12 @@ Grids in remote monitoring are based on [ag-grid][ag-grid], with our own customi ### Create the new grid 1. Create a folder named `exampleGrid` inside your page's folder. 1. Create 3 files in the new folder. See the individual example files for more details and comments inline. - - [exampleGrid.js](/src/components/pages/_gridExample/exampleGrid/exampleGrid.js) - main component for the grid, sets up context buttons and soft/hard selection event handlers, wraps [pcsGrid][pcsGrid] - - [exampleGridConfig.js](/src/components/pages/_gridExample/exampleGrid/exampleGridConfig.js) - configuration such as column definitions for the grid - - [index.js](/src/components/pages/_gridExample/exampleGrid/index.js) - exports for the new grid + - [exampleGrid.js](/src/walkthrough/components/pages/pageWithGrid/exampleGrid/exampleGrid.js) - main component for the grid, sets up context buttons and soft/hard selection event handlers, wraps [pcsGrid][pcsGrid] + - [exampleGridConfig.js](/src/walkthrough/components/pages/pageWithGrid/exampleGrid/exampleGridConfig.js) - configuration such as column definitions for the grid + - [index.js](/src/walkthrough/components/pages/pageWithGrid/exampleGrid/index.js) - exports for the new grid ### Setup the page -1. Open your page's container file [gridExample.container.js](/src/components/pages/_gridExample/gridExample.container.js) so the data and actions can be connected to the page props. +1. Open your page's container file [pageWithGrid.container.js](/src/walkthrough/components/pages/pageWithGrid/pageWithGrid.container.js) so the data and actions can be connected to the page props. 1. Map the data from the redux store to props. ```js const mapStateToProps = state => ({ @@ -38,11 +38,11 @@ Grids in remote monitoring are based on [ag-grid][ag-grid], with our own customi ``` 1. Connect the data and actions to the page component. ```js - export const GridExampleContainer = translate()(connect(mapStateToProps, mapDispatchToProps)(GridExample)); + export const PageWithGridContainer = translate()(connect(mapStateToProps, mapDispatchToProps)(PageWithGrid)); ``` - Notice the use of [i18next][i18next]'s translate method. This will pass an additional prop called `t` containing the translated strings for use in the page. -1. Open your page's file [gridExample.js](/src/components/pages/_gridExample/gridExample.js) so the grid and refresh bar can be added. +1. Open your page's file [pageWithGrid.js](/src/walkthrough/components/pages/pageWithGrid/pageWithGrid.js) so the grid and refresh bar can be added. 1. Import your grid as well as other components like `AjaxError` and `RefreshBar`. ```js import { AjaxError, RefreshBar } from 'components/shared'; @@ -57,8 +57,8 @@ Grids in remote monitoring are based on [ag-grid][ag-grid], with our own customi if (!lastUpdated && !isPending) fetchData(); } ``` - - Alternatively, if the the data is useful on other pages as well, then it can be loaded in the "APP_INITIALIZE" epic in [appReducer.js](../store/reducers/appReducer.js) -1. In render, set up the props for the grid. Choose the columnDefs to show from those configured in [exampleGridConfig.js](/src/components/pages/_gridExample/exampleGrid/exampleGridConfig.js). + - Alternatively, if the the data is useful on other pages as well, then it can be loaded in the "APP_INITIALIZE" epic in [appReducer.js](/src/store/reducers/appReducer.js) +1. In render, set up the props for the grid. Choose the columnDefs to show from those configured in [exampleGridConfig.js](/src/walkthrough/components/pages/pageWithGrid/exampleGrid/exampleGridConfig.js). ```js const { t, data, error, isPending, lastUpdated, fetchData } = this.props; const gridProps = { @@ -135,7 +135,7 @@ The user may need to act on mulitple rows at the same time. Checking a row's che ### Soft Select Rows The user may need to act on a single row. A soft select link can be configured for one or more columns in the columnDefs. -1. In [exampleGridConfig.js](/src/components/pages/_gridExample/exampleGrid/exampleGridConfig.js), add `SoftSelectLinkRenderer` as the cellRendererFramework for a columnDef. +1. In [exampleGridConfig.js](/src/walkthrough/components/pages/pageWithGrid/exampleGrid/exampleGridConfig.js), add `SoftSelectLinkRenderer` as the cellRendererFramework for a columnDef. ```js export const exampleColumnDefs = { id: { diff --git a/docs/walkthrough/addNewPage.md b/docs/walkthrough/addNewPage.md index b9fa77cbc..a219226fd 100644 --- a/docs/walkthrough/addNewPage.md +++ b/docs/walkthrough/addNewPage.md @@ -1,47 +1,62 @@ Walkthrough: Adding a New Page =========================== -The following is for creating a new page called "**example**." +The following is for creating a new page called "**basicPage**." 1. Create a folder named `example` inside the `components/pages` folder. 1. Create 4 files in the new folder. See the individual example files for more details and comments inline. - - [example.container.js](/src/components/pages/_example/example.container.js) - maps redux and epic actions and selectors to the props for the page - - [example.js](/src/components/pages/_example/example.js) - main component for the page - - [example.scss](/src/components/pages/_example/example.scss) - styles for the page - - [example.test.js](/src/components/pages/_example/example.test.js) - basic rendering test -1. Add the new page's container to the [components/pages/index.js](/src/components/pages/index.js) file. + - [basicPage.container.js](/src/walkthrough/components/pages/basicPage/basicPage.container.js) - maps redux and epic actions and selectors to the props for the page + - [basicPage.js](/src/walkthrough/components/pages/basicPage/basicPage.js) - main component for the page + - [basicPage.scss](/src/walkthrough/components/pages/basicPage/basicPage.scss) - styles for the page + - [basicPage.test.js](/src/walkthrough/components/pages/basicPage/basicPage.test.js) - basic rendering test +1. Add the new page's container to the [components/pages/index.js](/src/walkthrough/components/pages/index.js) file. ```js - export * from './example/example.container'; + export * from './basicPage/basicPage.container'; ``` 1. (Optional) Add an SVG icon for the new page. See [utilities/README.md](../utilities/README.md) for more information. - Note that existing SVGs can be used as well. 1. Add the page name to the translations file, [translations.json](../../public/locales/en/translations.json). [i18next][i18next] is used for internationalization. ```json "tabs": { - "template": "Template", + "example": "Example", }, ``` -1. Open the top level application page, [components/app/app.js](/src/components/app/app.js). +1. Open the top level application page, [walkthrough/components/app.js](/src/components/app.js). 1. Add the new page to the imports. ```javascript // Page Components import { //... - TemplateContainer as TemplatePage + BasicPageContainer } from 'components/pages'; ``` -1. Add a navigation tab, refernce the SVG icon added previously. +1. Add the new page to the `pagesConfig`. Set the to address for the route, reference the SVG icon and translations added previously, and set the component to the page's container. ```js - const templateTab = { to: '/template', svg: svgs.tabs.template, labelId: 'tabs.template' }; + const pagesConfig = [ + //... + { + to: '/basicpage', + exact: true, + svg: svgs.tabs.example, + labelId: 'walkthrough.tabs.basicPage', + component: BasicPageContainer + }, + //... + ]; ``` -1. Add the new navigation tab to tabConfigs. +1. Add any new breadcrumbs to the `crumbsConfig`. ```js - const tabConfigs = [ dashboardTab, devicesTab, rulesTab, maintenanceTab, templateTab ]; + const crumbsConfig = [ + //... + { + path: '/basicpage', crumbs: [ + { to: '/basicpage', labelId: 'walkthrough.tabs.basicPage' } + ] + }, + //... + ]; ``` -1. In the render method, add the route for the new page. - ```jsx - - ``` + - This example page only has one breadcrumb to be shown, but some pages will have more. See [components/app.js](/src/components/app.js) for more examples. 1. **Congratulations!** Run the application to see your new page in action. 1. Now, you can edit the page to do what you want. diff --git a/docs/walkthrough/addNewService.md b/docs/walkthrough/addNewService.md index 13eaca548..c635263b4 100644 --- a/docs/walkthrough/addNewService.md +++ b/docs/walkthrough/addNewService.md @@ -7,16 +7,16 @@ Services in remote monitoring are called using [rxjs][rxjs] Observables. ### Create the Service -1. Create [exampleModels.js](/src/services/models/_exampleModels.js) for the service under the `services/models` folder. +1. Create [exampleModels.js](/src/walkthrough/services/models/exampleModels.js) for the service under the `services/models` folder. - See the models [README](/src/services/models/README.md) for more information on the purpose of these models and general naming conventions. - - Don't forget to add your new file to the exports in [index.js](/src/services/models/index.js) -1. Create [exampleService.js](/src/services/_exampleService.js) in the `services` folder. - - Use [services/httpClient.js](/src/services/httpClient.js) to make calls to the services. Then, transform the response using the models. + - Don't forget to add your new file to the exports in [index.js](/src/walkthrough/services/models/index.js) +1. Create [exampleService.js](/src/walkthrough/services/exampleService.js) in the `services` folder. + - Use [utilities/httpClient.js](/src/utilities/httpClient.js) to make calls to the services. Then, transform the response using the models. - Note that the example service does not call actual services. Instead, it returns hardcoded data as an observable to mimick service calls. - - Don't forget to add your new file to the exports in [index.js](/src/services/index.js) + - Don't forget to add your new file to the exports in [index.js](/src/walkthrough/services/index.js) ### Set up the Service in the store -1. Create [exampleReducer.js](/src/store/reducers/_exampleReducer.js) in the `store/reducers/` folder. +1. Create [exampleReducer.js](/src/walkthrough/store/reducers/exampleReducer.js) in the `store/reducers/` folder. 1. Add [redux-observable][redux-obs] epics to make service calls. ```js export const epics = createEpicScenario({ @@ -80,9 +80,9 @@ Services in remote monitoring are called using [rxjs][rxjs] Observables. ## Configure the Middleware -1. Add the reducer to the [rootReducer.js](/src/store/rootReducer.js) in the `store` folder. +1. Add the reducer to the [rootReducer.js](/src/walkthrough/store/rootReducer.js) in the `store` folder. ```js - import { reducer as exampleReducer } from './reducers/_exampleReducer'; + import { reducer as exampleReducer } from './reducers/exampleReducer'; const rootReducer = combineReducers({ ...exampleReducer, @@ -90,9 +90,9 @@ Services in remote monitoring are called using [rxjs][rxjs] Observables. }); ``` -1. Add the epics to [rootEpic.js](/src/store/rootEpic.js) in the `store` folder. +1. Add the epics to [rootEpic.js](/src/walkthrough/store/rootEpic.js) in the `store` folder. ```js - import { epics as exampleEpics } from './reducers/_exampleReducer'; + import { epics as exampleEpics } from './reducers/exampleReducer'; const epics = [ ...exampleEpics.getEpics(), diff --git a/public/locales/en/translations.json b/public/locales/en/translations.json index 5f741db95..93d630fbf 100644 --- a/public/locales/en/translations.json +++ b/public/locales/en/translations.json @@ -17,10 +17,7 @@ "devices": "Devices", "rules": "Rules", "maintenance": "Maintenance", - "packages": "Packages", - "example": "Page Example", - "gridExample": "Grid Example", - "flyoutExample": "Flyout Example" + "packages": "Packages" }, "errorFormat": "Error: {{message}}", "errorCode": { @@ -583,15 +580,17 @@ "edgemanifest": "Edge Manifest" } }, - "examples": { - "pagePlaceholder": "This is a new page.", - "grid": { - "name": "Name", - "description": "Description", - "btn1": "Button 1", - "btn2": "Button 2" + "walkthrough": { + "tabs": { + "dashboard": "Dashboard", + "basicPage": "Basic Page", + "pageWithFlyout": "Page With Flyout", + "pageWithGrid": "Page With Grid" }, - "flyout": { + "basicPage": { + "pagePlaceholder": "This is a new page." + }, + "pageWithFlyout": { "title": "Flyout Example", "pageBody": "Click the context button above to open a flyout.", "open": "Open Flyout", @@ -610,6 +609,22 @@ } } }, + "pageWithGrid": { + "grid": { + "name": "Name", + "description": "Description", + "btn1": "Button 1", + "btn2": "Button 2" + } + }, + "dashboard": { + "panels": { + "example": { + "header": "Example Panel", + "panelBody": "This is a new panel." + } + } + }, "panel": { "header": "Example Panel", "panelBody": "This is a new panel." diff --git a/src/app.config.js b/src/app.config.js index ccf4072a6..f83340c4e 100644 --- a/src/app.config.js +++ b/src/app.config.js @@ -43,7 +43,11 @@ const Config = { closed: 'closed', acknowledged: 'acknowledged' }, - maxLogoFileSizeInBytes: 307200 + maxLogoFileSizeInBytes: 307200, + deviceType: { + simulated: 'Simulated', + physical: 'Physical' + } }; export default Config; diff --git a/src/components/app/app.container.js b/src/components/app.container.js similarity index 84% rename from src/components/app/app.container.js rename to src/components/app.container.js index aceaf4ee5..aff1c5938 100644 --- a/src/components/app/app.container.js +++ b/src/components/app.container.js @@ -22,6 +22,4 @@ const mapDispatchToProps = dispatch => ({ logout: () => AuthService.logout() }); -const AppContainer = withRouter(translate()(connect(mapStateToProps, mapDispatchToProps)(App))); - -export default AppContainer; +export const AppContainer = withRouter(translate()(connect(mapStateToProps, mapDispatchToProps)(App))); diff --git a/src/components/app.js b/src/components/app.js new file mode 100644 index 000000000..7f46ce9d4 --- /dev/null +++ b/src/components/app.js @@ -0,0 +1,134 @@ +// Copyright (c) Microsoft. All rights reserved. + +import React, { Component } from 'react'; + +import { svgs } from 'utilities'; +import Shell from "components/shell/shell"; +import { ManageDeviceGroupsContainer, SettingsContainer } from 'components/shell/flyouts'; +import { DashboardContainer, DevicesContainer, RulesContainer, MaintenanceContainer, PackagesContainer } from './pages'; + +class App extends Component { + + constructor(props) { + super(props); + + this.state = { openFlyout: '' }; + } + + closeFlyout = () => this.setState({ openFlyout: '' }); + + openSettings = () => this.setState({ openFlyout: 'settings' }); + + render() { + const { deviceGroupFlyoutIsOpen } = this.props; + const { openFlyout } = this.state; + + const pagesConfig = [ + { + to: '/dashboard', + exact: true, + svg: svgs.tabs.dashboard, + labelId: 'tabs.dashboard', + component: DashboardContainer + }, + { + to: '/devices', + exact: true, + svg: svgs.tabs.devices, + labelId: 'tabs.devices', + component: DevicesContainer + }, + { + to: '/rules', + exact: true, + svg: svgs.tabs.rules, + labelId: 'tabs.rules', + component: RulesContainer + }, + { + to: '/packages', + exact: true, + svg: svgs.tabs.packages, + labelId: 'tabs.packages', + component: PackagesContainer + }, + { + to: '/maintenance', + exact: false, + svg: svgs.tabs.maintenance, + labelId: 'tabs.maintenance', + component: MaintenanceContainer + } + ]; + + const crumbsConfig = [ + { + path: '/dashboard', crumbs: [ + { to: '/dashboard', labelId: 'tabs.dashboard' } + ] + }, + { + path: '/devices', crumbs: [ + { to: '/devices', labelId: 'tabs.devices' } + ] + }, + { + path: '/rules', crumbs: [ + { to: '/rules', labelId: 'tabs.rules' } + ] + }, + { + path: '/packages', crumbs: [ + { to: '/packages', labelId: 'tabs.packages' } + ] + }, + { + path: '/maintenance', crumbs: [ + { to: '/maintenance', labelId: 'tabs.maintenance' } + ] + }, + { + path: '/maintenance/notifications', crumbs: [ + { to: '/maintenance', labelId: 'tabs.maintenance' }, + { to: '/maintenance/notifications', labelId: 'maintenance.notifications' } + ] + }, + { + path: '/maintenance/rule/:id', crumbs: [ + { to: '/maintenance', labelId: 'tabs.maintenance' }, + { to: '/maintenance/notifications', labelId: 'maintenance.notifications' }, + { to: '/maintenance/rule/:id', matchParam: 'id' }, + ] + }, + { + path: '/maintenance/jobs', crumbs: [ + { to: '/maintenance', labelId: 'tabs.maintenance' }, + { to: '/maintenance/jobs', labelId: 'maintenance.jobs' } + ] + }, + { + path: '/maintenance/job/:id', crumbs: [ + { to: '/maintenance', labelId: 'tabs.maintenance' }, + { to: '/maintenance/jobs', labelId: 'maintenance.jobs' }, + { to: '/maintenance/rule/:id', matchParam: 'id' } + ] + } + ]; + + const shellProps = { + pagesConfig, + crumbsConfig, + openSettings: this.openSettings, + ...this.props + }; + + return ( + + {deviceGroupFlyoutIsOpen && } + {openFlyout === 'settings' && } + + ); + } +} + +export default App; diff --git a/src/components/app/app.js b/src/components/app/app.js deleted file mode 100644 index 002842633..000000000 --- a/src/components/app/app.js +++ /dev/null @@ -1,113 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. - -import React, { Component } from 'react'; -import { Route, Redirect, Switch } from 'react-router-dom'; - -import Config from 'app.config'; -import { SettingsContainer, ManageDeviceGroupsContainer } from './flyouts'; - -// App Components -import Header from './header/header'; -import NavigationContainer from './navigation/navigationContainer'; -import Main from './main/main'; - -// Page Components -import { - DashboardContainer as DashboardPage, - DevicesContainer as DevicesPage, - RulesContainer as RulesPage, - MaintenanceContainer as MaintenancePage, - PackagesContainer as PackagesPage, - ExampleContainer as ExamplePage, - FlyoutExampleContainer as FlyoutExamplePage, - GridExampleContainer as GridExamplePage, - PageNotFoundContainer as PageNotFound -} from 'components/pages'; - -import { svgs } from 'utilities'; - -import './app.css'; - -/** The navigation tab configurations */ -const dashboardTab = { to: '/dashboard', svg: svgs.tabs.dashboard, labelId: 'tabs.dashboard' }; -const devicesTab = { to: '/devices', svg: svgs.tabs.devices, labelId: 'tabs.devices' }; -const rulesTab = { to: '/rules', svg: svgs.tabs.rules, labelId: 'tabs.rules' }; -const maintenanceTab = { to: '/maintenance', svg: svgs.tabs.maintenance, labelId: 'tabs.maintenance' }; -const packagesTab = { to: '/packages', svg: svgs.tabs.packages, labelId: 'tabs.packages' }; -const exampleTab = { to: '/example', svg: svgs.tabs.example, labelId: 'tabs.example' }; -const flyoutExampleTab = { to: '/flyoutexample', svg: svgs.tabs.example, labelId: 'tabs.flyoutExample' }; -const gridExampleTab = { to: '/gridexample', svg: svgs.tabs.example, labelId: 'tabs.gridExample' }; -const tabConfigs = [dashboardTab, devicesTab, rulesTab, packagesTab, maintenanceTab]; - -/** Only show example pages and components when configured to do so */ -if (Config.showWalkthroughExamples) { - tabConfigs.push(exampleTab); - tabConfigs.push(flyoutExampleTab); - tabConfigs.push(gridExampleTab); -} - -class WalkthroughExampleRoute extends Component { - render() { - const { component: Component, ...props } = this.props - - return ( - ( - Config.showWalkthroughExamples ? - : - - )} - /> - ) - } -} - -/** The base component for the app */ -class App extends Component { - - constructor(props) { - super(props); - - this.state = { openFlyout: '' }; - } - - componentDidMount() { - const { history, registerRouteEvent } = this.props; - // Initialize listener to inject the route change event into the epic action stream - history.listen(({ pathname }) => registerRouteEvent(pathname)); - } - - closeFlyout = () => this.setState({ openFlyout: '' }); - - openSettings = () => this.setState({ openFlyout: 'settings' }); - - render() { - return ( -
-
- -
-
- - - - - - - - - - - - - {this.props.deviceGroupFlyoutIsOpen && } - {this.state.openFlyout === 'settings' && } -
-
-
- ); - } -} - -export default App; diff --git a/src/components/app/header/breadcrumbs.js b/src/components/app/header/breadcrumbs.js deleted file mode 100644 index 076458c81..000000000 --- a/src/components/app/header/breadcrumbs.js +++ /dev/null @@ -1,36 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. - -import React from 'react'; -import { Route, Switch, NavLink } from 'react-router-dom'; - -import { Svg } from 'components/shared'; -import { svgs } from 'utilities'; - -const Crumb = ({ children }) =>
{ children }
-const Chevron = () => ; - -const DashboardCrumbs = ({ t }) => {t('tabs.dashboard')}; -const DevicesCrumbs = ({ t }) => {t('tabs.devices')}; -const RulesCrumbs = ({ t }) => {t('tabs.rules')}; -const MaintenanceCrumbs = ({ t }) => {t('tabs.maintenance')}; -const RuleDetailsCrumbs = ({ t, match }) => [ - {t('tabs.maintenance')}, - , - {match.params.id} -]; -const JobDetailsCrumbs = ({ t, match }) => [ - {t('tabs.maintenance')}, - , - {match.params.id} -]; - -export const Breadcrumbs = ({ t }) => ( - - } /> - } /> - } /> - } /> - } /> - } /> - -); diff --git a/src/components/pages/_example/example.container.js b/src/components/pages/_example/example.container.js deleted file mode 100644 index 6bd7e95bf..000000000 --- a/src/components/pages/_example/example.container.js +++ /dev/null @@ -1,7 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. - -import { translate } from 'react-i18next'; - -import { Example } from './example'; - -export const ExampleContainer = translate()(Example); diff --git a/src/components/pages/_flyoutExample/flyoutExample.container.js b/src/components/pages/_flyoutExample/flyoutExample.container.js deleted file mode 100644 index 9ee5abb7d..000000000 --- a/src/components/pages/_flyoutExample/flyoutExample.container.js +++ /dev/null @@ -1,7 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. - -import { translate } from 'react-i18next'; - -import { FlyoutExample } from './flyoutExample'; - -export const FlyoutExampleContainer = translate()(FlyoutExample); diff --git a/src/components/pages/dashboard/dashboard.js b/src/components/pages/dashboard/dashboard.js index 330b51816..78b32c7e4 100644 --- a/src/components/pages/dashboard/dashboard.js +++ b/src/components/pages/dashboard/dashboard.js @@ -5,13 +5,13 @@ import { Observable, Subject } from 'rxjs'; import moment from 'moment'; import Config from 'app.config'; -import { TelemetryService, retryHandler } from 'services'; -import { compareByProperty, getIntervalParams } from 'utilities'; +import { TelemetryService } from 'services'; +import { compareByProperty, getIntervalParams, retryHandler } from 'utilities'; import { Grid, Cell } from './grid'; import { PanelErrorBoundary } from './panel'; -import { DeviceGroupDropdownContainer as DeviceGroupDropdown } from 'components/app/deviceGroupDropdown'; -import { ManageDeviceGroupsBtnContainer as ManageDeviceGroupsBtn } from 'components/app/manageDeviceGroupsBtn'; -import { TimeIntervalDropdown } from 'components/app/timeIntervalDropdown'; +import { DeviceGroupDropdownContainer as DeviceGroupDropdown } from 'components/shell/deviceGroupDropdown'; +import { ManageDeviceGroupsBtnContainer as ManageDeviceGroupsBtn } from 'components/shell/manageDeviceGroupsBtn'; +import { TimeIntervalDropdown } from 'components/shell/timeIntervalDropdown'; import { OverviewPanel, AlertsPanel, diff --git a/src/components/pages/devices/devices.container.js b/src/components/pages/devices/devices.container.js index 3db7b66e1..3b16f281b 100644 --- a/src/components/pages/devices/devices.container.js +++ b/src/components/pages/devices/devices.container.js @@ -12,6 +12,7 @@ import { } from 'store/reducers/devicesReducer'; import { redux as appRedux, + epics as appEpics, getDeviceGroups, getDeviceGroupError } from 'store/reducers/appReducer'; @@ -29,7 +30,8 @@ const mapStateToProps = state => ({ // Wrap the dispatch method const mapDispatchToProps = dispatch => ({ fetchDevices: () => dispatch(devicesEpics.actions.fetchDevices()), - updateCurrentWindow: (currentWindow) => dispatch(appRedux.actions.updateCurrentWindow(currentWindow)) + updateCurrentWindow: (currentWindow) => dispatch(appRedux.actions.updateCurrentWindow(currentWindow)), + logEvent: diagnosticsModel => dispatch(appEpics.actions.logEvent(diagnosticsModel)) }); export const DevicesContainer = translate()(connect(mapStateToProps, mapDispatchToProps)(Devices)); diff --git a/src/components/pages/devices/devices.js b/src/components/pages/devices/devices.js index 39e622a0d..f5e779aad 100644 --- a/src/components/pages/devices/devices.js +++ b/src/components/pages/devices/devices.js @@ -2,10 +2,10 @@ import React, { Component } from 'react'; -import { permissions } from 'services/models'; +import { permissions, toDiagnosticsModel } from 'services/models'; import { DevicesGrid } from './devicesGrid'; -import { DeviceGroupDropdownContainer as DeviceGroupDropdown } from 'components/app/deviceGroupDropdown'; -import { ManageDeviceGroupsBtnContainer as ManageDeviceGroupsBtn } from 'components/app/manageDeviceGroupsBtn'; +import { DeviceGroupDropdownContainer as DeviceGroupDropdown } from 'components/shell/deviceGroupDropdown'; +import { ManageDeviceGroupsBtnContainer as ManageDeviceGroupsBtn } from 'components/shell/manageDeviceGroupsBtn'; import { AjaxError, Btn, @@ -45,7 +45,10 @@ export class Devices extends Component { closeFlyout = () => this.setState(closedFlyoutState); openSIMManagement = () => this.setState({ openFlyoutName: 'sim-management' }); - openNewDeviceFlyout = () => this.setState({ openFlyoutName: 'new-device' }); + openNewDeviceFlyout = () => { + this.setState({ openFlyoutName: 'new-device' }); + this.props.logEvent(toDiagnosticsModel('Devices_NewClick', {})); + } onContextMenuChange = contextBtns => this.setState({ contextBtns, diff --git a/src/components/pages/devices/flyouts/deviceNew/deviceNew.container.js b/src/components/pages/devices/flyouts/deviceNew/deviceNew.container.js index 939b0c337..7756e5276 100644 --- a/src/components/pages/devices/flyouts/deviceNew/deviceNew.container.js +++ b/src/components/pages/devices/flyouts/deviceNew/deviceNew.container.js @@ -11,6 +11,9 @@ import { epics as devicesEpics, redux as devicesRedux } from 'store/reducers/devicesReducer'; +import { + epics as appEpics, +} from 'store/reducers/appReducer'; // Pass the global info needed const mapStateToProps = state => ({ @@ -21,7 +24,8 @@ const mapStateToProps = state => ({ const mapDispatchToProps = dispatch => ({ fetchDeviceModelOptions: () => dispatch(simulationEpics.actions.fetchSimulationDeviceModelOptions()), insertDevices: devices => dispatch(devicesRedux.actions.insertDevices(devices)), - fetchDevices: () => dispatch(devicesEpics.actions.fetchDevices()) + fetchDevices: () => dispatch(devicesEpics.actions.fetchDevices()), + logEvent: diagnosticsModel => dispatch(appEpics.actions.logEvent(diagnosticsModel)) }); export const DeviceNewContainer = translate()(connect(mapStateToProps, mapDispatchToProps)(DeviceNew)); diff --git a/src/components/pages/devices/flyouts/deviceNew/deviceNew.js b/src/components/pages/devices/flyouts/deviceNew/deviceNew.js index 94f170701..74c5950d9 100644 --- a/src/components/pages/devices/flyouts/deviceNew/deviceNew.js +++ b/src/components/pages/devices/flyouts/deviceNew/deviceNew.js @@ -4,7 +4,14 @@ import React from 'react'; import update from 'immutability-helper'; import { DeviceSimulationService, IoTHubManagerService } from 'services'; -import { authenticationTypeOptions, permissions, toNewDeviceRequestModel } from 'services/models'; +import { + authenticationTypeOptions, + permissions, + toNewDeviceRequestModel, + toSinglePropertyDiagnosticsModel, + toDeviceDiagnosticsModel, + toDiagnosticsModel +} from 'services/models'; import { copyToClipboard, int, @@ -39,6 +46,7 @@ import { } from 'components/shared'; import './deviceNew.css'; +import Config from 'app.config'; const isIntRegex = /^-?\d*$/; const nonInteger = x => !x.match(isIntRegex); @@ -242,6 +250,12 @@ export class DeviceNew extends LinkedComponent { ].every(link => !link.error); } + deviceTypeChange = ({ target: { value }}) => { + this.props.logEvent(toSinglePropertyDiagnosticsModel('Devices_DeviceTypeSelect', 'DeviceType', + (value === 'true') ? Config.deviceType.simulated: Config.deviceType.physical)); + this.formControlChange(); + } + formControlChange = () => { if (this.state.changesApplied) { this.setState({ @@ -252,6 +266,11 @@ export class DeviceNew extends LinkedComponent { } } + onFlyoutClose = (eventName) => { + this.props.logEvent(toDeviceDiagnosticsModel(eventName, this.state.formData)); + this.props.onClose(); + } + apply = (event) => { event.preventDefault(); const { formData } = this.state; @@ -261,11 +280,14 @@ export class DeviceNew extends LinkedComponent { if (this.provisionSubscription) this.provisionSubscription.unsubscribe(); + this.props.logEvent(toDeviceDiagnosticsModel('Devices_ApplyClick', formData)); + if (this.state.formData.isSimulated) { this.provisionSubscription = DeviceSimulationService.incrementSimulatedDeviceModel(formData.deviceModel, formData.count) .subscribe( () => { this.setState({ successCount: formData.count, isPending: false, changesApplied: true }); + this.props.logEvent(toSinglePropertyDiagnosticsModel('Devices_Created', 'DeviceType', Config.deviceType.simulated)); }, error => { this.setState({ error, isPending: false, changesApplied: true }); @@ -278,6 +300,11 @@ export class DeviceNew extends LinkedComponent { provisionedDevice => { this.setState({ provisionedDevice, successCount: formData.count, isPending: false, changesApplied: true }); this.props.insertDevices([provisionedDevice]); + const metadata = { + DeviceType: Config.deviceType.physical, + DeviceID: provisionedDevice.id + }; + this.props.logEvent(toDiagnosticsModel('Devices_Created', metadata)); }, error => { this.setState({ error, isPending: false, changesApplied: true }); @@ -303,7 +330,6 @@ export class DeviceNew extends LinkedComponent { render() { const { t, - onClose, deviceModelOptions } = this.props; const { @@ -328,7 +354,7 @@ export class DeviceNew extends LinkedComponent { {t('devices.flyouts.new.title')} - + this.onFlyoutClose('Devices_TopXCloseClick')} /> @@ -336,10 +362,10 @@ export class DeviceNew extends LinkedComponent {
{t(deviceTypeOptions.labelName)} - + {t(deviceTypeOptions.simulated.labelName)} - + {t(deviceTypeOptions.physical.labelName)} @@ -421,14 +447,14 @@ export class DeviceNew extends LinkedComponent { !changesApplied && {t('devices.flyouts.new.apply')} - {t('devices.flyouts.new.cancel')} + this.onFlyoutClose('Devices_CancelClick')}>{t('devices.flyouts.new.cancel')} } { !!changesApplied && [ , - {t('devices.flyouts.new.close')} + this.onFlyoutClose('Devices_CloseClick')}>{t('devices.flyouts.new.close')} ] } diff --git a/src/components/pages/index.js b/src/components/pages/index.js index 121e50c59..f41810530 100644 --- a/src/components/pages/index.js +++ b/src/components/pages/index.js @@ -1,14 +1,8 @@ // Copyright (c) Microsoft. All rights reserved. // Exports the app page components - -export * from './_example/example.container'; -export * from './_flyoutExample/flyoutExample.container'; -export * from './_gridExample/gridExample.container'; export * from './dashboard/dashboard.container'; export * from './devices/devices.container'; export * from './rules/rules.container'; export * from './maintenance/maintenance.container'; -export * from './pageNotFound/pageNotFound'; -export * from './pageNotFound/pageNotFound.container'; export * from './packages/packages.container'; diff --git a/src/components/pages/maintenance/jobDetails/jobDetails.js b/src/components/pages/maintenance/jobDetails/jobDetails.js index b619c9a11..6114c7014 100644 --- a/src/components/pages/maintenance/jobDetails/jobDetails.js +++ b/src/components/pages/maintenance/jobDetails/jobDetails.js @@ -6,7 +6,7 @@ import Config from 'app.config'; import { AjaxError, PageContent, ContextMenu, RefreshBar } from 'components/shared'; import { DevicesGrid } from 'components/pages/devices/devicesGrid'; import { JobGrid, JobStatusGrid } from 'components/pages/maintenance/grids'; -import { TimeIntervalDropdown } from 'components/app/timeIntervalDropdown'; +import { TimeIntervalDropdown } from 'components/shell/timeIntervalDropdown'; import { IoTHubManagerService } from 'services'; diff --git a/src/components/pages/maintenance/ruleDetails/ruleDetails.js b/src/components/pages/maintenance/ruleDetails/ruleDetails.js index 8c9429679..912431dd1 100644 --- a/src/components/pages/maintenance/ruleDetails/ruleDetails.js +++ b/src/components/pages/maintenance/ruleDetails/ruleDetails.js @@ -18,9 +18,9 @@ import { } from 'components/shared'; import { svgs, joinClasses, renderUndefined } from 'utilities'; import { DevicesGrid } from 'components/pages/devices/devicesGrid'; -import { DeviceGroupDropdownContainer as DeviceGroupDropdown } from 'components/app/deviceGroupDropdown'; -import { ManageDeviceGroupsBtnContainer as ManageDeviceGroupsBtn } from 'components/app/manageDeviceGroupsBtn'; -import { TimeIntervalDropdown } from 'components/app/timeIntervalDropdown'; +import { DeviceGroupDropdownContainer as DeviceGroupDropdown } from 'components/shell/deviceGroupDropdown'; +import { ManageDeviceGroupsBtnContainer as ManageDeviceGroupsBtn } from 'components/shell/manageDeviceGroupsBtn'; +import { TimeIntervalDropdown } from 'components/shell/timeIntervalDropdown'; import { TelemetryChart, transformTelemetryResponse, chartColorObjects } from 'components/pages/dashboard/panels/telemetry'; import { TelemetryService } from 'services'; import { TimeRenderer, SeverityRenderer } from 'components/shared/cellRenderers'; diff --git a/src/components/pages/maintenance/summary/summary.js b/src/components/pages/maintenance/summary/summary.js index 0a0574ff3..d602834ee 100644 --- a/src/components/pages/maintenance/summary/summary.js +++ b/src/components/pages/maintenance/summary/summary.js @@ -5,9 +5,9 @@ import React from 'react'; import { Route, Switch } from 'react-router-dom'; import { NavLink } from 'react-router-dom'; -import { DeviceGroupDropdownContainer as DeviceGroupDropdown } from 'components/app/deviceGroupDropdown'; -import { ManageDeviceGroupsBtnContainer as ManageDeviceGroupsBtn } from 'components/app/manageDeviceGroupsBtn'; -import { TimeIntervalDropdown } from 'components/app/timeIntervalDropdown'; +import { DeviceGroupDropdownContainer as DeviceGroupDropdown } from 'components/shell/deviceGroupDropdown'; +import { ManageDeviceGroupsBtnContainer as ManageDeviceGroupsBtn } from 'components/shell/manageDeviceGroupsBtn'; +import { TimeIntervalDropdown } from 'components/shell/timeIntervalDropdown'; import { Notifications } from './notifications'; import { Jobs } from './jobs'; import { diff --git a/src/components/pages/rules/flyouts/ruleEditor/ruleEditor.js b/src/components/pages/rules/flyouts/ruleEditor/ruleEditor.js index ff4a9cab1..7ef05576e 100644 --- a/src/components/pages/rules/flyouts/ruleEditor/ruleEditor.js +++ b/src/components/pages/rules/flyouts/ruleEditor/ruleEditor.js @@ -147,6 +147,7 @@ export class RuleEditor extends LinkedComponent { erroe: undefined } }; + logEvent(toRuleDiagnosticsModel('Rule_ApplyClick', requestProps)); if (this.props.rule) { // If rule object exist then update the existing rule this.subscription = TelemetryService.updateRule(this.props.rule.id, toNewRuleRequestModel(requestProps)) .subscribe( @@ -165,7 +166,6 @@ export class RuleEditor extends LinkedComponent { }, error => this.setState({ error, isPending: false, changesApplied: true }) ); - logEvent(toRuleDiagnosticsModel('Rule_ApplyClick', requestProps)); } } } diff --git a/src/components/pages/rules/rules.js b/src/components/pages/rules/rules.js index bea5ef561..31deedf75 100644 --- a/src/components/pages/rules/rules.js +++ b/src/components/pages/rules/rules.js @@ -3,8 +3,8 @@ import React, { Component } from 'react'; import { permissions, toDiagnosticsModel } from 'services/models'; import { RulesGrid } from './rulesGrid'; -import { DeviceGroupDropdownContainer as DeviceGroupDropdown } from 'components/app/deviceGroupDropdown'; -import { ManageDeviceGroupsBtnContainer as ManageDeviceGroupsBtn } from 'components/app/manageDeviceGroupsBtn'; +import { DeviceGroupDropdownContainer as DeviceGroupDropdown } from 'components/shell/deviceGroupDropdown'; +import { ManageDeviceGroupsBtnContainer as ManageDeviceGroupsBtn } from 'components/shell/manageDeviceGroupsBtn'; import { AjaxError, Btn, diff --git a/src/components/app/deviceGroupDropdown/deviceGroupDropdown.container.js b/src/components/shell/deviceGroupDropdown/deviceGroupDropdown.container.js similarity index 100% rename from src/components/app/deviceGroupDropdown/deviceGroupDropdown.container.js rename to src/components/shell/deviceGroupDropdown/deviceGroupDropdown.container.js diff --git a/src/components/app/deviceGroupDropdown/deviceGroupDropdown.js b/src/components/shell/deviceGroupDropdown/deviceGroupDropdown.js similarity index 100% rename from src/components/app/deviceGroupDropdown/deviceGroupDropdown.js rename to src/components/shell/deviceGroupDropdown/deviceGroupDropdown.js diff --git a/src/components/app/deviceGroupDropdown/deviceGroupDropdown.scss b/src/components/shell/deviceGroupDropdown/deviceGroupDropdown.scss similarity index 100% rename from src/components/app/deviceGroupDropdown/deviceGroupDropdown.scss rename to src/components/shell/deviceGroupDropdown/deviceGroupDropdown.scss diff --git a/src/components/app/deviceGroupDropdown/index.js b/src/components/shell/deviceGroupDropdown/index.js similarity index 100% rename from src/components/app/deviceGroupDropdown/index.js rename to src/components/shell/deviceGroupDropdown/index.js diff --git a/src/components/app/flyouts/index.js b/src/components/shell/flyouts/index.js similarity index 100% rename from src/components/app/flyouts/index.js rename to src/components/shell/flyouts/index.js diff --git a/src/components/app/flyouts/manageDeviceGroups/index.js b/src/components/shell/flyouts/manageDeviceGroups/index.js similarity index 100% rename from src/components/app/flyouts/manageDeviceGroups/index.js rename to src/components/shell/flyouts/manageDeviceGroups/index.js diff --git a/src/components/app/flyouts/manageDeviceGroups/manageDeviceGroups.container.js b/src/components/shell/flyouts/manageDeviceGroups/manageDeviceGroups.container.js similarity index 100% rename from src/components/app/flyouts/manageDeviceGroups/manageDeviceGroups.container.js rename to src/components/shell/flyouts/manageDeviceGroups/manageDeviceGroups.container.js diff --git a/src/components/app/flyouts/manageDeviceGroups/manageDeviceGroups.js b/src/components/shell/flyouts/manageDeviceGroups/manageDeviceGroups.js similarity index 100% rename from src/components/app/flyouts/manageDeviceGroups/manageDeviceGroups.js rename to src/components/shell/flyouts/manageDeviceGroups/manageDeviceGroups.js diff --git a/src/components/app/flyouts/manageDeviceGroups/manageDeviceGroups.scss b/src/components/shell/flyouts/manageDeviceGroups/manageDeviceGroups.scss similarity index 100% rename from src/components/app/flyouts/manageDeviceGroups/manageDeviceGroups.scss rename to src/components/shell/flyouts/manageDeviceGroups/manageDeviceGroups.scss diff --git a/src/components/app/flyouts/manageDeviceGroups/views/deviceGroupForm.js b/src/components/shell/flyouts/manageDeviceGroups/views/deviceGroupForm.js similarity index 100% rename from src/components/app/flyouts/manageDeviceGroups/views/deviceGroupForm.js rename to src/components/shell/flyouts/manageDeviceGroups/views/deviceGroupForm.js diff --git a/src/components/app/flyouts/manageDeviceGroups/views/deviceGroups.js b/src/components/shell/flyouts/manageDeviceGroups/views/deviceGroups.js similarity index 100% rename from src/components/app/flyouts/manageDeviceGroups/views/deviceGroups.js rename to src/components/shell/flyouts/manageDeviceGroups/views/deviceGroups.js diff --git a/src/components/app/flyouts/settings/applicationSettings.js b/src/components/shell/flyouts/settings/applicationSettings.js similarity index 100% rename from src/components/app/flyouts/settings/applicationSettings.js rename to src/components/shell/flyouts/settings/applicationSettings.js diff --git a/src/components/app/flyouts/settings/applicationSettings.scss b/src/components/shell/flyouts/settings/applicationSettings.scss similarity index 100% rename from src/components/app/flyouts/settings/applicationSettings.scss rename to src/components/shell/flyouts/settings/applicationSettings.scss diff --git a/src/components/app/flyouts/settings/index.js b/src/components/shell/flyouts/settings/index.js similarity index 100% rename from src/components/app/flyouts/settings/index.js rename to src/components/shell/flyouts/settings/index.js diff --git a/src/components/app/flyouts/settings/settings.container.js b/src/components/shell/flyouts/settings/settings.container.js similarity index 100% rename from src/components/app/flyouts/settings/settings.container.js rename to src/components/shell/flyouts/settings/settings.container.js diff --git a/src/components/app/flyouts/settings/settings.js b/src/components/shell/flyouts/settings/settings.js similarity index 99% rename from src/components/app/flyouts/settings/settings.js rename to src/components/shell/flyouts/settings/settings.js index a7e9eaa49..c25fe16ab 100644 --- a/src/components/app/flyouts/settings/settings.js +++ b/src/components/shell/flyouts/settings/settings.js @@ -6,7 +6,7 @@ import Config from 'app.config'; import Flyout from 'components/shared/flyout'; import { Btn, Indicator, ToggleBtn } from 'components/shared'; import { svgs, LinkedComponent, isDef } from 'utilities'; -import ApplicationSettings from 'components/app/flyouts/settings/applicationSettings'; +import ApplicationSettings from './applicationSettings'; import './settings.css'; diff --git a/src/components/app/flyouts/settings/settings.scss b/src/components/shell/flyouts/settings/settings.scss similarity index 100% rename from src/components/app/flyouts/settings/settings.scss rename to src/components/shell/flyouts/settings/settings.scss diff --git a/src/components/shell/header/breadcrumbs.js b/src/components/shell/header/breadcrumbs.js new file mode 100644 index 000000000..9dde2334d --- /dev/null +++ b/src/components/shell/header/breadcrumbs.js @@ -0,0 +1,37 @@ +// Copyright (c) Microsoft. All rights reserved. + +import React from 'react'; +import { Route, Switch, NavLink } from 'react-router-dom'; + +import { Svg } from 'components/shared'; +import { svgs, isDef } from 'utilities'; + +const Crumb = ({ children }) =>
{children}
+const Chevron = () => ; + +const CrumbFromConfig = ({ t, crumb, match, isLast }) => { + + const separator = ; + + if (isDef(crumb.labelId) && !isDef(crumb.matchParam)) { + if (!isLast) { + return [{t(crumb.labelId)}, separator] + } else { + return {t(crumb.labelId)} + } + } else if (!isDef(crumb.labelId) && isDef(crumb.matchParam)) { + return {match.params[crumb.matchParam]} + } +} + +export const Breadcrumbs = ({ t, crumbsConfig }) => ( + + { + crumbsConfig.map(({ path, crumbs }) => + { + return crumbs.map((crumb, idx) => ); + }} /> + ) + } + +); diff --git a/src/components/app/header/header.js b/src/components/shell/header/header.js similarity index 97% rename from src/components/app/header/header.js rename to src/components/shell/header/header.js index 7008a36e4..187d5066f 100644 --- a/src/components/app/header/header.js +++ b/src/components/shell/header/header.js @@ -75,7 +75,7 @@ class Header extends Component { return (
- +
{ this.props.t('header.appName') }
diff --git a/src/components/app/header/header.scss b/src/components/shell/header/header.scss similarity index 100% rename from src/components/app/header/header.scss rename to src/components/shell/header/header.scss diff --git a/src/components/app/main/main.js b/src/components/shell/main/main.js similarity index 100% rename from src/components/app/main/main.js rename to src/components/shell/main/main.js diff --git a/src/components/app/main/main.scss b/src/components/shell/main/main.scss similarity index 100% rename from src/components/app/main/main.scss rename to src/components/shell/main/main.scss diff --git a/src/components/app/manageDeviceGroupsBtn/index.js b/src/components/shell/manageDeviceGroupsBtn/index.js similarity index 100% rename from src/components/app/manageDeviceGroupsBtn/index.js rename to src/components/shell/manageDeviceGroupsBtn/index.js diff --git a/src/components/app/manageDeviceGroupsBtn/manageDeviceGroupsBtn.container.js b/src/components/shell/manageDeviceGroupsBtn/manageDeviceGroupsBtn.container.js similarity index 100% rename from src/components/app/manageDeviceGroupsBtn/manageDeviceGroupsBtn.container.js rename to src/components/shell/manageDeviceGroupsBtn/manageDeviceGroupsBtn.container.js diff --git a/src/components/app/manageDeviceGroupsBtn/manageDeviceGroupsBtn.js b/src/components/shell/manageDeviceGroupsBtn/manageDeviceGroupsBtn.js similarity index 100% rename from src/components/app/manageDeviceGroupsBtn/manageDeviceGroupsBtn.js rename to src/components/shell/manageDeviceGroupsBtn/manageDeviceGroupsBtn.js diff --git a/src/components/app/navigation/navigation.js b/src/components/shell/navigation/navigation.js similarity index 100% rename from src/components/app/navigation/navigation.js rename to src/components/shell/navigation/navigation.js diff --git a/src/components/app/navigation/navigation.scss b/src/components/shell/navigation/navigation.scss similarity index 100% rename from src/components/app/navigation/navigation.scss rename to src/components/shell/navigation/navigation.scss diff --git a/src/components/app/navigation/navigationContainer.js b/src/components/shell/navigation/navigationContainer.js similarity index 100% rename from src/components/app/navigation/navigationContainer.js rename to src/components/shell/navigation/navigationContainer.js diff --git a/src/components/shell/pageNotFound/index.js b/src/components/shell/pageNotFound/index.js new file mode 100644 index 000000000..78dfbc6cf --- /dev/null +++ b/src/components/shell/pageNotFound/index.js @@ -0,0 +1,4 @@ +// Copyright (c) Microsoft. All rights reserved. + +export * from './pageNotFound'; +export * from './pageNotFound.container'; diff --git a/src/components/pages/pageNotFound/pageNotFound.container.js b/src/components/shell/pageNotFound/pageNotFound.container.js similarity index 100% rename from src/components/pages/pageNotFound/pageNotFound.container.js rename to src/components/shell/pageNotFound/pageNotFound.container.js diff --git a/src/components/pages/pageNotFound/pageNotFound.js b/src/components/shell/pageNotFound/pageNotFound.js similarity index 100% rename from src/components/pages/pageNotFound/pageNotFound.js rename to src/components/shell/pageNotFound/pageNotFound.js diff --git a/src/components/pages/pageNotFound/pageNotFound.scss b/src/components/shell/pageNotFound/pageNotFound.scss similarity index 100% rename from src/components/pages/pageNotFound/pageNotFound.scss rename to src/components/shell/pageNotFound/pageNotFound.scss diff --git a/src/components/shell/shell.container.js b/src/components/shell/shell.container.js new file mode 100644 index 000000000..03d1487be --- /dev/null +++ b/src/components/shell/shell.container.js @@ -0,0 +1,25 @@ +// Copyright (c) Microsoft. All rights reserved. + +import { withRouter } from 'react-router-dom'; +import { connect } from 'react-redux'; +import { translate } from 'react-i18next'; +import { AuthService } from 'services'; +import { + epics as appEpics, + getTheme, + getDeviceGroupFlyoutStatus +} from 'store/reducers/appReducer'; +import Shell from './shell'; + +const mapStateToProps = state => ({ + theme: getTheme(state), + deviceGroupFlyoutIsOpen: getDeviceGroupFlyoutStatus(state) +}); + +// Wrap with the router and wrap the dispatch method +const mapDispatchToProps = dispatch => ({ + registerRouteEvent: pathname => dispatch(appEpics.actions.detectRouteChange(pathname)), + logout: () => AuthService.logout() +}); + +export const ShellContainer = withRouter(translate()(connect(mapStateToProps, mapDispatchToProps)(Shell))); diff --git a/src/components/shell/shell.js b/src/components/shell/shell.js new file mode 100644 index 000000000..b800e8685 --- /dev/null +++ b/src/components/shell/shell.js @@ -0,0 +1,57 @@ +// Copyright (c) Microsoft. All rights reserved. + +import React, { Component } from 'react'; +import { Route, Redirect, Switch } from 'react-router-dom'; + +// App Components +import Header from './header/header'; +import NavigationContainer from './navigation/navigationContainer'; +import Main from './main/main'; +import { PageNotFoundContainer as PageNotFound } from './pageNotFound' + +import './shell.css'; + +/** The base component for the app shell */ +class Shell extends Component { + + constructor(props) { + super(props); + + this.state = { openFlyout: '' }; + } + + componentDidMount() { + const { history, registerRouteEvent } = this.props; + // Initialize listener to inject the route change event into the epic action stream + history.listen(({ pathname }) => registerRouteEvent(pathname)); + } + + render() { + const { pagesConfig, crumbsConfig, openSettings, logout, t, theme, children } = this.props; + + return ( +
+ { + pagesConfig && +
+ +
+
+ + + { + pagesConfig.map(({ to, exact, component }) => + ) + } + + + {children} +
+
+ } +
+ ); + } +} + +export default Shell; diff --git a/src/components/app/app.scss b/src/components/shell/shell.scss similarity index 96% rename from src/components/app/app.scss rename to src/components/shell/shell.scss index 13d78f8c5..522c7af9c 100644 --- a/src/components/app/app.scss +++ b/src/components/shell/shell.scss @@ -3,9 +3,9 @@ @import 'src/styles/themes'; @import 'src/styles/mixins'; -.app-container { height: 100%; } +.shell-container { height: 100%; } -.app { +.shell { height: 100%; display: flex; diff --git a/src/components/app/app.test.js b/src/components/shell/shell.test.js similarity index 60% rename from src/components/app/app.test.js rename to src/components/shell/shell.test.js index 9a85395e4..daf402328 100644 --- a/src/components/app/app.test.js +++ b/src/components/shell/shell.test.js @@ -4,15 +4,15 @@ import React from 'react'; import { mount } from 'enzyme'; import 'polyfills'; -import AppContainer from 'components/app/app.container'; +import ShellContainer from 'components/shell/shell.container'; import MockApp from 'components/mocks/mockApp'; -describe('App integration test', () => { +describe('Shell integration test', () => { let wrapper; - it('Render App component', () => { + it('Render Shell component', () => { wrapper = mount( - + ); }); diff --git a/src/components/app/timeIntervalDropdown/index.js b/src/components/shell/timeIntervalDropdown/index.js similarity index 100% rename from src/components/app/timeIntervalDropdown/index.js rename to src/components/shell/timeIntervalDropdown/index.js diff --git a/src/components/app/timeIntervalDropdown/timeIntervalDropdown.js b/src/components/shell/timeIntervalDropdown/timeIntervalDropdown.js similarity index 100% rename from src/components/app/timeIntervalDropdown/timeIntervalDropdown.js rename to src/components/shell/timeIntervalDropdown/timeIntervalDropdown.js diff --git a/src/components/app/timeIntervalDropdown/timeIntervalDropdown.scss b/src/components/shell/timeIntervalDropdown/timeIntervalDropdown.scss similarity index 100% rename from src/components/app/timeIntervalDropdown/timeIntervalDropdown.scss rename to src/components/shell/timeIntervalDropdown/timeIntervalDropdown.scss diff --git a/src/index.js b/src/index.js index 75da4b1b3..3e9f4078b 100644 --- a/src/index.js +++ b/src/index.js @@ -5,8 +5,11 @@ import ReactDOM from 'react-dom'; import { Provider } from 'react-redux'; import { BrowserRouter as Router } from 'react-router-dom'; -import configureStore from 'store/configureStore'; -import AppContainer from 'components/app/app.container'; +import Config from 'app.config'; +import { configureStore } from 'store/configureStore'; +import { AppContainer as App } from 'components/app.container'; +import { configureStore as configureWalkthroughStore } from 'walkthrough/store/configureStore'; +import { AppContainer as WalkthroughApp } from 'walkthrough/components/app.container'; import registerServiceWorker from 'registerServiceWorker'; import { AuthService } from 'services'; import { epics as appEpics } from 'store/reducers/appReducer'; @@ -23,7 +26,9 @@ import './index.css'; // Initialize the user authentication AuthService.onLoad(() => { // Create the redux store and redux-observable streams - const store = configureStore(); + const store = Config.showWalkthroughExamples + ? configureWalkthroughStore() + : configureStore(); // Initialize the app redux state store.dispatch(appEpics.actions.initializeApp()); @@ -32,7 +37,10 @@ AuthService.onLoad(() => { ReactDOM.render( - + {Config.showWalkthroughExamples + ? + : + } , document.getElementById('root') diff --git a/src/services/authService.js b/src/services/authService.js index 7e6306147..06a2fad55 100644 --- a/src/services/authService.js +++ b/src/services/authService.js @@ -3,7 +3,7 @@ import Config from 'app.config'; import AuthenticationContext from 'adal-angular/dist/adal.min.js' import { Observable } from 'rxjs'; -import { HttpClient } from './httpClient'; +import { HttpClient } from 'utilities/httpClient'; import { toUserModel, authDisabledUser } from './models'; const ENDPOINT = Config.serviceUrls.auth; diff --git a/src/services/configService.js b/src/services/configService.js index d01b3d61a..c8f5117f0 100644 --- a/src/services/configService.js +++ b/src/services/configService.js @@ -1,7 +1,7 @@ // Copyright (c) Microsoft. All rights reserved. import Config from 'app.config'; -import { HttpClient } from './httpClient'; +import { HttpClient } from 'utilities/httpClient'; import { prepareLogoResponse, toDeviceGroupModel, diff --git a/src/services/deviceSimulationService.js b/src/services/deviceSimulationService.js index 2ff13de73..05d9c999f 100644 --- a/src/services/deviceSimulationService.js +++ b/src/services/deviceSimulationService.js @@ -3,7 +3,7 @@ import { Observable } from 'rxjs'; import Config from 'app.config'; -import { HttpClient } from './httpClient'; +import { HttpClient } from 'utilities/httpClient'; import { toDeviceModelSelectOptions, toDeviceSimulationModel, toDeviceSimulationRequestModel } from './models'; const ENDPOINT = Config.serviceUrls.deviceSimulation; diff --git a/src/services/diagnosticsService.js b/src/services/diagnosticsService.js index 29a0bdc4b..53ed2c6a8 100644 --- a/src/services/diagnosticsService.js +++ b/src/services/diagnosticsService.js @@ -1,5 +1,5 @@ import Config from 'app.config'; -import { HttpClient } from './httpClient'; +import { HttpClient } from 'utilities/httpClient'; import { toDiagnosticsRequestModel } from './models'; const ENDPOINT = Config.serviceUrls.diagnostics; diff --git a/src/services/gitHubService.js b/src/services/gitHubService.js index b56098822..70163f3a0 100644 --- a/src/services/gitHubService.js +++ b/src/services/gitHubService.js @@ -1,7 +1,7 @@ // Copyright (c) Microsoft. All rights reserved. import Config from 'app.config'; -import { HttpClient } from './httpClient'; +import { HttpClient } from 'utilities/httpClient'; import { toGitHubModel } from './models'; diff --git a/src/services/index.js b/src/services/index.js index 985bdd785..75b6ade97 100644 --- a/src/services/index.js +++ b/src/services/index.js @@ -2,10 +2,8 @@ // Exports the shared react components into as a library -export * from './_exampleService'; export * from './authService'; export * from './deviceSimulationService'; -export * from './httpClient'; export * from './iotHubManagerService'; export * from './telemetryService'; export * from './configService'; diff --git a/src/services/iotHubManagerService.js b/src/services/iotHubManagerService.js index 39b946642..c9998dff0 100644 --- a/src/services/iotHubManagerService.js +++ b/src/services/iotHubManagerService.js @@ -4,7 +4,7 @@ import { Observable } from 'rxjs'; import Config from 'app.config'; import { stringify } from 'query-string'; -import { HttpClient } from './httpClient'; +import { HttpClient } from 'utilities/httpClient'; import { toDevicesModel, toDeviceModel, toJobsModel, toJobStatusModel, toDevicePropertiesModel } from './models'; const ENDPOINT = Config.serviceUrls.iotHubManager; diff --git a/src/services/models/index.js b/src/services/models/index.js index ad766ed6e..753079aa8 100644 --- a/src/services/models/index.js +++ b/src/services/models/index.js @@ -2,8 +2,6 @@ // Exports models -export * from './_exampleModels'; -export * from './ajaxModels'; export * from './authModels'; export * from './deviceSimulationModels'; export * from './iotHubManagerModels'; diff --git a/src/services/models/logEventModels.js b/src/services/models/logEventModels.js index 9ece4eec8..0bdb511e7 100644 --- a/src/services/models/logEventModels.js +++ b/src/services/models/logEventModels.js @@ -1,6 +1,7 @@ // Copyright (c) Microsoft. All rights reserved. import { toDiagnosticsModel } from 'services/models'; +import Config from 'app.config'; export const toRuleDiagnosticsModel = (eventName, rule) => { @@ -21,3 +22,16 @@ export const toSinglePropertyDiagnosticsModel = (eventName, propertyTitle, prope const metadata = { [propertyTitle]: property }; return toDiagnosticsModel(eventName, metadata); } + +export const toDeviceDiagnosticsModel = (eventName, deviceFormData) => +{ + const metadata = { + DeviceIDType: deviceFormData.isSimulated ? '' : (deviceFormData.isGenerateId ? 'Generated' : 'Manual'), + DeviceType: deviceFormData.isSimulated ? Config.deviceType.simulated : Config.deviceType.physical, + NumberOfDevices: deviceFormData.count, + DeviceModel: deviceFormData.isSimulated ? deviceFormData.deviceModel : '', + AuthType: deviceFormData.isSimulated ? '' : (deviceFormData.authenticationType ? 'x.509' : 'Symmetric Key'), + AuthKey: deviceFormData.isSimulated ? '' : (deviceFormData.isGenerateKeys ? 'Auto': 'Manual') + } + return toDiagnosticsModel(eventName, metadata); +} diff --git a/src/services/telemetryService.js b/src/services/telemetryService.js index 2c625e597..d0fca62ec 100644 --- a/src/services/telemetryService.js +++ b/src/services/telemetryService.js @@ -2,7 +2,7 @@ import { stringify } from 'query-string'; import Config from 'app.config'; -import { HttpClient } from './httpClient'; +import { HttpClient } from 'utilities/httpClient'; import { toActiveAlertsModel, toAlertForRuleModel, diff --git a/src/store/configureStore.js b/src/store/configureStore.js index 2fb289b44..0986e8b81 100644 --- a/src/store/configureStore.js +++ b/src/store/configureStore.js @@ -6,7 +6,7 @@ import { composeWithDevTools } from 'redux-devtools-extension'; import rootEpic from './rootEpic'; import rootReducer from './rootReducer'; -export default function configureStore() { +export function configureStore() { // Initialize the redux-observable epics const epicMiddleware = createEpicMiddleware(rootEpic); // Initialize the redux store with middleware diff --git a/src/store/reducers/appReducer.js b/src/store/reducers/appReducer.js index d8588ffec..2f6c4e9cc 100644 --- a/src/store/reducers/appReducer.js +++ b/src/store/reducers/appReducer.js @@ -20,6 +20,7 @@ import { getError } from 'store/utilities'; import { svgs, compareByProperty } from 'utilities'; +import { toDiagnosticsModel } from 'services/models'; // ========================= Epics - START const handleError = fromAction => error => @@ -85,6 +86,16 @@ export const epics = createEpicScenario({ description: currSettings.description, diagnosticsOptIn: fromAction.payload }; + + var logEventName = 'Diagnostics_OptIn'; + if (!fromAction.payload) { + logEventName = 'Diagnostics_OptOut'; + } + var logPayload = toDiagnosticsModel(logEventName, {}); + logPayload.sessionId = getSessionId(store.getState()); + logPayload.eventProperties.CurrentWindow = getCurrentWindow(store.getState()); + DiagnosticsService.logEvent(logPayload).subscribe(); + return ConfigService.updateSolutionSettings(settings) .map(toActionCreator(redux.actions.updateSolutionSettings, fromAction)) .catch(handleError(fromAction)); diff --git a/src/store/reducers/devicesReducer.js b/src/store/reducers/devicesReducer.js index 488b76449..e8ce1b8f3 100644 --- a/src/store/reducers/devicesReducer.js +++ b/src/store/reducers/devicesReducer.js @@ -82,9 +82,15 @@ const deleteDevicesReducer = (state, { payload }) => { const insertDevicesReducer = (state, { payload }) => { const { entities: { devices }, result } = normalize(payload, deviceListSchema); + if (state.entities) { + return update(state, { + entities: { $merge: devices }, + items: { $splice: [[0, 0, result]] } + }); + } return update(state, { - entities: { $merge: devices }, - items: { $push: result } + entities: { $set: devices }, + items: { $set: [result] } }); }; diff --git a/src/store/reducers/rulesReducer.js b/src/store/reducers/rulesReducer.js index 7348af015..198963572 100644 --- a/src/store/reducers/rulesReducer.js +++ b/src/store/reducers/rulesReducer.js @@ -94,9 +94,15 @@ const initialState = { ...errorPendingInitialState, entities: {}, items: [] }; const insertRulesReducer = (state, { payload }) => { const { entities: { rules }, result } = normalize(payload, ruleListSchema); + if (state.entities) { + return update(state, { + entities: { $merge: rules }, + items: { $splice: [[0, 0, result]] } + }); + } return update(state, { - entities: { $merge: rules }, - items: { $splice: [[state.items.length, 0, result]] } + entities: { $set: rules }, + items: { $set: [result] } }); }; diff --git a/src/store/rootEpic.js b/src/store/rootEpic.js index 12e553d7f..d947da39b 100644 --- a/src/store/rootEpic.js +++ b/src/store/rootEpic.js @@ -3,7 +3,6 @@ import { combineEpics } from 'redux-observable'; // Epics -import { epics as exampleEpics } from './reducers/_exampleReducer'; import { epics as appEpics } from './reducers/appReducer'; import { epics as devicesEpics } from './reducers/devicesReducer'; import { epics as rulesEpics } from './reducers/rulesReducer'; @@ -12,7 +11,6 @@ import { epics as simulationEpics } from './reducers/deviceSimulationReducer'; // Extract the epic function from each property object const epics = [ - ...exampleEpics.getEpics(), ...appEpics.getEpics(), ...devicesEpics.getEpics(), ...packagesEpics.getEpics(), diff --git a/src/store/rootReducer.js b/src/store/rootReducer.js index 746e5c0e8..70ce522e9 100644 --- a/src/store/rootReducer.js +++ b/src/store/rootReducer.js @@ -3,7 +3,6 @@ import { combineReducers } from 'redux'; // Reducers -import { reducer as exampleReducer } from './reducers/_exampleReducer'; import { reducer as appReducer } from './reducers/appReducer'; import { reducer as simulationReducer } from './reducers/deviceSimulationReducer'; import { reducer as devicesReducer } from './reducers/devicesReducer'; @@ -11,7 +10,6 @@ import { reducer as rulesReducer } from './reducers/rulesReducer'; import { reducer as packagesReducer } from './reducers/packagesReducer'; const rootReducer = combineReducers({ - ...exampleReducer, ...appReducer, ...devicesReducer, ...packagesReducer, diff --git a/src/services/models/ajaxModels.js b/src/utilities/ajaxModels.js similarity index 100% rename from src/services/models/ajaxModels.js rename to src/utilities/ajaxModels.js diff --git a/src/services/httpClient.js b/src/utilities/httpClient.js similarity index 97% rename from src/services/httpClient.js rename to src/utilities/httpClient.js index 142d370b8..a5b2eb130 100644 --- a/src/services/httpClient.js +++ b/src/utilities/httpClient.js @@ -1,9 +1,9 @@ // Copyright (c) Microsoft. All rights reserved. import { Observable } from 'rxjs'; -import { AuthService } from './authService'; +import { AuthService } from 'services/authService'; import Config from 'app.config'; -import { AjaxError, RetryableAjaxError } from './models'; +import { AjaxError, RetryableAjaxError } from './ajaxModels'; /** * A class of static methods for creating ajax requests diff --git a/src/services/httpClient.test.js b/src/utilities/httpClient.test.js similarity index 89% rename from src/services/httpClient.test.js rename to src/utilities/httpClient.test.js index 2c66384fb..25d785205 100644 --- a/src/services/httpClient.test.js +++ b/src/utilities/httpClient.test.js @@ -2,7 +2,7 @@ import 'polyfills'; import Config from 'app.config'; -import { HttpClient } from './httpClient'; +import { HttpClient } from 'utilities/httpClient'; const url = 'http://www.fakeurl.com'; diff --git a/src/utilities/index.js b/src/utilities/index.js index f066eee96..c002e6863 100644 --- a/src/utilities/index.js +++ b/src/utilities/index.js @@ -2,6 +2,8 @@ // Exports the app utilities +export * from './ajaxModels'; +export * from './httpClient'; export * from './methods'; export * from './svgs'; export * from './validation'; diff --git a/src/walkthrough/components/app.container.js b/src/walkthrough/components/app.container.js new file mode 100644 index 000000000..aff1c5938 --- /dev/null +++ b/src/walkthrough/components/app.container.js @@ -0,0 +1,25 @@ +// Copyright (c) Microsoft. All rights reserved. + +import { withRouter } from 'react-router-dom'; +import { connect } from 'react-redux'; +import { translate } from 'react-i18next'; +import { AuthService } from 'services'; +import { + epics as appEpics, + getTheme, + getDeviceGroupFlyoutStatus +} from 'store/reducers/appReducer'; +import App from './app'; + +const mapStateToProps = state => ({ + theme: getTheme(state), + deviceGroupFlyoutIsOpen: getDeviceGroupFlyoutStatus(state) +}); + +// Wrap with the router and wrap the dispatch method +const mapDispatchToProps = dispatch => ({ + registerRouteEvent: pathname => dispatch(appEpics.actions.detectRouteChange(pathname)), + logout: () => AuthService.logout() +}); + +export const AppContainer = withRouter(translate()(connect(mapStateToProps, mapDispatchToProps)(App))); diff --git a/src/walkthrough/components/app.js b/src/walkthrough/components/app.js new file mode 100644 index 000000000..135c63fb7 --- /dev/null +++ b/src/walkthrough/components/app.js @@ -0,0 +1,80 @@ +// Copyright (c) Microsoft. All rights reserved. + +import React, { Component } from 'react'; + +import { svgs } from 'utilities'; +import Shell from "components/shell/shell"; +import { DashboardContainer, BasicPageContainer, PageWithFlyoutContainer, PageWithGridContainer } from './pages'; + +class App extends Component { + openSettings = () => alert('There are no settings in the walkthrough.'); + + render() { + const pagesConfig = [ + { + to: '/dashboard', + exact: true, + svg: svgs.tabs.dashboard, + labelId: 'walkthrough.tabs.dashboard', + component: DashboardContainer + }, + { + to: '/basicpage', + exact: true, + svg: svgs.tabs.example, + labelId: 'walkthrough.tabs.basicPage', + component: BasicPageContainer + }, + { + to: '/pagewithflyout', + exact: true, + svg: svgs.tabs.example, + labelId: 'walkthrough.tabs.pageWithFlyout', + component: PageWithFlyoutContainer + }, + { + to: '/pagewithgrid', + exact: true, + svg: svgs.tabs.example, + labelId: 'walkthrough.tabs.pageWithGrid', + component: PageWithGridContainer + } + ]; + + const crumbsConfig = [ + { + path: '/dashboard', crumbs: [ + { to: '/dashboard', labelId: 'walkthrough.tabs.dashboard' } + ] + }, + { + path: '/basicpage', crumbs: [ + { to: '/basicpage', labelId: 'walkthrough.tabs.basicPage' } + ] + }, + { + path: '/pagewithflyout', crumbs: [ + { to: '/pagewithflyout', labelId: 'walkthrough.tabs.pageWithFlyout' } + ] + }, + { + path: '/pagewithgrid', crumbs: [ + { to: '/pagewithgrid', labelId: 'walkthrough.tabs.pageWithGrid' } + ] + } + ]; + + const shellProps = { + pagesConfig, + crumbsConfig, + openSettings: this.openSettings, + ...this.props + }; + + return ( + + ); + } +} + +export default App; diff --git a/src/walkthrough/components/pages/basicPage/basicPage.container.js b/src/walkthrough/components/pages/basicPage/basicPage.container.js new file mode 100644 index 000000000..92ace17b0 --- /dev/null +++ b/src/walkthrough/components/pages/basicPage/basicPage.container.js @@ -0,0 +1,7 @@ +// Copyright (c) Microsoft. All rights reserved. + +import { translate } from 'react-i18next'; + +import { BasicPage } from './basicPage'; + +export const BasicPageContainer = translate()(BasicPage); diff --git a/src/components/pages/_example/example.js b/src/walkthrough/components/pages/basicPage/basicPage.js similarity index 56% rename from src/components/pages/_example/example.js rename to src/walkthrough/components/pages/basicPage/basicPage.js index 52ce83d99..618eaa534 100644 --- a/src/components/pages/_example/example.js +++ b/src/walkthrough/components/pages/basicPage/basicPage.js @@ -4,14 +4,14 @@ import React, { Component } from 'react'; import { PageContent } from 'components/shared'; -import './example.css'; +import './basicPage.css'; -export class Example extends Component { +export class BasicPage extends Component { render() { const { t } = this.props; return ( - - { t('examples.pagePlaceholder') } + + { t('walkthrough.basicPage.pagePlaceholder') } ); } diff --git a/src/components/pages/_example/example.scss b/src/walkthrough/components/pages/basicPage/basicPage.scss similarity index 71% rename from src/components/pages/_example/example.scss rename to src/walkthrough/components/pages/basicPage/basicPage.scss index 52791649f..3e87ddc7a 100644 --- a/src/components/pages/_example/example.scss +++ b/src/walkthrough/components/pages/basicPage/basicPage.scss @@ -4,4 +4,4 @@ @import 'src/styles/mixins'; @import 'src/styles/themes'; -.example-container { padding: $baseContentPadding; } +.basic-page-container { padding: $baseContentPadding; } diff --git a/src/components/pages/_example/example.test.js b/src/walkthrough/components/pages/basicPage/basicPage.test.js similarity index 70% rename from src/components/pages/_example/example.test.js rename to src/walkthrough/components/pages/basicPage/basicPage.test.js index 576ea26d6..56c418727 100644 --- a/src/components/pages/_example/example.test.js +++ b/src/walkthrough/components/pages/basicPage/basicPage.test.js @@ -4,9 +4,9 @@ import React from 'react'; import { shallow } from 'enzyme'; import 'polyfills'; -import { Example } from './example'; +import { BasicPage } from './basicPage'; -describe('Example Component', () => { +describe('BasicPage Component', () => { it('Renders without crashing', () => { const fakeProps = { @@ -14,7 +14,7 @@ describe('Example Component', () => { }; const wrapper = shallow( - + ); }); }); diff --git a/src/walkthrough/components/pages/dashboard/dashboard.container.js b/src/walkthrough/components/pages/dashboard/dashboard.container.js new file mode 100644 index 000000000..1bbdbec18 --- /dev/null +++ b/src/walkthrough/components/pages/dashboard/dashboard.container.js @@ -0,0 +1,8 @@ +// Copyright (c) Microsoft. All rights reserved. + +import { connect } from 'react-redux'; +import { translate } from 'react-i18next'; + +import { Dashboard } from './dashboard'; + +export const DashboardContainer = translate()(connect(null, null)(Dashboard)); diff --git a/src/walkthrough/components/pages/dashboard/dashboard.js b/src/walkthrough/components/pages/dashboard/dashboard.js new file mode 100644 index 000000000..04954d81f --- /dev/null +++ b/src/walkthrough/components/pages/dashboard/dashboard.js @@ -0,0 +1,56 @@ +// Copyright (c) Microsoft. All rights reserved. + +import React, { Component } from 'react'; + +import { Grid, Cell } from 'components/pages/dashboard/grid'; + +import { ExamplePanel } from './panels'; +import { ContextMenu, PageContent } from 'components/shared'; + +import './dashboard.css'; + +const initialState = {}; + +export class Dashboard extends Component { + + constructor(props) { + super(props); + + this.state = initialState; + } + + render() { + const { t } = this.props; + + return [ + + {/** Add context buttons here... as needed for your dashboard. In this example, there are none. */} + , + + + + + + + + + + + + + + + + + + + + + + + + + + ]; + } +} diff --git a/src/walkthrough/components/pages/dashboard/dashboard.scss b/src/walkthrough/components/pages/dashboard/dashboard.scss new file mode 100644 index 000000000..094839176 --- /dev/null +++ b/src/walkthrough/components/pages/dashboard/dashboard.scss @@ -0,0 +1,31 @@ +// Copyright (c) Microsoft. All rights reserved. + +@import 'src/styles/mixins'; +@import 'src/styles/themes'; + +.dashboard-container { + height: 100%; + display: flex; + flex-grow: 1; + flex-flow: column nowrap; + align-items: stretch; + + .grid-container { + padding: 10px; + + .grid-cell { + min-height: 400px; + + &.devices-overview-cell { min-width: 314px; } + } + } + + @include themify($themes) { + background-color: themed('colorContentBackground'); + color: themed('colorContentText'); + } +} + +@media (max-width: 1200px) { + .dashboard-container { height: auto; } +} diff --git a/src/walkthrough/components/pages/dashboard/dashboard.test.js b/src/walkthrough/components/pages/dashboard/dashboard.test.js new file mode 100644 index 000000000..9cf680ee5 --- /dev/null +++ b/src/walkthrough/components/pages/dashboard/dashboard.test.js @@ -0,0 +1,20 @@ +// Copyright (c) Microsoft. All rights reserved. + +import React from 'react'; +import { shallow } from 'enzyme'; +import 'polyfills'; + +import { Dashboard } from './dashboard'; + +describe('Dashboard Component', () => { + it('Renders without crashing', () => { + + const fakeProps = { + t: () => {} + }; + + const wrapper = shallow( + + ); + }); +}); diff --git a/src/walkthrough/components/pages/dashboard/panels/examplePanel/examplePanel.js b/src/walkthrough/components/pages/dashboard/panels/examplePanel/examplePanel.js new file mode 100644 index 000000000..f61f3661e --- /dev/null +++ b/src/walkthrough/components/pages/dashboard/panels/examplePanel/examplePanel.js @@ -0,0 +1,35 @@ +// Copyright (c) Microsoft. All rights reserved. + +import React, { Component } from 'react'; + +import { + Panel, + PanelHeader, + PanelHeaderLabel, + PanelContent, +} from 'components/pages/dashboard/panel'; + +import './examplePanel.css'; + +export class ExamplePanel extends Component { + constructor(props) { + super(props); + + this.state = { isPending: true }; + } + + render() { + const { t } = this.props; + + return ( + + + {t('walkthrough.dashboard.panels.example.header')} + + + {t('walkthrough.dashboard.panels.example.panelBody')} + + + ); + } +} diff --git a/src/walkthrough/components/pages/dashboard/panels/examplePanel/examplePanel.scss b/src/walkthrough/components/pages/dashboard/panels/examplePanel/examplePanel.scss new file mode 100644 index 000000000..e81169e5d --- /dev/null +++ b/src/walkthrough/components/pages/dashboard/panels/examplePanel/examplePanel.scss @@ -0,0 +1,14 @@ +// Copyright (c) Microsoft. All rights reserved. + +@import 'src/styles/mixins'; +@import 'src/styles/themes'; + +.example-panel-container { + display: flex; + flex-flow: column nowrap; + padding: 0 !important; + + @include themify($themes) { + color: themed('colorContentTextDim'); + } +} diff --git a/src/walkthrough/components/pages/dashboard/panels/examplePanel/index.js b/src/walkthrough/components/pages/dashboard/panels/examplePanel/index.js new file mode 100644 index 000000000..ef0305829 --- /dev/null +++ b/src/walkthrough/components/pages/dashboard/panels/examplePanel/index.js @@ -0,0 +1,3 @@ +// Copyright (c) Microsoft. All rights reserved. + +export * from './examplePanel'; diff --git a/src/walkthrough/components/pages/dashboard/panels/index.js b/src/walkthrough/components/pages/dashboard/panels/index.js new file mode 100644 index 000000000..ef0305829 --- /dev/null +++ b/src/walkthrough/components/pages/dashboard/panels/index.js @@ -0,0 +1,3 @@ +// Copyright (c) Microsoft. All rights reserved. + +export * from './examplePanel'; diff --git a/src/walkthrough/components/pages/index.js b/src/walkthrough/components/pages/index.js new file mode 100644 index 000000000..071c53bd1 --- /dev/null +++ b/src/walkthrough/components/pages/index.js @@ -0,0 +1,8 @@ +// Copyright (c) Microsoft. All rights reserved. + +// Exports the app page components + +export * from './basicPage/basicPage.container'; +export * from './dashboard/dashboard.container'; +export * from './pageWithFlyout/pageWithFlyout.container'; +export * from './pageWithGrid/pageWithGrid.container'; diff --git a/src/components/pages/_flyoutExample/flyouts/exampleFlyout/exampleFlyout.container.js b/src/walkthrough/components/pages/pageWithFlyout/flyouts/exampleFlyout/exampleFlyout.container.js similarity index 100% rename from src/components/pages/_flyoutExample/flyouts/exampleFlyout/exampleFlyout.container.js rename to src/walkthrough/components/pages/pageWithFlyout/flyouts/exampleFlyout/exampleFlyout.container.js diff --git a/src/components/pages/_flyoutExample/flyouts/exampleFlyout/exampleFlyout.js b/src/walkthrough/components/pages/pageWithFlyout/flyouts/exampleFlyout/exampleFlyout.js similarity index 76% rename from src/components/pages/_flyoutExample/flyouts/exampleFlyout/exampleFlyout.js rename to src/walkthrough/components/pages/pageWithFlyout/flyouts/exampleFlyout/exampleFlyout.js index edbf5b80d..307312d88 100644 --- a/src/components/pages/_flyoutExample/flyouts/exampleFlyout/exampleFlyout.js +++ b/src/walkthrough/components/pages/pageWithFlyout/flyouts/exampleFlyout/exampleFlyout.js @@ -2,7 +2,7 @@ import React, { Component } from 'react'; -import { ExampleService } from 'services'; +import { ExampleService } from 'walkthrough/services'; import { svgs } from 'utilities'; import { AjaxError, @@ -61,11 +61,11 @@ export class ExampleFlyout extends Component { const { isPending, changesApplied } = this.state; if (isPending) { - return t('examples.flyout.flyouts.example.pending'); + return t('walkthrough.pageWithFlyout.flyouts.example.pending'); } else if (changesApplied) { - return t('examples.flyout.flyouts.example.applySuccess'); + return t('walkthrough.pageWithFlyout.flyouts.example.applySuccess'); } else { - return t('examples.flyout.flyouts.example.affected'); + return t('walkthrough.pageWithFlyout.flyouts.example.affected'); } } @@ -86,7 +86,7 @@ export class ExampleFlyout extends Component { return ( - {t('examples.flyout.flyouts.example.header')} + {t('walkthrough.pageWithFlyout.flyouts.example.header')} @@ -97,14 +97,14 @@ export class ExampleFlyout extends Component { * */ }
-
{t('examples.flyout.flyouts.example.header')}
-
{t('examples.flyout.flyouts.example.description')}
+
{t('walkthrough.pageWithFlyout.flyouts.example.header')}
+
{t('walkthrough.pageWithFlyout.flyouts.example.description')}
-
{t('examples.flyout.flyouts.example.insertFormHere')}
+
{t('walkthrough.pageWithFlyout.flyouts.example.insertFormHere')}
{/** Sumarizes the action being taken; including count of items affected & status/pending indicator */} - {t('examples.flyout.flyouts.example.summaryHeader')} + {t('walkthrough.pageWithFlyout.flyouts.example.summaryHeader')} {summaryCount} {summaryMessage} @@ -119,8 +119,8 @@ export class ExampleFlyout extends Component { /** If changes are not yet applied, show the buttons for applying changes and closing the flyout. */ !changesApplied && - {t('examples.flyout.flyouts.example.apply')} - {t('examples.flyout.flyouts.example.cancel')} + {t('walkthrough.pageWithFlyout.flyouts.example.apply')} + {t('walkthrough.pageWithFlyout.flyouts.example.cancel')} } { @@ -131,7 +131,7 @@ export class ExampleFlyout extends Component { * */ !!changesApplied && - {t('examples.flyout.flyouts.example.close')} + {t('walkthrough.pageWithFlyout.flyouts.example.close')} } diff --git a/src/components/pages/_flyoutExample/flyouts/exampleFlyout/exampleFlyout.scss b/src/walkthrough/components/pages/pageWithFlyout/flyouts/exampleFlyout/exampleFlyout.scss similarity index 100% rename from src/components/pages/_flyoutExample/flyouts/exampleFlyout/exampleFlyout.scss rename to src/walkthrough/components/pages/pageWithFlyout/flyouts/exampleFlyout/exampleFlyout.scss diff --git a/src/components/pages/_flyoutExample/flyouts/exampleFlyout/index.js b/src/walkthrough/components/pages/pageWithFlyout/flyouts/exampleFlyout/index.js similarity index 100% rename from src/components/pages/_flyoutExample/flyouts/exampleFlyout/index.js rename to src/walkthrough/components/pages/pageWithFlyout/flyouts/exampleFlyout/index.js diff --git a/src/walkthrough/components/pages/pageWithFlyout/pageWithFlyout.container.js b/src/walkthrough/components/pages/pageWithFlyout/pageWithFlyout.container.js new file mode 100644 index 000000000..21494b33e --- /dev/null +++ b/src/walkthrough/components/pages/pageWithFlyout/pageWithFlyout.container.js @@ -0,0 +1,7 @@ +// Copyright (c) Microsoft. All rights reserved. + +import { translate } from 'react-i18next'; + +import { PageWithFlyout } from './pageWithFlyout'; + +export const PageWithFlyoutContainer = translate()(PageWithFlyout); diff --git a/src/components/pages/_flyoutExample/flyoutExample.js b/src/walkthrough/components/pages/pageWithFlyout/pageWithFlyout.js similarity index 78% rename from src/components/pages/_flyoutExample/flyoutExample.js rename to src/walkthrough/components/pages/pageWithFlyout/pageWithFlyout.js index a8e832901..77ec0ce8c 100644 --- a/src/components/pages/_flyoutExample/flyoutExample.js +++ b/src/walkthrough/components/pages/pageWithFlyout/pageWithFlyout.js @@ -6,11 +6,11 @@ import { Btn, ContextMenu, PageContent } from 'components/shared'; import { svgs } from 'utilities'; import { ExampleFlyoutContainer } from './flyouts/exampleFlyout'; -import './flyoutExample.css'; +import './pageWithFlyout.css'; const closedFlyoutState = { openFlyoutName: undefined }; -export class FlyoutExample extends Component { +export class PageWithFlyout extends Component { constructor(props) { super(props); this.state = closedFlyoutState; @@ -28,10 +28,10 @@ export class FlyoutExample extends Component { return [ - {t('examples.flyout.open')} + {t('walkthrough.pageWithFlyout.open')} , - - {t('examples.flyout.pageBody')} + + {t('walkthrough.pageWithFlyout.pageBody')} { isExampleFlyoutOpen && } ]; diff --git a/src/components/pages/_flyoutExample/flyoutExample.scss b/src/walkthrough/components/pages/pageWithFlyout/pageWithFlyout.scss similarity index 69% rename from src/components/pages/_flyoutExample/flyoutExample.scss rename to src/walkthrough/components/pages/pageWithFlyout/pageWithFlyout.scss index 046fcf1a9..0c6aa6b80 100644 --- a/src/components/pages/_flyoutExample/flyoutExample.scss +++ b/src/walkthrough/components/pages/pageWithFlyout/pageWithFlyout.scss @@ -4,4 +4,4 @@ @import 'src/styles/mixins'; @import 'src/styles/themes'; -.flyout-example-container { padding: $baseContentPadding; } +.page-with-flyout-container { padding: $baseContentPadding; } diff --git a/src/components/pages/_flyoutExample/flyoutExample.test.js b/src/walkthrough/components/pages/pageWithFlyout/pageWithFlyout.test.js similarity index 66% rename from src/components/pages/_flyoutExample/flyoutExample.test.js rename to src/walkthrough/components/pages/pageWithFlyout/pageWithFlyout.test.js index b6e17be17..7f6f2a0dc 100644 --- a/src/components/pages/_flyoutExample/flyoutExample.test.js +++ b/src/walkthrough/components/pages/pageWithFlyout/pageWithFlyout.test.js @@ -4,9 +4,9 @@ import React from 'react'; import { shallow } from 'enzyme'; import 'polyfills'; -import { FlyoutExample } from './flyoutExample'; +import { PageWithFlyout } from './pageWithFlyout'; -describe('FlyoutExample Component', () => { +describe('PageWithFlyout Component', () => { it('Renders without crashing', () => { const fakeProps = { @@ -14,7 +14,7 @@ describe('FlyoutExample Component', () => { }; const wrapper = shallow( - + ); }); }); diff --git a/src/components/pages/_gridExample/exampleGrid/exampleGrid.js b/src/walkthrough/components/pages/pageWithGrid/exampleGrid/exampleGrid.js similarity index 96% rename from src/components/pages/_gridExample/exampleGrid/exampleGrid.js rename to src/walkthrough/components/pages/pageWithGrid/exampleGrid/exampleGrid.js index f7ece34b7..622994efb 100644 --- a/src/components/pages/_gridExample/exampleGrid/exampleGrid.js +++ b/src/walkthrough/components/pages/pageWithGrid/exampleGrid/exampleGrid.js @@ -32,8 +32,8 @@ export class ExampleGrid extends Component { // Set up the available context buttons. // If these are subject to user permissions, use the Protected component (src/components/shared/protected). this.contextBtns = [ - {props.t('examples.grid.btn1')}, - {props.t('examples.grid.btn2')} + {props.t('walkthrough.pageWithGrid.grid.btn1')}, + {props.t('walkthrough.pageWithGrid.grid.btn2')} ]; } diff --git a/src/components/pages/_gridExample/exampleGrid/exampleGridConfig.js b/src/walkthrough/components/pages/pageWithGrid/exampleGrid/exampleGridConfig.js similarity index 86% rename from src/components/pages/_gridExample/exampleGrid/exampleGridConfig.js rename to src/walkthrough/components/pages/pageWithGrid/exampleGrid/exampleGridConfig.js index 044e4e5c3..440ef2433 100644 --- a/src/components/pages/_gridExample/exampleGrid/exampleGridConfig.js +++ b/src/walkthrough/components/pages/pageWithGrid/exampleGrid/exampleGridConfig.js @@ -6,13 +6,13 @@ import { SoftSelectLinkRenderer } from 'components/shared/cellRenderers'; /** A collection of column definitions for the example grid */ export const exampleColumnDefs = { id: { - headerName: 'examples.grid.name', + headerName: 'walkthrough.pageWithGrid.grid.name', field: 'id', sort: 'asc', cellRendererFramework: SoftSelectLinkRenderer }, description: { - headerName: 'examples.grid.description', + headerName: 'walkthrough.pageWithGrid.grid.description', field: 'descr' } }; diff --git a/src/components/pages/_gridExample/exampleGrid/index.js b/src/walkthrough/components/pages/pageWithGrid/exampleGrid/index.js similarity index 100% rename from src/components/pages/_gridExample/exampleGrid/index.js rename to src/walkthrough/components/pages/pageWithGrid/exampleGrid/index.js diff --git a/src/components/pages/_gridExample/gridExample.container.js b/src/walkthrough/components/pages/pageWithGrid/pageWithGrid.container.js similarity index 74% rename from src/components/pages/_gridExample/gridExample.container.js rename to src/walkthrough/components/pages/pageWithGrid/pageWithGrid.container.js index feefb7d7b..d3a40c0da 100644 --- a/src/components/pages/_gridExample/gridExample.container.js +++ b/src/walkthrough/components/pages/pageWithGrid/pageWithGrid.container.js @@ -9,8 +9,8 @@ import { getExamplesError, getExamplesLastUpdated, getExamplesPendingStatus -} from 'store/reducers/_exampleReducer'; -import { GridExample } from './gridExample'; +} from 'walkthrough/store/reducers/exampleReducer'; +import { PageWithGrid } from './pageWithGrid'; // Pass the data const mapStateToProps = state => ({ @@ -25,4 +25,4 @@ const mapDispatchToProps = dispatch => ({ fetchData: () => dispatch(exampleEpics.actions.fetchExamples()) }); -export const GridExampleContainer = translate()(connect(mapStateToProps, mapDispatchToProps)(GridExample)); +export const PageWithGridContainer = translate()(connect(mapStateToProps, mapDispatchToProps)(PageWithGrid)); diff --git a/src/components/pages/_gridExample/gridExample.js b/src/walkthrough/components/pages/pageWithGrid/pageWithGrid.js similarity index 89% rename from src/components/pages/_gridExample/gridExample.js rename to src/walkthrough/components/pages/pageWithGrid/pageWithGrid.js index 4a8c839c9..62cf2b4d8 100644 --- a/src/components/pages/_gridExample/gridExample.js +++ b/src/walkthrough/components/pages/pageWithGrid/pageWithGrid.js @@ -10,9 +10,9 @@ import { } from 'components/shared'; import { ExampleGrid } from './exampleGrid'; -import './gridExample.css'; +import './pageWithGrid.css'; -export class GridExample extends Component { +export class PageWithGrid extends Component { constructor(props) { super(props); this.state = { contextBtns: null }; @@ -40,7 +40,7 @@ export class GridExample extends Component { {this.state.contextBtns} , - + {!!error && } {!error && } diff --git a/src/components/pages/_gridExample/gridExample.scss b/src/walkthrough/components/pages/pageWithGrid/pageWithGrid.scss similarity index 70% rename from src/components/pages/_gridExample/gridExample.scss rename to src/walkthrough/components/pages/pageWithGrid/pageWithGrid.scss index 0c893380c..6cea2ea32 100644 --- a/src/components/pages/_gridExample/gridExample.scss +++ b/src/walkthrough/components/pages/pageWithGrid/pageWithGrid.scss @@ -4,4 +4,4 @@ @import 'src/styles/mixins'; @import 'src/styles/themes'; -.grid-example-container { padding: $baseContentPadding; } +.page-with-grid-container { padding: $baseContentPadding; } diff --git a/src/components/pages/_gridExample/gridExample.test.js b/src/walkthrough/components/pages/pageWithGrid/pageWithGrid.test.js similarity index 75% rename from src/components/pages/_gridExample/gridExample.test.js rename to src/walkthrough/components/pages/pageWithGrid/pageWithGrid.test.js index b4ae1b882..631e59f41 100644 --- a/src/components/pages/_gridExample/gridExample.test.js +++ b/src/walkthrough/components/pages/pageWithGrid/pageWithGrid.test.js @@ -4,9 +4,9 @@ import React from 'react'; import { shallow } from 'enzyme'; import 'polyfills'; -import { GridExample } from './gridExample'; +import { PageWithGrid } from './pageWithGrid'; -describe('GridExample Component', () => { +describe('PageWithGrid Component', () => { it('Renders without crashing', () => { const fakeProps = { @@ -19,7 +19,7 @@ describe('GridExample Component', () => { }; const wrapper = shallow( - + ); }); }); diff --git a/src/services/_exampleService.js b/src/walkthrough/services/exampleService.js similarity index 100% rename from src/services/_exampleService.js rename to src/walkthrough/services/exampleService.js diff --git a/src/walkthrough/services/index.js b/src/walkthrough/services/index.js new file mode 100644 index 000000000..1eed9f524 --- /dev/null +++ b/src/walkthrough/services/index.js @@ -0,0 +1,5 @@ +// Copyright (c) Microsoft. All rights reserved. + +// Exports the shared react components into as a library + +export * from './exampleService'; diff --git a/src/services/models/_exampleModels.js b/src/walkthrough/services/models/exampleModels.js similarity index 100% rename from src/services/models/_exampleModels.js rename to src/walkthrough/services/models/exampleModels.js diff --git a/src/walkthrough/services/models/index.js b/src/walkthrough/services/models/index.js new file mode 100644 index 000000000..5e7ed9a22 --- /dev/null +++ b/src/walkthrough/services/models/index.js @@ -0,0 +1,5 @@ +// Copyright (c) Microsoft. All rights reserved. + +// Exports models + +export * from './exampleModels'; diff --git a/src/walkthrough/store/configureStore.js b/src/walkthrough/store/configureStore.js new file mode 100644 index 000000000..0986e8b81 --- /dev/null +++ b/src/walkthrough/store/configureStore.js @@ -0,0 +1,19 @@ +// Copyright (c) Microsoft. All rights reserved. + +import { createStore, applyMiddleware } from 'redux'; +import { createEpicMiddleware } from 'redux-observable'; +import { composeWithDevTools } from 'redux-devtools-extension'; +import rootEpic from './rootEpic'; +import rootReducer from './rootReducer'; + +export function configureStore() { + // Initialize the redux-observable epics + const epicMiddleware = createEpicMiddleware(rootEpic); + // Initialize the redux store with middleware + return createStore( + rootReducer, + composeWithDevTools( + applyMiddleware(epicMiddleware) + ) + ); +} diff --git a/src/store/reducers/_exampleReducer.js b/src/walkthrough/store/reducers/exampleReducer.js similarity index 98% rename from src/store/reducers/_exampleReducer.js rename to src/walkthrough/store/reducers/exampleReducer.js index 8ae37a311..00f3b9af5 100644 --- a/src/store/reducers/_exampleReducer.js +++ b/src/walkthrough/store/reducers/exampleReducer.js @@ -6,7 +6,7 @@ import moment from 'moment'; import { schema, normalize } from 'normalizr'; import update from 'immutability-helper'; import { createSelector } from 'reselect'; -import { ExampleService } from 'services'; +import { ExampleService } from 'walkthrough/services'; import { createReducerScenario, createEpicScenario, diff --git a/src/walkthrough/store/rootEpic.js b/src/walkthrough/store/rootEpic.js new file mode 100644 index 000000000..e85807ebb --- /dev/null +++ b/src/walkthrough/store/rootEpic.js @@ -0,0 +1,19 @@ +// Copyright (c) Microsoft. All rights reserved. + +import { combineEpics } from 'redux-observable'; + +// Epics +import { epics as appEpics } from 'store/reducers/appReducer'; +import { epics as deviceSimulationEpics } from 'store/reducers/deviceSimulationReducer'; +import { epics as exampleEpics } from './reducers/exampleReducer'; + +// Extract the epic function from each property object +const epics = [ + ...appEpics.getEpics(), + ...deviceSimulationEpics.getEpics(), + ...exampleEpics.getEpics() +]; + +const rootEpic = combineEpics(...epics); + +export default rootEpic; diff --git a/src/walkthrough/store/rootReducer.js b/src/walkthrough/store/rootReducer.js new file mode 100644 index 000000000..3e1f0feb5 --- /dev/null +++ b/src/walkthrough/store/rootReducer.js @@ -0,0 +1,16 @@ +// Copyright (c) Microsoft. All rights reserved. + +import { combineReducers } from 'redux'; + +// Reducers +import { reducer as appReducer } from 'store/reducers/appReducer'; +import { reducer as deviceSimulationReducer } from 'store/reducers/deviceSimulationReducer'; +import { reducer as exampleReducer } from './reducers/exampleReducer'; + +const rootReducer = combineReducers({ + ...appReducer, + ...deviceSimulationReducer, + ...exampleReducer +}); + +export default rootReducer;