diff --git a/src/api/projectReports.js b/src/api/projectReports.js index 6dfa68ba1..9ccb0411c 100644 --- a/src/api/projectReports.js +++ b/src/api/projectReports.js @@ -51,4 +51,13 @@ export function getProjectSummary(projectId) { export function getProjectReportUrl(projectId, reportName) { return axios.get(`${PROJECTS_API_URL}/v5/projects/${projectId}/reports/embed?reportName=${reportName}`) .then(resp => resp.data) +} + +/** + * Gets signed URL for embeding the requested report. + * @param {*} reportName unique name of the report + */ +export function getUserReportUrl(reportName) { + return axios.get(`${PROJECTS_API_URL}/v5/projects/reports/embed?reportName=${reportName}`) + .then(resp => resp.data) } \ No newline at end of file diff --git a/src/components/UserSidebar/UserSidebar.jsx b/src/components/UserSidebar/UserSidebar.jsx index 7dd5f53c9..c234185d4 100644 --- a/src/components/UserSidebar/UserSidebar.jsx +++ b/src/components/UserSidebar/UserSidebar.jsx @@ -9,6 +9,7 @@ import MenuList from '../MenuList/MenuList' import NotificationsIcon from '../../assets/icons/ui-bell.svg' import AllProjectsIcon from '../../assets/icons/v.2.5/icon-all-projects.svg' import MyProfileIcon from '../../assets/icons/v.2.5/icon-my-profile.svg' +import ReportsIcon from '../../assets/icons/v.2.5/icon-reports.svg' import NotificationSettingsIcon from '../../assets/icons/v.2.5/icon-notification-setting.svg' import AccountSecurityIcon from '../../assets/icons/v.2.5/icon-account-security.svg' @@ -20,6 +21,12 @@ const navLinks = [{ Icon: AllProjectsIcon, iconClassName: 'fill', exact: false, +}, { + label: 'REPORTS', + to: '/reports', + Icon: ReportsIcon, + iconClassName: 'stroke', + exact: false, }, { label: 'MY PROFILE', to: '/settings/profile', diff --git a/src/config/constants.js b/src/config/constants.js index d0acc1943..6d67af5b2 100644 --- a/src/config/constants.js +++ b/src/config/constants.js @@ -419,6 +419,12 @@ export const LOAD_PROJECT_SUMMARY_SUCCESS = 'LOAD_PROJECT_SUMMARY_SUCCESS' export const LOAD_PROJECT_SUMMARY_FAILURE = 'LOAD_PROJECT_SUMMARY_FAILURE' export const SET_LOOKER_SESSION_EXPIRED = 'SET_LOOKER_SESSION_EXPIRED' +// User Reports +export const LOAD_USER_REPORTS = 'LOAD_USER_REPORTS' +export const LOAD_USER_REPORTS_PENDING = 'LOAD_USER_REPORTS_PENDING' +export const LOAD_USER_REPORTS_SUCCESS = 'LOAD_USER_REPORTS_SUCCESS' +export const LOAD_USER_REPORTS_FAILURE = 'LOAD_USER_REPORTS_FAILURE' + // Product attachments export const ADD_PRODUCT_ATTACHMENT = 'ADD_PRODUCT_ATTACHMENT' export const ADD_PRODUCT_ATTACHMENT_PENDING = 'ADD_PRODUCT_ATTACHMENT_PENDING' diff --git a/src/reducers/index.js b/src/reducers/index.js index 36e40fc17..722b3d6bf 100644 --- a/src/reducers/index.js +++ b/src/reducers/index.js @@ -7,6 +7,7 @@ import { projectTopics } from '../projects/reducers/projectTopics' import { topics } from './topics' import { productsTimelines } from '../projects/reducers/productsTimelines' import { projectReports } from '../projects/reducers/projectReports' +import { userReports } from '../routes/reports/reducers' import navSearch from './navSearch' import projectSearch from '../projects/reducers/projectSearch' import projectSearchSuggestions from '../projects/reducers/projectSearchSuggestions' @@ -33,4 +34,5 @@ export default combineReducers({ settings, templates, productsTimelines, + userReports, }) diff --git a/src/routes.jsx b/src/routes.jsx index f12a62495..ddbf6cd72 100644 --- a/src/routes.jsx +++ b/src/routes.jsx @@ -9,6 +9,7 @@ import projectRoutes from './projects/routes.jsx' import notificationsRoutes from './routes/notifications/routes.jsx' import settingsRoutes from './routes/settings/routes.jsx' import metaDataRoutes from './routes/metadata/routes.jsx' +import reportsListRoutes from './routes/reports/routes' import TopBarContainer from './components/TopBar/TopBarContainer' import ProjectsToolBar from './components/TopBar/ProjectsToolBar' import RedirectComponent from './components/RedirectComponent' @@ -154,7 +155,7 @@ class Routes extends React.Component { {/* Handle /projects/* routes */} {projectRoutes} - {/* {reportsListRoutes} */} + {reportsListRoutes} {notificationsRoutes} {settingsRoutes} {metaDataRoutes} diff --git a/src/routes/reports/actions/index.js b/src/routes/reports/actions/index.js new file mode 100644 index 000000000..b50dee87b --- /dev/null +++ b/src/routes/reports/actions/index.js @@ -0,0 +1,34 @@ +import { + LOAD_USER_REPORTS, + SET_LOOKER_SESSION_EXPIRED, +} from '../../../config/constants' +import { + getUserReportUrl, +} from '../../../api/projectReports' + +/** + * Redux action to start fetching the signed URL for embeding the given report + * @param {*} reportName unique name of the report + */ +export function loadUserReportsUrls(reportName) { + return (dispatch) => { + return dispatch({ + type: LOAD_USER_REPORTS, + payload: getUserReportUrl(reportName), + }) + } +} + +/** + * Redux action set the flag `lookerSessionExpired` + * + * @param {Boolean} isExpired true to indicate that looker session is expired + */ +export function setLookerSessionExpired(isExpired) { + return (dispatch) => { + return dispatch({ + type: SET_LOOKER_SESSION_EXPIRED, + payload: { lookerSessionExpired: isExpired } + }) + } +} \ No newline at end of file diff --git a/src/routes/reports/containers/UserReportsContainer.jsx b/src/routes/reports/containers/UserReportsContainer.jsx new file mode 100644 index 000000000..c45f47fd3 --- /dev/null +++ b/src/routes/reports/containers/UserReportsContainer.jsx @@ -0,0 +1,125 @@ +import React, { Component } from 'react' +import MediaQuery from 'react-responsive' +import Modal from 'react-modal' +import Sticky from 'react-stickynode' +import { connect } from 'react-redux' +import { withRouter } from 'react-router-dom' +import { loadUserReportsUrls, setLookerSessionExpired } from '../actions' +import { SCREEN_BREAKPOINT_MD, PROJECT_REPORTS, REPORT_SESSION_LENGTH } from '../../../config/constants' +import TwoColsLayout from '../../../components/TwoColsLayout' +import UserSidebar from '../../../components/UserSidebar/UserSidebar' +import spinnerWhileLoading from '../../../components/LoadingSpinner' + +const LookerEmbedReport = (props) => { + return () +} + +const EnhancedLookerEmbedReport = spinnerWhileLoading(props => { + return !props.isLoading +})(LookerEmbedReport) + +class UserReportsContainer extends Component { + constructor(props) { + super(props) + + this.timer = null + this.setLookerSessionTimer = this.setLookerSessionTimer.bind(this) + this.reloadProjectReport = this.reloadProjectReport.bind(this) + } + + reloadProjectReport() { + this.props.loadUserReportsUrls(PROJECT_REPORTS.PROJECT_SUMMARY) + // don't have to set session expire timer here, it would be set of iframe load + } + + componentWillMount() { + this.reloadProjectReport() + // don't have to set session expire timer here, it would be set of iframe load + } + + componentWillUnmount() { + if (this.timer) { + clearTimeout(this.timer) + } + } + + setLookerSessionTimer() { + console.log('Setting Looker Session Timer') + + if (this.timer) { + clearTimeout(this.timer) + } + + // set timeout for raising alert to refresh the token when session expires + this.timer = setTimeout(() => { + console.log('Looker Session is expired by timer') + this.props.setLookerSessionExpired(true) + window.analytics && window.analytics.track('Looker Session Expired') + }, REPORT_SESSION_LENGTH * 1000) + } + + render() { + const { user, isLoading, userReportsEmbedUrl, lookerSessionExpired } = this.props + + return ( + + + + {(matches) => { + if (matches) { + return ( + + + + ) + } else { + return + } + }} + + + + + + Report sessions expired + + + + To keep the data up to date, please, hit "Refresh" button to reload the report. + + + + Refresh + + + + + + ) + } +} +const mapStateToProps = ({ loadUser, userReports }) => { + + return { + user: loadUser.user, + isLoading: userReports.isLoading, + lookerSessionExpired: userReports.lookerSessionExpired, + userReportsEmbedUrl: userReports.userReportsEmbedUrl, + } +} + +const mapDispatchToProps = { + loadUserReportsUrls, + setLookerSessionExpired, +} + +export default connect(mapStateToProps, mapDispatchToProps)(withRouter(UserReportsContainer)) \ No newline at end of file diff --git a/src/routes/reports/reducers/index.js b/src/routes/reports/reducers/index.js new file mode 100644 index 000000000..2dad42c58 --- /dev/null +++ b/src/routes/reports/reducers/index.js @@ -0,0 +1,51 @@ +import { + LOAD_USER_REPORTS_PENDING, + LOAD_USER_REPORTS_SUCCESS, + LOAD_USER_REPORTS_FAILURE, + SET_LOOKER_SESSION_EXPIRED, +} from '../../../config/constants' + +const initialState = { + isLoading: false, + error: false, + userReports: null, + userReportsEmbedUrl: null, + lookerSessionExpired: false, +} + +export const userReports = function (state=initialState, action) { + const payload = action.payload + + switch (action.type) { + case LOAD_USER_REPORTS_PENDING: + return Object.assign({}, state, { + isLoading: true, + error: false, + }) + + case LOAD_USER_REPORTS_SUCCESS: + return Object.assign({}, state, { + isLoading: false, + error: false, + userReportsEmbedUrl: payload, + lookerSessionExpired: false, + }) + + case LOAD_USER_REPORTS_FAILURE: { + return Object.assign({}, state, { + isLoading: false, + error: payload + }) + } + + case SET_LOOKER_SESSION_EXPIRED: { + return Object.assign({}, state, { + lookerSessionExpired: payload + }) + } + + default: + return state + } +} + \ No newline at end of file diff --git a/src/routes/reports/routes.jsx b/src/routes/reports/routes.jsx new file mode 100644 index 000000000..aff3e436a --- /dev/null +++ b/src/routes/reports/routes.jsx @@ -0,0 +1,15 @@ +/** + * Settings routes + */ +import React from 'react' +import { Route } from 'react-router-dom' +import { renderApp } from '../../components/App/App' +import TopBarContainer from '../../components/TopBar/TopBarContainer' +import ProjectsToolBar from '../../components/TopBar/ProjectsToolBar' +import { requiresAuthentication } from '../../components/AuthenticatedComponent' +import UserReportsContainer from './containers/UserReportsContainer' +const UserReportsContainerWithAuth = requiresAuthentication(UserReportsContainer) +export default [ + , )} />, + +]