Skip to content

Commit

Permalink
Godirectory (#813)
Browse files Browse the repository at this point in the history
* feat: godirectory endpoint draft 1

- add GET endpoint for /api/directory/search
- requires query, order, limit, offset and isEmail
- decision to search by email or text is done in UrlRepository
- to be done: use mappers
- to be done: change hardcoded sql variables and sql query checkers
- to be done: include user guard for route
- to be done: isolate filter checkers into helper functions

* feat: godirectory dashboard

- add directory ui
- add directory redirection
- add actions and reducers for directory
- remove userid that was previously added to urltypes
- refactor endpoint

* feat: include email in directory

- include individual email in search results

* fix: ui and redirection for directory

- add mini go logo for mobile version
- ensure directory to redirect back to itself on refresh
- stop pagination row and sort panel from intersecting
- fix svg resizing
- refactor domain validation

* fix: filter panel and ui tweaks

- expand filter button to cover square area
- replace go logo with mini version for mobile mode
- clip shorturl in mobile panel for directory

* refactor: add userguard to directory endpoint

- change the jsdoc for rawdirectoryinput

* refactor: godirectory

- refactor code for redirection to directory page from login page
- remove the new email validation and use the old validation
- replace lengthy arguments with directoryquerycondition
- make isfile condition explicit
- refactor rawdirectorysearch
- add noopener and noreferrer to window open and anchor tag
- remove semicolon for both directory and search headers
- add test for directorycontroller
- add test for directorysearchservice
- add test for urlrepository

* feat: event tracking and redirection

- track ga event and sentry event for directory search for both success and error case
- track ga event and pageview when entering into directory page
- provide redirection to directory page in error message on link creation modal

* fix: directory reset and announcement modal

- add new reset result state and action in redux
- reset to initial result state when toggle between keyword and email in directory
- add new svg to announcement modal
- add line break functionality to announcement message

* Update BaseLayoutHeader.jsx

* Update UrlManagementService.ts

* fix: ga tracking and refactor

Co-authored-by: oxiang <[email protected]>
  • Loading branch information
Oxiang and oxiang authored Nov 3, 2020
1 parent 07738c7 commit ff6d42c
Show file tree
Hide file tree
Showing 75 changed files with 3,525 additions and 32 deletions.
8 changes: 4 additions & 4 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,11 +32,11 @@ services:
- VALID_EMAIL_GLOB_EXPRESSION=*.gov.sg
- LOGIN_MESSAGE=Your OTP might take awhile to get to you.
- USER_MESSAGE=User message test
- ANNOUNCEMENT_MESSAGE=The quick brown fox jumps over a lazy dog
- ANNOUNCEMENT_TITLE=Some title
- ANNOUNCEMENT_SUBTITLE=Subtitle is found here
- ANNOUNCEMENT_MESSAGE=Search by email to find link owners, or by keyword to discover other links! \n PRO TIP! Search your email domain to find out all the links made by your agency.
- ANNOUNCEMENT_TITLE=GoDirectory is here!
- ANNOUNCEMENT_SUBTITLE=Search all go.gov.sg links
- ANNOUNCEMENT_URL=https://go.gov.sg/
- ANNOUNCEMENT_IMAGE=/assets/transition-page/images/browser.svg
- ANNOUNCEMENT_IMAGE=/assets/transition-page/images/directory-browser.svg
- AWS_S3_BUCKET=local-bucket
- ROTATED_LINKS=whatsapp,passport,spsc,sppr
- AWS_ACCESS_KEY_ID=foobar
Expand Down
20 changes: 20 additions & 0 deletions public/assets/transition-page/images/directory-browser.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
126 changes: 126 additions & 0 deletions src/client/actions/directory/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
import { ThunkAction } from 'redux-thunk'
import { Dispatch } from 'react'
import querystring from 'querystring'
import { History } from 'history'
import * as Sentry from '@sentry/browser'
import {
DirectoryActionType,
ResetDirectoryResultsAction,
SET_DIRECTORY_RESULTS,
SET_INITIAL_STATE,
SetDirectoryResultsAction,
} from './types'
import { GoGovReduxState } from '../../reducers/types'
import { RootActionType, SetErrorMessageAction } from '../root/types'
import rootActions from '../root'
import { SearchResultsSortOrder } from '../../../shared/search'
import { get } from '../../util/requests'
import { DIRECTORY_PAGE } from '../../util/types'
import { UrlTypePublic } from '../../reducers/directory/types'
import { GAEvent } from '../ga'

function setDirectoryResults(payload: {
count: number
urls: Array<UrlTypePublic>
query: string
}): SetDirectoryResultsAction {
return {
type: SET_DIRECTORY_RESULTS,
payload,
}
}

function resetDirectoryResults(): ResetDirectoryResultsAction {
return {
type: SET_INITIAL_STATE,
}
}

const getDirectoryResults = (
query: string,
order: SearchResultsSortOrder,
rowsPerPage: number,
currentPage: number,
state: string,
isFile: string,
isEmail: string,
): ThunkAction<
void,
GoGovReduxState,
void,
DirectoryActionType | RootActionType
> => async (
dispatch: Dispatch<SetErrorMessageAction | SetDirectoryResultsAction>,
) => {
if (!query.trim()) {
return
}
const offset = currentPage * rowsPerPage
const limit = rowsPerPage
const paramsObj = {
query,
order,
limit,
offset,
state,
isFile,
isEmail,
}
const params = querystring.stringify(paramsObj)
const response = await get(`/api/directory/search?${params}`)
const json = await response.json()
if (!response.ok) {
// Report error from endpoints
GAEvent('directory page', query, 'unsuccessful')
Sentry.captureMessage('directory search unsuccessful')
dispatch(
rootActions.setErrorMessage(
json.message || 'Error fetching search results',
),
)
return
}

let filteredQuery = ''

if (isEmail === 'true') {
// split by space, then split each subset by @ and take the last part (domain), then rejoin back the string
filteredQuery = query
.split(' ')
.map((subset) => subset.split('@').slice(-1))
.join(' ')
} else {
// Remove all words that have @ inside to prevent potential email address problem
filteredQuery = query.replace('@', ' ')
}
GAEvent('directory page', filteredQuery, 'successful')
dispatch(
setDirectoryResults({
count: json.count,
urls: json.urls as Array<UrlTypePublic>,
query,
}),
)
}

const redirectToDirectoryPage = (
history: History,
query: string,
): ThunkAction<
void,
GoGovReduxState,
void,
DirectoryActionType | RootActionType
> => () => {
history.push({
pathname: DIRECTORY_PAGE,
search: querystring.stringify({ query }),
})
}

export default {
getDirectoryResults,
setDirectoryResults,
redirectToDirectoryPage,
resetDirectoryResults,
}
22 changes: 22 additions & 0 deletions src/client/actions/directory/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { UrlTypePublic } from '../../reducers/directory/types'

export const SET_DIRECTORY_RESULTS = 'SET_DIRECTORY_RESULTS'
export const SET_DIRECTORY_TABLE_CONFIG = 'SET_DIRECTORY_TABLE_CONFIG'
export const SET_INITIAL_STATE = 'SET_INITIAL_STATE'

export type SetDirectoryResultsAction = {
type: typeof SET_DIRECTORY_RESULTS
payload: {
urls: Array<UrlTypePublic>
count: number
query: string
}
}

export type ResetDirectoryResultsAction = {
type: typeof SET_INITIAL_STATE
}

export type DirectoryActionType =
| SetDirectoryResultsAction
| ResetDirectoryResultsAction
2 changes: 2 additions & 0 deletions src/client/actions/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { RootActionType } from './root/types'
import { UserActionType } from './user/types'
import { LoginActionType } from './login/types'
import { SearchActionType } from './search/types'
import { DirectoryActionType } from './directory/types'

export type GetReduxState = () => GoGovReduxState

Expand All @@ -14,5 +15,6 @@ export type AllActions =
| LoginActionType
| HomeActionType
| SearchActionType
| DirectoryActionType

export type AllThunkDispatch = ThunkDispatch<GoGovReduxState, void, AllActions>
4 changes: 4 additions & 0 deletions src/client/assets/go-main-logo-mini-light.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 4 additions & 0 deletions src/client/assets/go-main-logo-mini.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
52 changes: 46 additions & 6 deletions src/client/components/BaseLayout/BaseLayoutHeader.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,14 @@ import {
import i18next from 'i18next'
import GoLogo from '~/assets/go-main-logo.svg'
import GoLogoLight from '~/assets/go-main-logo-light.svg'
import GoLogoMini from '~/assets/go-main-logo-mini.svg'
import GoLogoMiniLight from '~/assets/go-main-logo-mini-light.svg'
import loginActions from '../../actions/login'
import Section from '../Section'
import logoutIcon from './assets/logout-icon.svg'
import logoutWhiteIcon from './assets/logout-white-icon.svg'
import helpIcon from '../../assets/help-icon.svg'
import directoryIcon from './assets/directory-icon.svg'
import feedbackIcon from './assets/feedback-icon.svg'
import githubIcon from './assets/github-icon.svg'
import signinIcon from './assets/signin-icon.svg'
Expand Down Expand Up @@ -50,7 +54,8 @@ const useStyles = makeStyles((theme) =>
flexGrow: 0.85,
},
appBarSignOutBtn: {
fill: theme.palette.primary.main,
// fill: theme.palette.primary.main,
color: (props) => (props.isLightItems ? 'white' : '#384A51'),
order: 10,
},
appBarSignInBtn: {
Expand All @@ -72,17 +77,23 @@ const useStyles = makeStyles((theme) =>
display: 'flex',
width: '100%',
height: '100%',
[theme.breakpoints.down('sm')]: {
width: 'auto',
},
},
headerButton: {
filter: (props) => (props.isLightItems ? 'brightness(10)' : ''),
// this class is not mobile first by default as padding should not be set
// when it is not mobile.
[theme.breakpoints.down('xs')]: {
[theme.breakpoints.down('xm')]: {
paddingLeft: 0,
paddingRight: 0,
minWidth: theme.spacing(6),
},
},
logoutIcon: {
width: '24px',
},
sectionPageSticky: {
paddingTop: '43px',
},
Expand Down Expand Up @@ -113,6 +124,14 @@ const BaseLayoutHeader = ({
const classes = useStyles({ isLoggedIn, isLightItems })

const headers = [
{
text: 'Directory',
link: i18next.t('general.links.directory'),
public: false,
icon: directoryIcon,
mobileOrder: 1,
internalLink: true,
},
{
text: 'Contribute',
link: i18next.t('general.links.contribute'),
Expand All @@ -131,22 +150,30 @@ const BaseLayoutHeader = ({
link: i18next.t('general.links.contact'),
public: false,
icon: feedbackIcon,
mobileOrder: 1,
mobileOrder: 3,
},
]

const appBarBtn = isLoggedIn ? (
<Button
onClick={logout}
size="large"
color="primary"
color={isLightItems ? 'primary' : 'white'}
variant="text"
className={classes.appBarSignOutBtn}
>
<Hidden xsDown>
<strong>Sign out&nbsp;</strong>
</Hidden>
<img src={logoutIcon} alt="Sign out" />
{isLightItems ? (
<img
className={classes.logoutIcon}
src={logoutWhiteIcon}
alt="Sign out"
/>
) : (
<img className={classes.logoutIcon} src={logoutIcon} alt="Sign out" />
)}
</Button>
) : (
<>
Expand Down Expand Up @@ -175,6 +202,19 @@ const BaseLayoutHeader = ({
</>
)

const getGoLogo = () => {
if (isLightItems && isMobileVariant) {
return GoLogoMiniLight
}
if (isLightItems) {
return GoLogoLight
}
if (!isLightItems && isMobileVariant) {
return GoLogoMini
}
return GoLogo
}

return (
<Section
backgroundType={backgroundType}
Expand All @@ -186,7 +226,7 @@ const BaseLayoutHeader = ({
<Toolbar className={classes.toolbar}>
<a href="/#/" className={classes.toolbarLogo}>
<img
src={isLightItems ? GoLogoLight : GoLogo}
src={getGoLogo()}
className={classes.logo}
alt="GoGovSG Logo"
/>
Expand Down
3 changes: 3 additions & 0 deletions src/client/components/BaseLayout/assets/directory-icon.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 4 additions & 0 deletions src/client/components/BaseLayout/assets/logout-white-icon.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading

0 comments on commit ff6d42c

Please sign in to comment.