Skip to content

Commit

Permalink
Merge pull request #3863 from appirio-tech/feature/user_level_reports
Browse files Browse the repository at this point in the history
Initial working draft for user level reports
  • Loading branch information
vikasrohit authored Apr 9, 2020
2 parents 5f21882 + 9d32df9 commit 8daf3ee
Show file tree
Hide file tree
Showing 9 changed files with 251 additions and 1 deletion.
9 changes: 9 additions & 0 deletions src/api/projectReports.js
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
7 changes: 7 additions & 0 deletions src/components/UserSidebar/UserSidebar.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -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'

Expand All @@ -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',
Expand Down
6 changes: 6 additions & 0 deletions src/config/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down
2 changes: 2 additions & 0 deletions src/reducers/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand All @@ -33,4 +34,5 @@ export default combineReducers({
settings,
templates,
productsTimelines,
userReports,
})
3 changes: 2 additions & 1 deletion src/routes.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down Expand Up @@ -154,7 +155,7 @@ class Routes extends React.Component {

{/* Handle /projects/* routes */}
{projectRoutes}
{/* {reportsListRoutes} */}
{reportsListRoutes}
{notificationsRoutes}
{settingsRoutes}
{metaDataRoutes}
Expand Down
34 changes: 34 additions & 0 deletions src/routes/reports/actions/index.js
Original file line number Diff line number Diff line change
@@ -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 }
})
}
}
125 changes: 125 additions & 0 deletions src/routes/reports/containers/UserReportsContainer.jsx
Original file line number Diff line number Diff line change
@@ -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 (<iframe width="100%" src={props.userReportsEmbedUrl} onLoad={props.onLoad} />)
}

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 (
<TwoColsLayout noPadding>
<TwoColsLayout.Sidebar>
<MediaQuery minWidth={SCREEN_BREAKPOINT_MD}>
{(matches) => {
if (matches) {
return (
<Sticky top={60} bottomBoundary="#wrapper-main">
<UserSidebar user={user}/>
</Sticky>
)
} else {
return <UserSidebar user={user}/>
}
}}
</MediaQuery>
</TwoColsLayout.Sidebar>
<TwoColsLayout.Content>
<Modal
isOpen={lookerSessionExpired && !isLoading}
className="delete-post-dialog"
overlayClassName="delete-post-dialog-overlay"
contentLabel=""
>
<div className="modal-title">
Report sessions expired
</div>

<div className="modal-body">
To keep the data up to date, please, hit "Refresh" button to reload the report.
</div>

<div className="button-area flex center action-area">
<button className="tc-btn tc-btn-primary tc-btn-sm" onClick={this.reloadProjectReport}>Refresh</button>
</div>
</Modal>
<EnhancedLookerEmbedReport
isLoading={isLoading}
userReportsEmbedUrl={userReportsEmbedUrl}
onLoad={this.setLookerSessionTimer}
/>
</TwoColsLayout.Content>
</TwoColsLayout>
)
}
}
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))
51 changes: 51 additions & 0 deletions src/routes/reports/reducers/index.js
Original file line number Diff line number Diff line change
@@ -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
}
}

15 changes: 15 additions & 0 deletions src/routes/reports/routes.jsx
Original file line number Diff line number Diff line change
@@ -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 [
<Route key="reports" exact path="/reports" render={renderApp(<TopBarContainer toolbar={ProjectsToolBar} />, <UserReportsContainerWithAuth />)} />,

]

0 comments on commit 8daf3ee

Please sign in to comment.