Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
792172c
feat(FeedSourceTableRow): Display future feed versions beneath active…
binh-dam-ibigroup Oct 2, 2026
f027cd6
improvement(PublushStatus): Don't render if no version.
binh-dam-ibigroup Oct 2, 2026
d53822a
refactor(types): Add types
binh-dam-ibigroup Oct 2, 2026
bfd84aa
refactor(FeedSourceTableRow): Remove duplicate comparison content
binh-dam-ibigroup Oct 2, 2026
ebc41ca
fix(FeedSourceTableRow): Show 'Feed not published' if comparisonValid…
binh-dam-ibigroup Oct 5, 2026
ca203b7
refactor(FeedSourceTableRow): Fix comment
binh-dam-ibigroup Oct 5, 2026
859eb52
refactor(FeedSourceTableRow): Extract common render code
binh-dam-ibigroup Oct 5, 2026
8ce5dfe
refactor(FeedSourceTable): Combine detail cell rendering
binh-dam-ibigroup Oct 5, 2026
4a568c0
refactor(PublishStatus): Replace props with version prop.
binh-dam-ibigroup Oct 5, 2026
d132e81
refactor(FeedSourceTableRow): Improve types
binh-dam-ibigroup Oct 5, 2026
ff50210
refactor(PublishStatus): Use same subtext formatting as Status.
binh-dam-ibigroup Oct 6, 2026
0756e57
refactor(FeedSourceTableRow): Move publish status rendering inside re…
binh-dam-ibigroup Oct 6, 2026
0616950
refactor(TimedStatus): Extract and reuse component
binh-dam-ibigroup Oct 6, 2026
e1a7f4c
refactor(FeedSourceTableRow): Use destructured vars
binh-dam-ibigroup Oct 6, 2026
39a6e52
refactor(FeedStatus): rename component
binh-dam-ibigroup Oct 6, 2026
3847877
fix(FeedSourceTableRow): Update active vers render status.
binh-dam-ibigroup Oct 6, 2026
f90dd8a
test(FeedSourceTable): Update snapshot
binh-dam-ibigroup Oct 6, 2026
df801c9
refactor(FeedSourceTableRow): Simplify vars
binh-dam-ibigroup Oct 6, 2026
3c85fd1
fix(FeedSourceTableRow): Display version last updated. Update tests.
binh-dam-ibigroup Oct 6, 2026
b333629
test(manager): Add flow exception
binh-dam-ibigroup Oct 6, 2026
ad702d3
refactor(FeedSourceTableRow): Destructure vars
binh-dam-ibigroup Oct 6, 2026
affcafd
refactor: Remove publishState from FeedSourceSummary
binh-dam-ibigroup Oct 6, 2026
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
5 changes: 5 additions & 0 deletions __tests__/test-utils/mock-data/manager.js
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,10 @@ export const mockFeedWithVersion = {
tripCount: 415
},
latestVersionId: 'mock-feed-version-id',
// Include version as sometimes, this object is used as a FeedSourceSummary too.
latestVersion: {
lastUpdated: 1543389038810
},
labelIds: [],
name: 'test feed with a version',
noteCount: 0,
Expand Down Expand Up @@ -398,6 +402,7 @@ mockProjectWithDeployment.name = 'mock-project-with-deployments'

export const mockDeployment = makeMockDeployment(
mockProjectWithDeployment,
// $FlowFixMe, Passing partial data under FeedSourceSummary.latestVersion
[mockFeedVersion]
)
export const mockDeploymentSummary = makeMockDeploymentSummary()
Expand Down
190 changes: 103 additions & 87 deletions lib/manager/components/FeedSourceTableRow.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,14 @@ import WatchButton from '../../common/containers/WatchButton'
import FeedLabel from '../../common/components/FeedLabel'
import LabelAssignerModal from '../components/LabelAssignerModal'
import type { Props as ContainerProps } from '../containers/FeedSourceTableRow'
import type { Feed, ValidationSummary, Project } from '../../types'
import type { Feed, FeedVersionSummary, ValidationSummary, Project } from '../../types'
import type {
FeedSourceTableComparisonColumns,
ManagerUserState
} from '../../types/reducers'

import { PublishStatus } from './PublishStatus'
import Status from './FeedStatus'

