Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions frontend/api/index.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 3 additions & 2 deletions frontend/common/stores/base/_store.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ module.exports = Object.assign({}, EventEmitter.prototype, {
// console.log('change', this.id)
this.trigger(DEFAULT_CHANGE_EVENT)
},
// Config store uses {type: FlagsmithStartupErrors, message: string}
error: null,
goneABitWest() {
this.hasLoaded = true
Expand All @@ -32,9 +33,9 @@ module.exports = Object.assign({}, EventEmitter.prototype, {

isSaving: false,

loaded() {
loaded(persistError = false) {
this.hasLoaded = true
this.error = null
this.error = persistError ? this.error : null
this.isLoading = false
this.trigger(DEFAULT_LOADED_EVENT)
this.trigger(DEFAULT_CHANGE_EVENT)
Expand Down
26 changes: 22 additions & 4 deletions frontend/common/stores/config-store.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,25 @@ const controller = {
}
store.model = flagsmith.getAllFlags()
},
onError() {
store.error = true
onError(e) {
if (
Project.isFlagsmithOnFlagsmith ||
(!Project.flagsmith && !Project.flagsmithClientAPI)
) {
store.model = {}
// TODO: Migrate to TS and use enum
store.error = {
message: e?.message,
type: 'fof_init_error',
}
store.loaded(true)
return
}

store.error = {
message: e,
type: 'unknown',
}
store.goneABitWest()
},
}
Expand Down Expand Up @@ -54,8 +71,9 @@ flagsmith
onChange: controller.loaded,
realtime: Project.flagsmithRealtime,
})
.catch(() => {
controller.onError()
.catch((e) => {
console.error(e)
controller.onError(e)
})

controller.store = store
Expand Down
99 changes: 50 additions & 49 deletions frontend/common/utils/utils.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -207,7 +207,8 @@ const Utils = Object.assign({}, require('./base/_utils'), {
* only add behaviour to Flagsmith-on-Flagsmith flags that have been explicitly created by customers.
*/
flagsmithFeatureExists(flag: string) {
return Object.prototype.hasOwnProperty.call(flagsmith.getAllFlags(), flag)
const allFlags = flagsmith?.getAllFlags()
return allFlags && Object.prototype.hasOwnProperty.call(allFlags, flag)
},
getContentType(contentTypes: ContentType[], model: string, type: string) {
return contentTypes.find((c: ContentType) => c[model] === type) || null
Expand Down Expand Up @@ -403,6 +404,53 @@ const Utils = Object.assign({}, require('./base/_utils'), {
}
return true
},
getExistingWaitForTime: (
waitFor: string | undefined,
): { amountOfTime: number; timeUnit: (typeof TimeUnit)[keyof typeof TimeUnit] } | undefined => {
if (!waitFor) {
return
}

const timeParts = waitFor.split(':')

if (timeParts.length != 3) return

const [hours, minutes, seconds] = timeParts

const amountOfMinutes = Number(minutes)
const amountOfHours = Number(hours)
const amountOfSeconds = Number(seconds)

if (amountOfHours + amountOfMinutes + amountOfSeconds === 0) {
return
}

// Days
if (
amountOfHours % 24 === 0 &&
amountOfMinutes === 0 &&
amountOfSeconds === 0
) {
return {
amountOfTime: amountOfHours / 24,
timeUnit: TimeUnit.DAY,
}
}

// Hours
if (amountOfHours > 0 && amountOfMinutes === 0 && amountOfSeconds === 0) {
return {
amountOfTime: amountOfHours,
timeUnit: TimeUnit.HOUR,
}
}

// Minutes
return {
amountOfTime: amountOfMinutes,
timeUnit: TimeUnit.MINUTE,
}
},
getPlansPermission: (feature: PaidFeature) => {
const isOrgPermission = feature !== '2FA'
const plans = isOrgPermission
Expand All @@ -423,6 +471,7 @@ const Utils = Object.assign({}, require('./base/_utils'), {
getProjectColour(index: number) {
return Constants.projectColors[index % (Constants.projectColors.length - 1)]
},

getRequiredPlan: (feature: PaidFeature) => {
let plan
switch (feature) {
Expand Down Expand Up @@ -538,58 +587,10 @@ const Utils = Object.assign({}, require('./base/_utils'), {

return str
},

getViewIdentitiesPermission() {
return 'VIEW_IDENTITIES'
},
getExistingWaitForTime: (
waitFor: string | undefined,
): { amountOfTime: number; timeUnit: (typeof TimeUnit)[keyof typeof TimeUnit] } | undefined => {
if (!waitFor) {
return
}

const timeParts = waitFor.split(':')

if (timeParts.length != 3) return

const [hours, minutes, seconds] = timeParts

const amountOfMinutes = Number(minutes)
const amountOfHours = Number(hours)
const amountOfSeconds = Number(seconds)

if (amountOfHours + amountOfMinutes + amountOfSeconds === 0) {
return
}

// Days
if (
amountOfHours % 24 === 0 &&
amountOfMinutes === 0 &&
amountOfSeconds === 0
) {
return {
amountOfTime: amountOfHours / 24,
timeUnit: TimeUnit.DAY,
}
}

// Hours
if (amountOfHours > 0 && amountOfMinutes === 0 && amountOfSeconds === 0) {
return {
amountOfTime: amountOfHours,
timeUnit: TimeUnit.HOUR,
}
}

// Minutes
return {
amountOfTime: amountOfMinutes,
timeUnit: TimeUnit.MINUTE,
}
},

hasEntityPermission(key: string, entityPermissions: UserPermissions) {
if (entityPermissions?.admin) return true
return !!entityPermissions?.permissions?.find(
Expand Down
3 changes: 2 additions & 1 deletion frontend/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

35 changes: 33 additions & 2 deletions frontend/web/components/App.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { Component, Fragment } from 'react'
import React, { Component } from 'react'
import { matchPath, withRouter } from 'react-router-dom'
import * as amplitude from '@amplitude/analytics-browser'
import { plugin as engagementPlugin } from '@amplitude/engagement-browser'
Expand All @@ -16,6 +16,10 @@ import { resolveAuthFlow } from '@datadog/ui-extensions-sdk'
import ConfigProvider from 'common/providers/ConfigProvider'
import AccountStore from 'common/stores/account-store'
import OrganisationLimit from './OrganisationLimit'
import {
getStartupErrorText,
isFlagsmithOnFlagsmithError,
} from './base/errors/init.error'
import OrganisationStore from 'common/stores/organisation-store'
import ScrollToTop from './ScrollToTop'
import AnnouncementPerPage from './AnnouncementPerPage'
Expand Down Expand Up @@ -240,9 +244,32 @@ const App = class extends Component {
) {
return <Blocked />
}
if (Project.maintenance || this.props.error || !window.projectOverrides) {

const maintenanceMode =
Utils.getFlagsmithHasFeature('maintenance_mode') || Project.maintenance
const isUnknownError =
this.props.error && !isFlagsmithOnFlagsmithError(this.props.error)
if (maintenanceMode || !window.projectOverrides || isUnknownError) {
return <Maintenance />
}

if (this.props.error && isFlagsmithOnFlagsmithError(this.props.error)) {
toast(
getStartupErrorText(this.props.error),
'danger',
2 * 60 * 1000,
{
buttonText: 'See documentation',
onClick: () =>
window.open(
'https://docs.flagsmith.com/deployment/#running-flagsmith-on-flagsmith',
'_blank',
),
},
{ size: 'large' },
)
}

const activeProject = OrganisationStore.getProject(projectId)
const projectNotLoaded =
!activeProject && document.location.href.includes('project/')
Expand All @@ -262,12 +289,15 @@ const App = class extends Component {
</AccountProvider>
)
}

if (AccountStore.forced2Factor()) {
return <AccountSettingsPage isLoginPage={true} />
}

if (document.location.pathname.includes('widget')) {
return <div>{this.props.children}</div>
}

return (
<Provider store={getStore()}>
<AccountProvider
Expand Down Expand Up @@ -303,6 +333,7 @@ const App = class extends Component {
AccountStore.getOrganisation()?.subscription
.billing_status
}
fofError={this.props.error?.message}
/>
{user && (
<>
Expand Down
36 changes: 35 additions & 1 deletion frontend/web/components/ButterBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,14 @@ import Constants from 'common/constants'
interface ButterBarProps {
billingStatus?: string
projectId: string
fofError?: string
}

const ButterBar: React.FC<ButterBarProps> = ({ billingStatus, projectId }) => {
const ButterBar: React.FC<ButterBarProps> = ({
billingStatus,
fofError,
projectId,
}) => {
const matches = document.location.href.match(/\/environment\/([^/]*)/)
const environment = matches && matches[1]
const timerRef = useRef<NodeJS.Timer>()
Expand Down Expand Up @@ -59,6 +64,35 @@ const ButterBar: React.FC<ButterBarProps> = ({ billingStatus, projectId }) => {
return processing
}, [checkProcessing, featureImports])

if (fofError) {
return (
<div
className='announcement-container font-weight-medium bg-danger'
style={{ padding: '10px' }}
>
<Row>
<h6>Could not initialise Flagsmith-on-Flagsmith</h6>
</Row>
<p className='mb-1'>The error was: "{fofError}"</p>
<p className='mb-1'>
Flag evaluation for is not affected, but some dashboard features might
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, looks like there's a typo:

Suggested change
Flag evaluation for is not affected, but some dashboard features might
Flag evaluation is not affected, but some dashboard features might

be unavailable.
</p>
<p className='mb-1'>
Check with{' '}
<a
href='https://docs.flagsmith.com/deployment/#running-flagsmith-on-flagsmith'
target='_blank'
rel='noopener noreferrer'
>
Flagsmith-on-Flagsmith documentation
</a>{' '}
for more info.
</p>
</div>
)
}

if (processingImport) {
return (
<div className='butter-bar font-weight-medium'>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,34 +1,21 @@
import React from 'react'
import ConfigProvider from 'common/providers/ConfigProvider'

const HomePage = class extends React.Component {
static displayName = 'HomePage'

constructor(props, context) {
super(props, context)
this.state = {}
}

render = () => (
const MaintenancePage: React.FC = () => {
return (
<div className='fullscreen-container maintenance justify-content-center'>
<div className='col-md-6 mt-5' id='sign-up'>
<h1>Maintenance</h1>
We are currently undergoing some scheduled maintenance of the admin
site, this will not affect your application's feature flags.
{
<>
{' '}
Check{' '}
<a
target='_blank'
href='https://x.com/getflagsmith'
rel='noreferrer'
>
@getflagsmith
</a>{' '}
for updates.
</>
}
<>
{' '}
Check{' '}
<a target='_blank' href='https://x.com/getflagsmith' rel='noreferrer'>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not fundamental part of this PR but we should consider replacing this with https://status.flagsmith.com/ because we're not posting status updates on twitter

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Definitely agree with this, but let's include this in a separate PR.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
<a target='_blank' href='https://x.com/getflagsmith' rel='noreferrer'>
<a target='_blank' href='https://status.flagsmith.com/' rel='noreferrer'>

@getflagsmith
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
@getflagsmith
Flagsmith status

</a>{' '}
for updates.
</>
<br />
<p className='small'>
Sorry for the inconvenience, we will be back up and running shortly.
Expand All @@ -38,4 +25,6 @@ const HomePage = class extends React.Component {
)
}

module.exports = ConfigProvider(HomePage)
MaintenancePage.displayName = 'MaintenancePage'

export default ConfigProvider(MaintenancePage)
Loading
Loading