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: Adds support for quickly navigating to recently created or existing requested changes #5109

Merged
merged 6 commits into from
Feb 18, 2025
Merged
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
43 changes: 25 additions & 18 deletions frontend/common/stores/feature-list-store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,13 @@ import {
} from 'common/services/useProjectFlag'
import OrganisationStore from './organisation-store'
import {
ChangeRequest,
Environment,
FeatureState,
PagedResponse,
ProjectFlag, TypedFeatureState,
} from 'common/types/responses';
ChangeRequest,
Environment,
FeatureState,
PagedResponse,
ProjectFlag,
TypedFeatureState,
} from 'common/types/responses'
import Utils from 'common/utils/utils'
import Actions from 'common/dispatcher/action-constants'
import Project from 'common/project'
Expand Down Expand Up @@ -472,16 +473,17 @@ const controller = {
API.trackEvent(Constants.events.EDIT_FEATURE)
const env: Environment = ProjectStore.getEnvironment(environmentId) as any
// Detect differences between change request and existing feature states
const res: { data: PagedResponse<TypedFeatureState> } = await getFeatureStates(
getStore(),
{
environment: environmentFlag.environment,
feature: projectFlag.id,
},
{
forceRefetch: true,
},
)
const res: { data: PagedResponse<TypedFeatureState> } =
await getFeatureStates(
getStore(),
{
environment: environmentFlag.environment,
feature: projectFlag.id,
},
{
forceRefetch: true,
},
)
const segmentResult = await getSegments(getStore(), {
include_feature_specific: true,
page_size: 1000,
Expand Down Expand Up @@ -668,12 +670,17 @@ const controller = {
)
}
})
return updatedChangeRequest
},
)
})

