Skip to content

Commit 3e68fd1

Browse files
author
vikasrohit
authored
Merge pull request #3859 from appirio-tech/feature/user_level_reports
User level reports
2 parents f7e35b6 + 9d32df9 commit 3e68fd1

File tree

9 files changed

+251
-1
lines changed

9 files changed

+251
-1
lines changed

src/api/projectReports.js

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,4 +51,13 @@ export function getProjectSummary(projectId) {
5151
export function getProjectReportUrl(projectId, reportName) {
5252
return axios.get(`${PROJECTS_API_URL}/v5/projects/${projectId}/reports/embed?reportName=${reportName}`)
5353
.then(resp => resp.data)
54+
}
55+
56+
/**
57+
* Gets signed URL for embeding the requested report.
58+
* @param {*} reportName unique name of the report
59+
*/
60+
export function getUserReportUrl(reportName) {
61+
return axios.get(`${PROJECTS_API_URL}/v5/projects/reports/embed?reportName=${reportName}`)
62+
.then(resp => resp.data)
5463
}

src/components/UserSidebar/UserSidebar.jsx

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import MenuList from '../MenuList/MenuList'
99
import NotificationsIcon from '../../assets/icons/ui-bell.svg'
1010
import AllProjectsIcon from '../../assets/icons/v.2.5/icon-all-projects.svg'
1111
import MyProfileIcon from '../../assets/icons/v.2.5/icon-my-profile.svg'
12+
import ReportsIcon from '../../assets/icons/v.2.5/icon-reports.svg'
1213
import NotificationSettingsIcon from '../../assets/icons/v.2.5/icon-notification-setting.svg'
1314
import AccountSecurityIcon from '../../assets/icons/v.2.5/icon-account-security.svg'
1415

@@ -20,6 +21,12 @@ const navLinks = [{
2021
Icon: AllProjectsIcon,
2122
iconClassName: 'fill',
2223
exact: false,
24+
}, {
25+
label: 'REPORTS',
26+
to: '/reports',
27+
Icon: ReportsIcon,
28+
iconClassName: 'stroke',
29+
exact: false,
2330
}, {
2431
label: 'MY PROFILE',
2532
to: '/settings/profile',

src/config/constants.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -419,6 +419,12 @@ export const LOAD_PROJECT_SUMMARY_SUCCESS = 'LOAD_PROJECT_SUMMARY_SUCCESS'
419419
export const LOAD_PROJECT_SUMMARY_FAILURE = 'LOAD_PROJECT_SUMMARY_FAILURE'
420420
export const SET_LOOKER_SESSION_EXPIRED = 'SET_LOOKER_SESSION_EXPIRED'
421421

422+
// User Reports
423+
export const LOAD_USER_REPORTS = 'LOAD_USER_REPORTS'
424+
export const LOAD_USER_REPORTS_PENDING = 'LOAD_USER_REPORTS_PENDING'
425+
export const LOAD_USER_REPORTS_SUCCESS = 'LOAD_USER_REPORTS_SUCCESS'
426+
export const LOAD_USER_REPORTS_FAILURE = 'LOAD_USER_REPORTS_FAILURE'
427+
422428
// Product attachments
423429
export const ADD_PRODUCT_ATTACHMENT = 'ADD_PRODUCT_ATTACHMENT'
424430
export const ADD_PRODUCT_ATTACHMENT_PENDING = 'ADD_PRODUCT_ATTACHMENT_PENDING'

src/reducers/index.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { projectTopics } from '../projects/reducers/projectTopics'
77
import { topics } from './topics'
88
import { productsTimelines } from '../projects/reducers/productsTimelines'
99
import { projectReports } from '../projects/reducers/projectReports'
10+
import { userReports } from '../routes/reports/reducers'
1011
import navSearch from './navSearch'
1112
import projectSearch from '../projects/reducers/projectSearch'
1213
import projectSearchSuggestions from '../projects/reducers/projectSearchSuggestions'
@@ -33,4 +34,5 @@ export default combineReducers({
3334
settings,
3435
templates,
3536
productsTimelines,
37+
userReports,
3638
})

src/routes.jsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import projectRoutes from './projects/routes.jsx'
99
import notificationsRoutes from './routes/notifications/routes.jsx'
1010
import settingsRoutes from './routes/settings/routes.jsx'
1111
import metaDataRoutes from './routes/metadata/routes.jsx'
12+
import reportsListRoutes from './routes/reports/routes'
1213
import TopBarContainer from './components/TopBar/TopBarContainer'
1314
import ProjectsToolBar from './components/TopBar/ProjectsToolBar'
1415
import RedirectComponent from './components/RedirectComponent'
@@ -154,7 +155,7 @@ class Routes extends React.Component {
154155

155156
{/* Handle /projects/* routes */}
156157
{projectRoutes}
157-
{/* {reportsListRoutes} */}
158+
{reportsListRoutes}
158159
{notificationsRoutes}
159160
{settingsRoutes}
160161
{metaDataRoutes}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import {
2+
LOAD_USER_REPORTS,
3+
SET_LOOKER_SESSION_EXPIRED,
4+
} from '../../../config/constants'
5+
import {
6+
getUserReportUrl,
7+
} from '../../../api/projectReports'
8+
9+
/**
10+
* Redux action to start fetching the signed URL for embeding the given report
11+
* @param {*} reportName unique name of the report
12+
*/
13+
export function loadUserReportsUrls(reportName) {
14+
return (dispatch) => {
15+
return dispatch({
16+
type: LOAD_USER_REPORTS,
17+
payload: getUserReportUrl(reportName),
18+
})
19+
}
20+
}
21+
22+
/**
23+
* Redux action set the flag `lookerSessionExpired`
24+
*
25+
* @param {Boolean} isExpired true to indicate that looker session is expired
26+
*/
27+
export function setLookerSessionExpired(isExpired) {
28+
return (dispatch) => {
29+
return dispatch({
30+
type: SET_LOOKER_SESSION_EXPIRED,
31+
payload: { lookerSessionExpired: isExpired }
32+
})
33+
}
34+
}
Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
import React, { Component } from 'react'
2+
import MediaQuery from 'react-responsive'
3+
import Modal from 'react-modal'
4+
import Sticky from 'react-stickynode'
5+
import { connect } from 'react-redux'
6+
import { withRouter } from 'react-router-dom'
7+
import { loadUserReportsUrls, setLookerSessionExpired } from '../actions'
8+
import { SCREEN_BREAKPOINT_MD, PROJECT_REPORTS, REPORT_SESSION_LENGTH } from '../../../config/constants'
9+
import TwoColsLayout from '../../../components/TwoColsLayout'
10+
import UserSidebar from '../../../components/UserSidebar/UserSidebar'
11+
import spinnerWhileLoading from '../../../components/LoadingSpinner'
12+
13+
const LookerEmbedReport = (props) => {
14+
return (<iframe width="100%" src={props.userReportsEmbedUrl} onLoad={props.onLoad} />)
15+
}
16+
17+
const EnhancedLookerEmbedReport = spinnerWhileLoading(props => {
18+
return !props.isLoading
19+
})(LookerEmbedReport)
20+
21+
class UserReportsContainer extends Component {
22+
constructor(props) {
23+
super(props)
24+
25+
this.timer = null
26+
this.setLookerSessionTimer = this.setLookerSessionTimer.bind(this)
27+
this.reloadProjectReport = this.reloadProjectReport.bind(this)
28+
}
29+
30+
reloadProjectReport() {
31+
this.props.loadUserReportsUrls(PROJECT_REPORTS.PROJECT_SUMMARY)
32+
// don't have to set session expire timer here, it would be set of iframe load
33+
}
34+
35+
componentWillMount() {
36+
this.reloadProjectReport()
37+
// don't have to set session expire timer here, it would be set of iframe load
38+
}
39+
40+
componentWillUnmount() {
41+
if (this.timer) {
42+
clearTimeout(this.timer)
43+
}
44+
}
45+
46+
setLookerSessionTimer() {
47+
console.log('Setting Looker Session Timer')
48+
49+
if (this.timer) {
50+
clearTimeout(this.timer)
51+
}
52+
53+
// set timeout for raising alert to refresh the token when session expires
54+
this.timer = setTimeout(() => {
55+
console.log('Looker Session is expired by timer')
56+
this.props.setLookerSessionExpired(true)
57+
window.analytics && window.analytics.track('Looker Session Expired')
58+
}, REPORT_SESSION_LENGTH * 1000)
59+
}
60+
61+
render() {
62+
const { user, isLoading, userReportsEmbedUrl, lookerSessionExpired } = this.props
63+
64+
return (
65+
<TwoColsLayout noPadding>
66+
<TwoColsLayout.Sidebar>
67+
<MediaQuery minWidth={SCREEN_BREAKPOINT_MD}>
68+
{(matches) => {
69+
if (matches) {
70+
return (
71+
<Sticky top={60} bottomBoundary="#wrapper-main">
72+
<UserSidebar user={user}/>
73+
</Sticky>
74+
)
75+
} else {
76+
return <UserSidebar user={user}/>
77+
}
78+
}}
79+
</MediaQuery>
80+
</TwoColsLayout.Sidebar>
81+
<TwoColsLayout.Content>
82+
<Modal
83+
isOpen={lookerSessionExpired && !isLoading}
84+
className="delete-post-dialog"
85+
overlayClassName="delete-post-dialog-overlay"
86+
contentLabel=""
87+
>
88+
<div className="modal-title">
89+
Report sessions expired
90+
</div>
91+
92+
<div className="modal-body">
93+
To keep the data up to date, please, hit "Refresh" button to reload the report.
94+
</div>
95+
96+
<div className="button-area flex center action-area">
97+
<button className="tc-btn tc-btn-primary tc-btn-sm" onClick={this.reloadProjectReport}>Refresh</button>
98+
</div>
99+
</Modal>
100+
<EnhancedLookerEmbedReport
101+
isLoading={isLoading}
102+
userReportsEmbedUrl={userReportsEmbedUrl}
103+
onLoad={this.setLookerSessionTimer}
104+
/>
105+
</TwoColsLayout.Content>
106+
</TwoColsLayout>
107+
)
108+
}
109+
}
110+
const mapStateToProps = ({ loadUser, userReports }) => {
111+
112+
return {
113+
user: loadUser.user,
114+
isLoading: userReports.isLoading,
115+
lookerSessionExpired: userReports.lookerSessionExpired,
116+
userReportsEmbedUrl: userReports.userReportsEmbedUrl,
117+
}
118+
}
119+
120+
const mapDispatchToProps = {
121+
loadUserReportsUrls,
122+
setLookerSessionExpired,
123+
}
124+
125+
export default connect(mapStateToProps, mapDispatchToProps)(withRouter(UserReportsContainer))
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import {
2+
LOAD_USER_REPORTS_PENDING,
3+
LOAD_USER_REPORTS_SUCCESS,
4+
LOAD_USER_REPORTS_FAILURE,
5+
SET_LOOKER_SESSION_EXPIRED,
6+
} from '../../../config/constants'
7+
8+
const initialState = {
9+
isLoading: false,
10+
error: false,
11+
userReports: null,
12+
userReportsEmbedUrl: null,
13+
lookerSessionExpired: false,
14+
}
15+
16+
export const userReports = function (state=initialState, action) {
17+
const payload = action.payload
18+
19+
switch (action.type) {
20+
case LOAD_USER_REPORTS_PENDING:
21+
return Object.assign({}, state, {
22+
isLoading: true,
23+
error: false,
24+
})
25+
26+
case LOAD_USER_REPORTS_SUCCESS:
27+
return Object.assign({}, state, {
28+
isLoading: false,
29+
error: false,
30+
userReportsEmbedUrl: payload,
31+
lookerSessionExpired: false,
32+
})
33+
34+
case LOAD_USER_REPORTS_FAILURE: {
35+
return Object.assign({}, state, {
36+
isLoading: false,
37+
error: payload
38+
})
39+
}
40+
41+
case SET_LOOKER_SESSION_EXPIRED: {
42+
return Object.assign({}, state, {
43+
lookerSessionExpired: payload
44+
})
45+
}
46+
47+
default:
48+
return state
49+
}
50+
}
51+

src/routes/reports/routes.jsx

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
/**
2+
* Settings routes
3+
*/
4+
import React from 'react'
5+
import { Route } from 'react-router-dom'
6+
import { renderApp } from '../../components/App/App'
7+
import TopBarContainer from '../../components/TopBar/TopBarContainer'
8+
import ProjectsToolBar from '../../components/TopBar/ProjectsToolBar'
9+
import { requiresAuthentication } from '../../components/AuthenticatedComponent'
10+
import UserReportsContainer from './containers/UserReportsContainer'
11+
const UserReportsContainerWithAuth = requiresAuthentication(UserReportsContainer)
12+
export default [
13+
<Route key="reports" exact path="/reports" render={renderApp(<TopBarContainer toolbar={ProjectsToolBar} />, <UserReportsContainerWithAuth />)} />,
14+
15+
]

0 commit comments

Comments
 (0)