Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: redesign organisation layout #3257

Merged
merged 11 commits into from
Jan 24, 2024
1 change: 0 additions & 1 deletion frontend/.eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,6 @@ module.exports = {
'OptionalObject': true,
'OptionalString': true,
'OrganisationProvider': true,
'OrganisationSelect': true,
'Paging': true,
'Panel': true,
'PanelSearch': true,
Expand Down
2 changes: 2 additions & 0 deletions frontend/common/providers/AccountProvider.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ const AccountProvider = class extends Component {
organisations: AccountStore.getOrganisations(),
user: AccountStore.getUser(),
})
this.props.onChange && this.props.onChange()
})

this.listenTo(AccountStore, 'loaded', () => {
Expand Down Expand Up @@ -115,6 +116,7 @@ const AccountProvider = class extends Component {

AccountProvider.propTypes = {
children: OptionalFunc,
onChange: OptionalFunc,
onLogin: OptionalFunc,
onLogout: OptionalFunc,
onNoUser: OptionalFunc,
Expand Down
3 changes: 3 additions & 0 deletions frontend/common/stores/account-store.js
Original file line number Diff line number Diff line change
Expand Up @@ -467,6 +467,9 @@ const store = Object.assign({}, BaseStore, {
const id = store.organisation && store.organisation.id
return id && store.getOrganisationRole(id) === 'ADMIN'
},
isSuper() {
return store.model && store.model.is_superuser
},
setToken(token) {
data.token = token
},
Expand Down
2 changes: 1 addition & 1 deletion frontend/e2e/helpers.cafe.ts
Original file line number Diff line number Diff line change
Expand Up @@ -245,7 +245,7 @@ export const login = async (email: string, password: string) => {
await setText('[name="email"]', `${email}`)
await setText('[name="password"]', `${password}`)
await click('#login-btn')
await waitForElementVisible('#project-select-page')
await waitForElementVisible('#project-manage-widget')
}
export const logout = async (t) => {
await click('#account-settings-link')
Expand Down
2 changes: 1 addition & 1 deletion frontend/e2e/tests/initialise-tests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ export default async function () {
await click(byId('signup-btn'))
await setText('[name="orgName"]', 'Bullet Train Ltd 0')
await click('#create-org-btn')
await waitForElementVisible(byId('project-select-page'))
await waitForElementVisible(byId('project-manage-widget'))

log('Create Project')
await click(byId('create-first-project-btn'))
Expand Down
7 changes: 3 additions & 4 deletions frontend/e2e/tests/segment-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ export const testSegment1 = async () => {
await click(byId('create-organisation-btn'))
await setText('[name="orgName"]', 'Bullet Train Ltd 2')
await click('#create-org-btn')
await waitForElementVisible(byId('project-select-page'))
await waitForElementVisible(byId('project-manage-widget'))

log('Create Project')

Expand Down Expand Up @@ -151,7 +151,7 @@ export const testSegment2 = async () => {
await click(byId('create-organisation-btn'))
await setText('[name="orgName"]', 'Bullet Train Ltd 3')
await click('#create-org-btn')
await waitForElementVisible(byId('project-select-page'))
await waitForElementVisible(byId('project-manage-widget'))

log('Create Project')

Expand All @@ -160,7 +160,6 @@ export const testSegment2 = async () => {
await click(byId('create-project-btn'))
await waitForElementVisible(byId('features-page'))


log('Create segments')
await gotoSegments()
await createSegment(0, 'segment_1', [
Expand Down Expand Up @@ -249,7 +248,7 @@ export const testSegment3 = async () => {
await click(byId('create-organisation-btn'))
await setText('[name="orgName"]', 'Bullet Train Ltd 4')
await click('#create-org-btn')
await waitForElementVisible(byId('project-select-page'))
await waitForElementVisible(byId('project-manage-widget'))

log('Create Project')

Expand Down
27 changes: 15 additions & 12 deletions frontend/web/components/AdminAPIKeys.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,11 @@ export class CreateAPIKey extends PureComponent {
this.setState({ isSaving: true })
data
.post(
`${Project.api}organisations/${
AccountStore.getOrganisation().id
}/master-api-keys/`,
`${Project.api}organisations/${this.props.organisationId}/master-api-keys/`,
{
expiry_date: this.state.expiry_date,
name: this.state.name,
organisation: AccountStore.getOrganisation().id,
organisation: this.props.organisationId,
},
)
.then((res) => {
Expand Down Expand Up @@ -126,7 +124,8 @@ export default class AdminAPIKeys extends PureComponent {
static displayName = 'TheComponent'

state = {
isLoading: true,
isLoading: false,
organisationId: null,
}

static propTypes = {}
Expand All @@ -135,12 +134,19 @@ export default class AdminAPIKeys extends PureComponent {
this.fetch()
}

componentDidUpdate() {
if (this.props.organisationId === this.state.organisationId) return

this.fetch()
this.setState({ organisationId: this.props.organisationId })
}

createAPIKey = () => {
openModal(
'New Admin API Key',
<CreateAPIKey
organisationId={this.props.organisationId}
onSuccess={() => {
this.setState({ isLoading: true })
this.fetch()
}}
/>,
Expand All @@ -149,11 +155,10 @@ export default class AdminAPIKeys extends PureComponent {
}

fetch = () => {
this.setState({ isLoading: true })
data
.get(
`${Project.api}organisations/${
AccountStore.getOrganisation().id
}/master-api-keys/`,
`${Project.api}organisations/${this.props.organisationId}/master-api-keys/`,
)
.then((res) => {
this.setState({
Expand All @@ -173,9 +178,7 @@ export default class AdminAPIKeys extends PureComponent {
() => {
data
.delete(
`${Project.api}organisations/${
AccountStore.getOrganisation().id
}/master-api-keys/${v.prefix}/`,
`${Project.api}organisations/${this.props.organisationId}/master-api-keys/${v.prefix}/`,
)
.then(() => {
this.fetch()
Expand Down
73 changes: 5 additions & 68 deletions frontend/web/components/App.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,13 @@ import { Provider } from 'react-redux'
import { getStore } from 'common/store'
import { resolveAuthFlow } from '@datadog/ui-extensions-sdk'
import ConfigProvider from 'common/providers/ConfigProvider'
import Permission from 'common/providers/Permission'
import { getOrganisationUsage } from 'common/services/useOrganisationUsage'
import Button from './base/forms/Button'
import Icon from './Icon'
import AccountStore from 'common/stores/account-store'
import InfoMessage from './InfoMessage'
import OrganisationLimit from './OrganisationLimit'
import OrganisationLink from './OrganisationLink'

const App = class extends Component {
static propTypes = {
Expand Down Expand Up @@ -347,29 +347,14 @@ const App = class extends Component {
<React.Fragment>
<nav className='my-3 my-md-0 hidden-xs-down flex-row navbar-right space'>
<Row>
{!!AccountStore.getOrganisation() && (
<NavLink
id='projects-link'
data-test='projects-link'
activeClassName='active'
className='nav-link'
to={'/projects'}
>
<span className='mr-1'>
<Icon
name='layout'
width={20}
fill='#9DA4AE'
/>
</span>
Projects
</NavLink>
)}
<OrganisationLink />
</Row>
<Row>
<NavLink
id='account-settings-link'
data-test='account-settings-link'
activeClassName='active'
className='nav-link'
className='nav-link mr-4'
to={
projectId
? `/project/${projectId}/environment/${environmentId}/account`
Expand All @@ -385,54 +370,6 @@ const App = class extends Component {
</span>
Account
</NavLink>
{AccountStore.getOrganisationRole() ===
'ADMIN' ? (
<NavLink
id='org-settings-link'
activeClassName='active'
className='nav-link'
to='/organisation-settings'
>
<span className='mr-1'>
<Icon
name='setting'
width={20}
fill='#9DA4AE'
/>
</span>
{'Manage'}
</NavLink>
) : (
!!AccountStore.getOrganisation() && (
<Permission
level='organisation'
permission='MANAGE_USER_GROUPS'
id={AccountStore.getOrganisation().id}
>
{({ permission }) => (
<>
{!!permission && (
<NavLink
id='org-settings-link'
activeClassName='active'
className='nav-link'
to='/organisation-groups'
>
<span
style={{ marginRight: 4 }}
>
<Icon name='setting' />
</span>
{'Manage'}
</NavLink>
)}
</>
)}
</Permission>
)
)}
</Row>
<Row>
<Button
href='https://docs.flagsmith.com'
target='_blank'
Expand Down
5 changes: 1 addition & 4 deletions frontend/web/components/Aside.js
Original file line number Diff line number Diff line change
Expand Up @@ -154,10 +154,6 @@ const Aside = class extends Component {
disabled ? 'disabled' : ''
}`}
>
<a href={'/projects'} className='nav-logo'>
<Icon name='nav-logo' />
</a>
<hr className='my-0 py-0' />
<Collapsible
data-test={
project?.name
Expand Down Expand Up @@ -258,6 +254,7 @@ const Aside = class extends Component {
}}
/>
</Collapsible>
<hr className='my-0 py-0' />
<Permission
level='project'
permission='ADMIN'
Expand Down
44 changes: 44 additions & 0 deletions frontend/web/components/OrganisationLink.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { FC, useMemo } from 'react'
import { NavLink } from 'react-router-dom'

import AccountStore from 'common/stores/account-store'
import { useHasPermission } from 'common/providers/Permission'
import Icon from 'components/Icon'

const OrganisationLink: FC = () => {
const organisation = AccountStore.getOrganisation()

const { permission: canManageUserGroups } = useHasPermission({
id: organisation?.id,
level: 'organisation',
permission: 'MANAGE_USER_GROUPS',
})

const pageLink = useMemo(() => {
if (!organisation) return null

if (AccountStore.isAdmin() || canManageUserGroups) {
return '/organisation-settings'
}

return '/projects'
}, [organisation, canManageUserGroups])

return (
pageLink && (
<NavLink
id='org-settings-link'
activeClassName='active'
className='nav-link'
to={pageLink}
>
<span className='mr-1'>
<Icon name='layout' width={20} fill='#9DA4AE' />
</span>
{'Organisation'}
</NavLink>
)
)
}

export default OrganisationLink
62 changes: 62 additions & 0 deletions frontend/web/components/OrganisationManageWidget.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import { FC, useCallback, useEffect } from 'react'

import AccountProvider from 'common/providers/AccountProvider'
import AccountStore from 'common/stores/account-store'
import AppActions from 'common/dispatcher/app-actions'
import Utils from 'common/utils/utils'
import Project from 'common/project'
import Button from 'components/base/forms/Button'
import CreateOrganisationModal from 'components/modals/CreateOrganisation'
import Icon from './Icon'
import OrganisationSelect from './OrganisationSelect'

type OrganisationManageWidgetType = {
onChange?: () => void
}

const OrganisationManageWidget: FC<OrganisationManageWidgetType> = ({
onChange,
}) => {
const handleCreateOrganisationClick = useCallback(() => {
openModal('Create Organisation', <CreateOrganisationModal />, 'side-modal')
}, [])

useEffect(() => {
AppActions.getOrganisation(AccountStore.getOrganisation().id)
}, [])

return (
<Row>
<AccountProvider onChange={() => onChange && onChange()}>
{({ organisation }: { organisation: unknown }) =>
organisation && (
<OrganisationSelect
onChange={(organisationId: string) => {
AppActions.selectOrganisation(organisationId)
AppActions.getOrganisation(organisationId)
}}
/>
)
}
</AccountProvider>
{!Utils.getFlagsmithHasFeature('disable_create_org') &&
(!Project.superUserCreateOnly ||
(Project.superUserCreateOnly && AccountStore.isSuper())) && (
<div>
<Flex className='text-center ml-3'>
<Button
data-test='create-organisation-btn'
onClick={handleCreateOrganisationClick}
size='large'
className='btn btn-with-icon btn-lg px-3'
>
<Icon name='plus' width={24} fill='#656D7B' />
</Button>
</Flex>
</div>
)}
</Row>
)
}

export default OrganisationManageWidget
Loading
Loading