Promise.all([prom]).then(() => {
store.saved({ changeRequest: true, isCreate: true })
Promise.all([prom]).then(([updatedChangeRequest]) => {
store.saved({
changeRequest: true,
isCreate: true,
updatedChangeRequest,
})
})
} catch (e) {
API.ajaxHandler(store, e)
Expand Down
2 changes: 1 addition & 1 deletion frontend/global.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ declare global {
) => void
const openConfirm: (data: OpenConfirm) => void
const Row: typeof Component
const toast: (value: ReactNode, theme?: string, expiry?: number) => void
const toast: (value: ReactNode, theme?: string, expiry?: number, action?: { buttonText: string; onClick: () => void }) => void
const Flex: typeof Component
const isMobile: boolean
const FormGroup: typeof Component
Expand Down
106 changes: 68 additions & 38 deletions frontend/web/components/ExistingChangeRequestAlert.tsx
Original file line number Diff line number Diff line change
@@ -1,54 +1,84 @@
import { FC, useRef } from 'react'
import { useGetChangeRequestsQuery } from 'common/services/useChangeRequest'
import WarningMessage from './WarningMessage'
import moment from 'moment'
import { FC } from 'react'
import InfoMessage from './InfoMessage'
import { RouterChildContext } from 'react-router-dom'
import Button from './base/forms/Button'
import { ChangeRequestSummary } from 'common/types/responses'

type ExistingChangeRequestAlertType = {
changeRequests: ChangeRequestSummary[]
editingChangeRequest?: ChangeRequestSummary
scheduledChangeRequests: ChangeRequestSummary[]
projectId: number | string
environmentId: string
featureId: number
className?: string
history: RouterChildContext['router']['history']
}

const ExistingChangeRequestAlert: FC<ExistingChangeRequestAlertType> = ({
changeRequests,
className,
editingChangeRequest,
environmentId,
featureId,
history,
projectId,
scheduledChangeRequests,
}) => {
const { data } = useGetChangeRequestsQuery({
committed: false,
environmentId,
feature_id: featureId,
})
const date = useRef(moment().toISOString())
const { data: scheduledChangeRequests } = useGetChangeRequestsQuery({
environmentId,
feature_id: featureId,
live_from_after: date.current,
})

if (scheduledChangeRequests?.results?.length) {
return (
<div className={className}>
<WarningMessage
warningMessage={
'You have scheduled changes upcoming for this feature, please check the Scheduling page.'
}
/>
</div>
const handleNavigate = () => {
const changes = scheduledChangeRequests?.length
? scheduledChangeRequests
: changeRequests
const latestChangeRequest = !editingChangeRequest?.id
? changes?.at(-1)?.id
: editingChangeRequest?.id
closeModal()
history.push(
`/project/${projectId}/environment/${environmentId}/change-requests/${latestChangeRequest}`,
)
}
if (data?.results?.length) {
return (
<div className={className}>
<WarningMessage
warningMessage={
'You have open change requests for this feature, please check the Change Requests page.'
}
/>
</div>
)

const getRequestChangeInfoText = (
hasScheduledChangeRequests: boolean,
hasChangeRequests: boolean,
) => {
if (hasScheduledChangeRequests) {
return [
'You have scheduled changes upcoming for this feature.',
'to view your scheduled changes.',
]
}

if (hasChangeRequests) {
return [
'You have open change requests for this feature.',
'to view your requested changes.',
]
}
}

const requestChangeInfoText = getRequestChangeInfoText(
!!scheduledChangeRequests?.length,
!!changeRequests?.length,
)

console.log({ requestChangeInfoText })

if (!requestChangeInfoText?.length) {
return null
}
return null

return (
<div className={className}>
<InfoMessage>
<span>
{requestChangeInfoText[0]} Click{' '}
<Button onClick={handleNavigate} theme='text'>
here
</Button>{' '}
{requestChangeInfoText[1]}
</span>
</InfoMessage>
</div>
)
}

export default ExistingChangeRequestAlert
91 changes: 83 additions & 8 deletions frontend/web/components/modals/CreateFlag.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import React, { Component } from 'react'
import withSegmentOverrides from 'common/providers/withSegmentOverrides'
import moment from 'moment'
import Constants from 'common/constants'
import data from 'common/data/base/_data'
import ProjectStore from 'common/stores/project-store'
Expand Down Expand Up @@ -47,6 +48,7 @@ import PlanBasedBanner from 'components/PlanBasedAccess'
import FeatureHistory from 'components/FeatureHistory'
import WarningMessage from 'components/WarningMessage'
import { getPermission } from 'common/services/usePermission'
import { getChangeRequests } from 'common/services/useChangeRequest'

const CreateFlag = class extends Component {
static displayName = 'CreateFlag'
Expand Down Expand Up @@ -78,6 +80,7 @@ const CreateFlag = class extends Component {
}
this.state = {
allowEditDescription,
changeRequests: [],
default_enabled: enabled,
description,
enabledIndentity: false,
Expand Down Expand Up @@ -106,6 +109,7 @@ const CreateFlag = class extends Component {
multivariate_options: _.cloneDeep(multivariate_options),
name,
period: 30,
scheduledChangeRequests: [],
selectedIdentity: null,
tags: tags?.filter((tag) => !hideTags.includes(tag)) || [],
}
Expand Down Expand Up @@ -201,6 +205,9 @@ const CreateFlag = class extends Component {
})
}

this.fetchChangeRequests()
this.fetchScheduledChangeRequests()

getGithubIntegration(getStore(), {
organisation_id: AccountStore.getOrganisation().id,
}).then((res) => {
Expand Down Expand Up @@ -543,6 +550,43 @@ const CreateFlag = class extends Component {
this.forceUpdate()
}

fetchChangeRequests = (forceRefetch) => {
const { environmentId, projectFlag } = this.props
if (!projectFlag?.id) return

getChangeRequests(
getStore(),
{
committed: false,
environmentId,
feature_id: projectFlag?.id,
},
{ forceRefetch },
).then((res) => {
this.setState({ changeRequests: res.data?.results })
})
}

fetchScheduledChangeRequests = (forceRefetch) => {
const { environmentId, projectFlag } = this.props
if (!projectFlag?.id) return

const date = moment().toISOString()

console.log('data', date)
getChangeRequests(
getStore(),
{
environmentId,
feature_id: projectFlag.id,
live_from_after: date,
},
{ forceRefetch },
).then((res) => {
this.setState({ scheduledChangeRequests: res.data?.results })
})
}

render() {
const {
default_enabled,
Expand Down Expand Up @@ -579,6 +623,9 @@ const CreateFlag = class extends Component {
let regexValid = true
const metadataEnable = Utils.getPlansPermission('METADATA')

const { changeRequests, scheduledChangeRequests } = this.state
console.log({ changeRequests, out: true, scheduledChangeRequests })

try {
if (!isEdit && name && regex) {
regexValid = name.match(new RegExp(regex))
Expand Down Expand Up @@ -745,13 +792,18 @@ const CreateFlag = class extends Component {

const Value = (error, projectAdmin, createFeature, hideValue) => {
const { featureError, featureWarning } = this.parseError(error)
const { changeRequests, scheduledChangeRequests } = this.state
return (
<>
{!!isEdit && (
{!!isEdit && !identity && (
<ExistingChangeRequestAlert
className='mb-4'
featureId={projectFlag.id}
editingChangeRequest={this.props.changeRequest}
projectId={this.props.projectId}
environmentId={this.props.environmentId}
history={this.props.history}
changeRequests={changeRequests}
scheduledChangeRequests={scheduledChangeRequests}
/>
)}
{!isEdit && (
Expand Down Expand Up @@ -886,6 +938,11 @@ const CreateFlag = class extends Component {
this.props.projectId,
this.props.environmentId,
)

if (is4Eyes && !identity) {
this.fetchChangeRequests(true)
this.fetchScheduledChangeRequests(true)
}
}}
>
{(
Expand Down Expand Up @@ -2070,7 +2127,13 @@ const FeatureProvider = (WrappedComponent) => {
this.listenTo(
FeatureListStore,
'saved',
({ changeRequest, createdFlag, error, isCreate } = {}) => {
({
changeRequest,
createdFlag,
error,
isCreate,
updatedChangeRequest,
} = {}) => {
if (error?.data?.metadata) {
error.data.metadata?.forEach((m) => {
if (Object.keys(m).length > 0) {
Expand All @@ -2081,11 +2144,23 @@ const FeatureProvider = (WrappedComponent) => {
toast('Error updating the Flag', 'danger')
return
} else {
toast(
`${createdFlag || isCreate ? 'Created' : 'Updated'} ${
changeRequest ? 'Change Request' : 'Feature'
}`,
)
const operation = createdFlag || isCreate ? 'Created' : 'Updated'
const type = changeRequest ? 'Change Request' : 'Feature'

const toastText = `${operation} ${type}`
const toastAction = changeRequest
? {
buttonText: 'Open',
onClick: () => {
closeModal()
this.props.history.push(
`/project/${this.props.projectId}/environment/${this.props.environmentId}/change-requests/${updatedChangeRequest?.id}`,
)
},
}
: undefined

toast(toastText, 'success', undefined, toastAction)
}
const envFlags = FeatureListStore.getEnvironmentFlags()

Expand Down
Loading
Loading