type Props = ContainerProps & {
comparisonColumn: FeedSourceTableComparisonColumns,
Expand All @@ -45,6 +46,15 @@ type Props = ContainerProps & {
user: ManagerUserState
}

type VersionData = {
className: string,
endDate?: string,
errorCount: number | string,
icon: string,
startDate?: string,
status: string
}

const statusAttributeLookup = {
'active': {
className: 'status-active',
Expand Down Expand Up @@ -94,6 +104,13 @@ export default class FeedSourceTableRow extends PureComponent<Props> {
messages = getComponentMessages('FeedSourceTableRow')
dateFormat = this.messages('dateFormat')

getLastUpdateRelative (lastUpdated?: any) {
const lastVersionUpdate = !!lastUpdated && relativeDuration(lastUpdated)
return lastVersionUpdate
? this.messages('lastUpdated').replace('%lastVersionUpdate%', lastVersionUpdate)
: ''
}

/**
* Get some UI data from a ValidationSummary
*/
Expand Down Expand Up @@ -126,16 +143,11 @@ export default class FeedSourceTableRow extends PureComponent<Props> {
}

/**
* This method generates the variables and used to live in the render method,
* but was extracted to appease the eslint complexity rule
* Generate the variables for comparing published/deployed version vs. latest version.
*/
generateFeedSourceLabelRenderVars (feedSource: Feed, comparisonValidationSummary: ?ValidationSummary) {
// data for feed source info column
let lastVersionUpdate = !!feedSource.lastUpdated &&
relativeDuration(feedSource.lastUpdated)
lastVersionUpdate = lastVersionUpdate
? this.messages('lastUpdated').replace('%lastVersionUpdate%', lastVersionUpdate)
: ''
const lastVersionUpdate = this.getLastUpdateRelative(feedSource.lastUpdated)

// data for comparison column
const comparisonData = comparisonValidationSummary &&
Expand Down Expand Up @@ -166,6 +178,37 @@ export default class FeedSourceTableRow extends PureComponent<Props> {
}
}

renderDetailCells (versionData: VersionData, publishedVersion?: FeedVersionSummary) {
const { className, endDate, errorCount, icon, startDate, status } = versionData
const { lastUpdated } = publishedVersion || {}
const lastVersionUpdate = this.getLastUpdateRelative(lastUpdated)
return (
<>
<td className='feed-version-column'>
<Status
icon={icon}
dateFormat={this.dateFormat}
statusSpanClass={className}
statusText={this.messages(`status.${status}`)}
subtext={lastVersionUpdate}
/>
</td>
<td className='feed-version-column'>
{startDate}
{startDate && ' -'}
{endDate && <br />}
{endDate}
</td>
<td className='feed-version-column'>{errorCount}</td>
{isExtensionEnabled('mtc') && (
<td className='feed-version-column'>
<PublishStatus version={publishedVersion} />
</td>
)}
</>
)
}

// eslint-disable-next-line complexity
render () {
const {
Expand All @@ -177,7 +220,6 @@ export default class FeedSourceTableRow extends PureComponent<Props> {
} = this.props

const {
lastVersionUpdate,
comparisonSubtext,
comparisonData,
comparisonSubtextDate
Expand All @@ -187,67 +229,67 @@ export default class FeedSourceTableRow extends PureComponent<Props> {
)

// data for latest validation columns
const currentVersionData = this.getVersionDisplayData(feedSource.latestValidation)
const latestVersionId = feedSource.latestValidation && feedSource.latestValidation.feedVersionId
const comparisonVersionId = comparisonValidationSummary && comparisonValidationSummary.feedVersionId
const { latestActiveFeedValidation, latestActiveVersion, latestValidation, latestVersion } = feedSource
const defaultVersion = latestActiveVersion || latestVersion
const defaultValidation = latestActiveFeedValidation || latestValidation
const { feedVersionId: latestVersionId } = latestValidation || {}
const { feedVersionId: activeVersionId } = defaultValidation || {}

if (latestVersionId && latestVersionId === comparisonVersionId) {
currentVersionData.status = comparisonColumn === 'DEPLOYED'
const { endDate, feedVersionId: comparisonVersionId, startDate } = comparisonValidationSummary || {}
const hasComparisonDates = startDate && endDate

const latestVersionData = this.getVersionDisplayData(latestValidation)
const activeVersionData = this.getVersionDisplayData(defaultValidation)

if (activeVersionId && activeVersionId === comparisonVersionId) {
activeVersionData.status = comparisonColumn === 'DEPLOYED'
? 'same-as-deployed'
: 'same-as-published'
}

const hasPendingVersion = activeVersionId !== latestVersionId
const rowsRequired = hasPendingVersion ? 2 : 1

// Render the active or expired version first.
// If there is a future version, show it underneath.
return (
<tr key={feedSource.id} className='feed-source-table-row'>
<td className='feed-source-info'>
<FeedInfo feedSource={feedSource} project={project} user={user} />
</td>
{comparisonColumn &&
<td className='comparison-column'>
{comparisonData
? (
<Status
icon={comparisonData.icon}
dateFormat={this.dateFormat}
statusSpanClass={comparisonData.className}
statusText={this.messages(`status.${comparisonData.status}`)}
subtext={comparisonSubtext}
subtextDate={comparisonSubtextDate}
/>
)
: <span className='feed-status-subtext'>
{comparisonColumn === 'PUBLISHED'
? this.messages('status.feedNotPublished')
: this.messages('status.feedNotInDeployment')}
</span>
}
<>
<tr className='feed-source-table-row'>
<td className='feed-source-info' rowSpan={rowsRequired}>
<FeedInfo feedSource={feedSource} project={project} user={user} />
</td>
}
<td className='feed-version-column'>
<Status
icon={currentVersionData.icon}
dateFormat={this.dateFormat}
statusSpanClass={currentVersionData.className}
statusText={this.messages(`status.${currentVersionData.status}`)}
subtext={lastVersionUpdate}
/>
</td>
<td className='feed-version-column'>
{currentVersionData.startDate}
{currentVersionData.startDate && ' -'}
{currentVersionData.endDate && <br />}
{currentVersionData.endDate}
</td>
<td className='feed-version-column'>{currentVersionData.errorCount}</td>
{isExtensionEnabled('mtc') &&
<td className='feed-version-column'>
<PublishStatus publishState={feedSource.publishState != null ? feedSource.publishState : ''} latestSentToExternalPublisher={feedSource.latestSentToExternalPublisher != null ? feedSource.latestSentToExternalPublisher : 0} />
{comparisonColumn &&
<td className='comparison-column' rowSpan={rowsRequired}>
{comparisonData && hasComparisonDates
? (
<Status
icon={comparisonData.icon}
dateFormat={this.dateFormat}
statusSpanClass={comparisonData.className}
statusText={this.messages(`status.${comparisonData.status}`)}
subtext={comparisonSubtext}
subtextDate={comparisonSubtextDate}
/>
)
: <span className='feed-status-subtext'>
{comparisonColumn === 'PUBLISHED'
? this.messages('status.feedNotPublished')
: this.messages('status.feedNotInDeployment')}
</span>
}
</td>
}
{this.renderDetailCells(activeVersionData, defaultVersion)}
<td rowSpan={rowsRequired}>
<FeedActionsDropdown {...this.props} />
</td>
</tr>
{hasPendingVersion &&
<tr className='feed-source-table-row' key={`${feedSource.id}-extra`}>
{this.renderDetailCells(latestVersionData, latestVersion)}
</tr>
}
<td>
<FeedActionsDropdown {...this.props} />
</td>
</tr>
</>
)
}
}
Expand Down Expand Up @@ -348,32 +390,6 @@ class FeedInfo extends PureComponent<{ feedSource: Feed, project: Project, user:
}
}

class Status extends PureComponent<{
dateFormat: string,
icon: string,
statusSpanClass: string,
statusText: string,
subtext: ?string,
subtextDate?: string
}> {
render () {
const {icon, dateFormat, subtext, statusSpanClass, statusText, subtextDate} = this.props
return (
<div>
<span className={`feed-status ${statusSpanClass}`}>
<Icon type={icon} />
{statusText}
</span>
<div className='feed-status-subtext'>
{subtext}
{subtextDate && <br />}
{subtextDate && `(${moment(subtextDate).format(dateFormat)})`}
</div>
</div>
)
}
}

class FeedActionsDropdown extends PureComponent<Props> {
messages = getComponentMessages('FeedActionsDropdown')

Expand Down
37 changes: 37 additions & 0 deletions lib/manager/components/FeedStatus.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
// @flow

import Icon from '@conveyal/woonerf/components/icon'
import moment from 'moment'
import React from 'react'

type Props = {
dateFormat?: string,
icon: string,
statusSpanClass: string,
statusText: string,
subtext?: string,
subtextDate?: string
}

export default function FeedStatus ({
dateFormat,
icon,
statusSpanClass,
statusText,
subtext,
subtextDate
}: Props) {
return (
<div>
<span className={`feed-status ${statusSpanClass}`}>
<Icon type={icon} />
{statusText}
</span>
<div className='feed-status-subtext'>
{subtext}
{subtextDate && <br />}
{subtextDate && dateFormat && `(${moment(subtextDate).format(dateFormat)})`}
</div>
</div>
)
}
Loading
Loading