Show cancelled schedule status

This commit is contained in:
2026-07-03 02:57:18 -04:00
parent 85a8b6ad44
commit c973c03689
20 changed files with 173 additions and 29 deletions

View File

@@ -10,6 +10,7 @@ interface Props {
function SourceBadge({ source }: { source: RaceHub['source'] }) { function SourceBadge({ source }: { source: RaceHub['source'] }) {
if (source === 'local') return <span className="badge badge-local">Local</span> if (source === 'local') return <span className="badge badge-local">Local</span>
if (source === 'partial') return <span className="badge badge-partial">Partial</span> if (source === 'partial') return <span className="badge badge-partial">Partial</span>
if (source === 'cancelled') return <span className="badge badge-cancelled">Cancelled</span>
return <span className="badge badge-none">No data</span> return <span className="badge badge-none">No data</span>
} }

View File

@@ -1,4 +1,4 @@
type Source = 'local' | 'partial' | 'none' type Source = 'local' | 'partial' | 'none' | 'cancelled'
interface Props { interface Props {
source: Source source: Source
@@ -11,6 +11,8 @@ export function SourceBadge({ source, label }: Props) {
return <span className="badge badge-local">{label ?? 'Local'}</span> return <span className="badge badge-local">{label ?? 'Local'}</span>
case 'partial': case 'partial':
return <span className="badge badge-partial">{label ?? 'Partial'}</span> return <span className="badge badge-partial">{label ?? 'Partial'}</span>
case 'cancelled':
return <span className="badge badge-cancelled">{label ?? 'Cancelled'}</span>
default: default:
return <span className="badge badge-none">{label ?? 'None'}</span> return <span className="badge badge-none">{label ?? 'None'}</span>
} }
@@ -22,6 +24,8 @@ export function weekendStatusLabel(source: Source): string {
return 'Full' return 'Full'
case 'partial': case 'partial':
return 'Partial' return 'Partial'
case 'cancelled':
return 'Cancelled'
default: default:
return 'Missing' return 'Missing'
} }

View File

@@ -51,6 +51,7 @@ export function countWeekendStats(weekends: (Weekend | undefined)[]) {
let full = 0 let full = 0
let partial = 0 let partial = 0
let missing = 0 let missing = 0
let cancelled = 0
for (const weekend of weekends) { for (const weekend of weekends) {
if (!weekend || weekend.sessions.length === 0) { if (!weekend || weekend.sessions.length === 0) {
@@ -64,15 +65,19 @@ export function countWeekendStats(weekends: (Weekend | undefined)[]) {
case 'partial': case 'partial':
partial++ partial++
break break
case 'cancelled':
cancelled++
break
default: default:
missing++ missing++
} }
} }
return { full, partial, missing, total: weekends.length } return { full, partial, cancelled, missing, total: weekends.length }
} }
export function sessionIconClass(session: WeekendSession): string { export function sessionIconClass(session: WeekendSession): string {
if (session.source === 'cancelled') return 'si-cancelled'
if (session.source === 'none') return 'si-missing' if (session.source === 'none') return 'si-missing'
if (isSessionComplete(session.datasets)) return 'si-full' if (isSessionComplete(session.datasets)) return 'si-full'
return 'si-partial' return 'si-partial'

View File

@@ -137,6 +137,9 @@ export function DataLibraryPage() {
<span> <span>
<em className="dl-stat-partial">{stats.partial}</em> partial <em className="dl-stat-partial">{stats.partial}</em> partial
</span> </span>
<span>
<em className="dl-stat-cancelled">{stats.cancelled}</em> cancelled
</span>
<span> <span>
<em>{stats.missing}</em> missing <em>{stats.missing}</em> missing
</span> </span>
@@ -177,6 +180,10 @@ export function DataLibraryPage() {
<span className="dl-stat-label">Partial</span> <span className="dl-stat-label">Partial</span>
<span className="dl-stat-val dl-stat-partial">{stats.partial}</span> <span className="dl-stat-val dl-stat-partial">{stats.partial}</span>
</div> </div>
<div className="dl-stat">
<span className="dl-stat-label">Cancelled</span>
<span className="dl-stat-val dl-stat-cancelled">{stats.cancelled}</span>
</div>
<div className="dl-stat"> <div className="dl-stat">
<span className="dl-stat-label">Missing</span> <span className="dl-stat-label">Missing</span>
<span className="dl-stat-val">{stats.missing}</span> <span className="dl-stat-val">{stats.missing}</span>
@@ -294,7 +301,9 @@ export function DataLibraryPage() {
)} )}
</td> </td>
<td className="hide-mobile mono" style={{ color: 'var(--text-2)' }}> <td className="hide-mobile mono" style={{ color: 'var(--text-2)' }}>
{weekend && weekend.sessions.length > 0 {weekend?.source === 'cancelled'
? 'cancelled'
: weekend && weekend.sessions.length > 0
? `${weekend.sessions.filter((s) => s.source === 'local').length}/${weekend.sessions.length} full` ? `${weekend.sessions.filter((s) => s.source === 'local').length}/${weekend.sessions.length} full`
: '—'} : '—'}
</td> </td>

View File

@@ -361,6 +361,7 @@ a { color: inherit; text-decoration: none; }
} }
.badge-local { background: rgba(57,199,58,.12); color: var(--green); border: 1px solid rgba(57,199,58,.25); } .badge-local { background: rgba(57,199,58,.12); color: var(--green); border: 1px solid rgba(57,199,58,.25); }
.badge-partial { background: rgba(255,214,0,.12); color: var(--yellow); border: 1px solid rgba(255,214,0,.25); } .badge-partial { background: rgba(255,214,0,.12); color: var(--yellow); border: 1px solid rgba(255,214,0,.25); }
.badge-cancelled { background: rgba(255,107,53,.12); color: #ff8a5c; border: 1px solid rgba(255,107,53,.28); }
.badge-none { background: rgba(80,80,80,.12); color: var(--text-3); border: 1px solid var(--border); } .badge-none { background: rgba(80,80,80,.12); color: var(--text-3); border: 1px solid var(--border); }
/* ── Dataset strip ── */ /* ── Dataset strip ── */
@@ -1178,6 +1179,7 @@ a { color: inherit; text-decoration: none; }
} }
.dl-banner-stats em.dl-stat-full { color: var(--green); } .dl-banner-stats em.dl-stat-full { color: var(--green); }
.dl-banner-stats em.dl-stat-partial { color: var(--yellow); } .dl-banner-stats em.dl-stat-partial { color: var(--yellow); }
.dl-banner-stats em.dl-stat-cancelled { color: #ff8a5c; }
.dl-footer-link { .dl-footer-link {
display: flex; display: flex;
@@ -1259,6 +1261,7 @@ a { color: inherit; text-decoration: none; }
} }
.dl-stat-full { color: var(--green); } .dl-stat-full { color: var(--green); }
.dl-stat-partial { color: var(--yellow); } .dl-stat-partial { color: var(--yellow); }
.dl-stat-cancelled { color: #ff8a5c; }
.dl-content { .dl-content {
display: flex; display: flex;
@@ -1328,6 +1331,7 @@ a { color: inherit; text-decoration: none; }
} }
.session-icon.si-full { background: rgba(57,199,58,0.2); color: var(--green); } .session-icon.si-full { background: rgba(57,199,58,0.2); color: var(--green); }
.session-icon.si-partial { background: rgba(255,214,0,0.2); color: var(--yellow); } .session-icon.si-partial { background: rgba(255,214,0,0.2); color: var(--yellow); }
.session-icon.si-cancelled { background: rgba(255,107,53,0.18); color: #ff8a5c; }
.session-icon.si-missing { background: rgba(50,50,50,0.5); color: var(--text-3); } .session-icon.si-missing { background: rgba(50,50,50,0.5); color: var(--text-3); }
.dl-detail-wrap { .dl-detail-wrap {
@@ -4440,4 +4444,3 @@ a { color: inherit; text-decoration: none; }
background: #f82f34; background: #f82f34;
transform: translateY(-1px); transform: translateY(-1px);
} }

View File

@@ -59,11 +59,18 @@ describe('coverage helpers', () => {
meeting: {} as Weekend['meeting'], meeting: {} as Weekend['meeting'],
sessions: [{ session: {} as Weekend['sessions'][0]['session'], source: 'partial', datasets: {} }], sessions: [{ session: {} as Weekend['sessions'][0]['session'], source: 'partial', datasets: {} }],
} }
expect(countWeekendStats([local, partial, undefined])).toEqual({ const cancelled: Weekend = {
source: 'cancelled',
meeting_key: 3,
meeting: {} as Weekend['meeting'],
sessions: [{ session: {} as Weekend['sessions'][0]['session'], source: 'cancelled', datasets: {} }],
}
expect(countWeekendStats([local, partial, cancelled, undefined])).toEqual({
full: 1, full: 1,
partial: 1, partial: 1,
cancelled: 1,
missing: 1, missing: 1,
total: 3, total: 4,
}) })
}) })
}) })

View File

@@ -16,6 +16,7 @@ export interface Meeting {
date_start: string date_start: string
date_end: string date_end: string
year: number year: number
is_cancelled?: boolean
} }
export interface Session { export interface Session {
@@ -26,6 +27,7 @@ export interface Session {
date_start: string date_start: string
date_end: string date_end: string
gmt_offset: string gmt_offset: string
is_cancelled?: boolean
} }
export interface Driver { export interface Driver {
@@ -73,7 +75,7 @@ export interface EnrichedGrid {
} }
export interface RaceHub { export interface RaceHub {
source: 'local' | 'partial' | 'none' source: 'local' | 'partial' | 'none' | 'cancelled'
session_key: number session_key: number
datasets: Record<string, DatasetInfo> datasets: Record<string, DatasetInfo>
meeting?: Meeting meeting?: Meeting
@@ -158,12 +160,12 @@ export interface Lap {
export interface WeekendSession { export interface WeekendSession {
session: Session session: Session
source: 'local' | 'partial' | 'none' source: 'local' | 'partial' | 'none' | 'cancelled'
datasets: Record<string, DatasetInfo> datasets: Record<string, DatasetInfo>
} }
export interface Weekend { export interface Weekend {
source: 'local' | 'partial' | 'none' source: 'local' | 'partial' | 'none' | 'cancelled'
meeting_key: number meeting_key: number
meeting: Meeting meeting: Meeting
sessions: WeekendSession[] sessions: WeekendSession[]

View File

@@ -430,6 +430,27 @@ func (s *Service) ingestSessionDatasets(sess models.Session) (Summary, error) {
coverage = make(map[string]store.CoverageEntry) coverage = make(map[string]store.CoverageEntry)
} }
if sess.IsCancelled {
s.opts.Progress.Step("session %d (%s) is cancelled; skipping Race Hub datasets", sessionKey, sess.SessionName)
if !s.opts.DryRun {
for _, dataset := range []string{
"drivers",
"session_result",
"starting_grid",
"stints",
"pit_stops",
"positions",
"race_control",
"weather",
"laps",
} {
_ = s.store.UpsertCoverage(sessionKey, dataset, "skipped", 0, "session cancelled")
}
}
summary.Status = statusForCancelled(s.opts.DryRun)
return summary, nil
}
// 1. Ingest drivers // 1. Ingest drivers
if cov, ok := coverage["drivers"]; ok && cov.Status == "complete" && !s.opts.Force { if cov, ok := coverage["drivers"]; ok && cov.Status == "complete" && !s.opts.Force {
s.opts.Progress.Step("drivers already complete for session %d, skipping", sessionKey) s.opts.Progress.Step("drivers already complete for session %d, skipping", sessionKey)
@@ -919,6 +940,13 @@ func statusForErrors(dryRun bool, errs []string) string {
return "completed" return "completed"
} }
func statusForCancelled(dryRun bool) string {
if dryRun {
return "dry_run"
}
return "cancelled"
}
type fetchFunc[T any] func() (FetchResult, T, error) type fetchFunc[T any] func() (FetchResult, T, error)
func fetchWithRetry[T any](s *Service, fn fetchFunc[T]) (FetchResult, T, error) { func fetchWithRetry[T any](s *Service, fn fetchFunc[T]) (FetchResult, T, error) {

View File

@@ -325,6 +325,7 @@ func meetingToStore(m models.Meeting) store.Meeting {
DateStart: m.DateStart, DateStart: m.DateStart,
DateEnd: m.DateEnd, DateEnd: m.DateEnd,
Year: m.Year, Year: m.Year,
IsCancelled: m.IsCancelled,
} }
} }
@@ -338,6 +339,7 @@ func sessionToStore(s models.Session) store.Session {
DateStart: s.DateStart, DateStart: s.DateStart,
DateEnd: s.DateEnd, DateEnd: s.DateEnd,
GMTOffset: s.GMTOffset, GMTOffset: s.GMTOffset,
IsCancelled: s.IsCancelled,
} }
} }

View File

@@ -24,6 +24,8 @@ type Meeting struct {
DateStart string `json:"date_start"` DateStart string `json:"date_start"`
DateEnd string `json:"date_end"` DateEnd string `json:"date_end"`
Year int `json:"year"` Year int `json:"year"`
IsCancelled bool `json:"is_cancelled"`
} }
type Session struct { type Session struct {
@@ -38,6 +40,8 @@ type Session struct {
DateStart string `json:"date_start"` DateStart string `json:"date_start"`
DateEnd string `json:"date_end"` DateEnd string `json:"date_end"`
GMTOffset string `json:"gmt_offset"` GMTOffset string `json:"gmt_offset"`
IsCancelled bool `json:"is_cancelled"`
} }
// TyreCompound represents the type of tyre compound used. // TyreCompound represents the type of tyre compound used.

View File

@@ -23,6 +23,8 @@ func meetingToModel(m store.Meeting) models.Meeting {
DateStart: m.DateStart, DateStart: m.DateStart,
DateEnd: m.DateEnd, DateEnd: m.DateEnd,
Year: m.Year, Year: m.Year,
IsCancelled: m.IsCancelled,
} }
} }
@@ -36,6 +38,8 @@ func sessionToModel(s store.Session) models.Session {
DateStart: s.DateStart, DateStart: s.DateStart,
DateEnd: s.DateEnd, DateEnd: s.DateEnd,
GMTOffset: s.GMTOffset, GMTOffset: s.GMTOffset,
IsCancelled: s.IsCancelled,
} }
} }

View File

@@ -55,3 +55,23 @@ func datasetsFromCounts(meetingAvailable, sessionAvailable bool, counts store.Se
} }
return ds return ds
} }
func cancelledDatasets() map[string]DatasetInfo {
ds := emptyDatasetMap()
ds["meeting"] = availableLocal(1)
ds["session"] = availableLocal(1)
for _, key := range []string{
"drivers",
"results",
"starting_grid",
"stints",
"pit_stops",
"positions",
"race_control",
"weather",
"laps",
} {
ds[key] = skippedNA()
}
return ds
}

View File

@@ -13,6 +13,7 @@ const (
ResponseSourceLocal = "local" ResponseSourceLocal = "local"
ResponseSourceNone = "none" ResponseSourceNone = "none"
ResponseSourcePartial = "partial" ResponseSourcePartial = "partial"
ResponseSourceCancelled = "cancelled"
) )
// DatasetInfo describes availability of a single dataset. // DatasetInfo describes availability of a single dataset.

View File

@@ -5,6 +5,7 @@ import (
"errors" "errors"
"github.com/AmanTahiliani/box-box/internal/models" "github.com/AmanTahiliani/box-box/internal/models"
"github.com/AmanTahiliani/box-box/internal/store"
) )
// ErrMeetingNotFound is returned when a meeting is not in the local store. // ErrMeetingNotFound is returned when a meeting is not in the local store.
@@ -65,21 +66,35 @@ func (s *Service) GetWeekend(meetingKey int) (Weekend, error) {
} }
datasets := datasetsFromCounts(true, true, counts) datasets := datasetsFromCounts(true, true, counts)
sessionModel := sessionToModel(sess) sessionModel := sessionToModel(sess)
if sess.IsCancelled {
datasets = cancelledDatasets()
}
if datasets["starting_grid"].Status == DatasetStatusMissing && !isGridExpected(sessionModel.SessionType, sessionModel.SessionName) { if datasets["starting_grid"].Status == DatasetStatusMissing && !isGridExpected(sessionModel.SessionType, sessionModel.SessionName) {
datasets["starting_grid"] = skippedNA() datasets["starting_grid"] = skippedNA()
} }
out.Sessions = append(out.Sessions, WeekendSession{ out.Sessions = append(out.Sessions, WeekendSession{
Session: sessionModel, Session: sessionModel,
Source: responseSource(datasets), Source: sessionSource(sess, datasets),
Datasets: datasets, Datasets: datasets,
}) })
} }
if meeting.IsCancelled {
out.Source = ResponseSourceCancelled
} else {
out.Source = weekendSource(out.Sessions) out.Source = weekendSource(out.Sessions)
}
out.DefaultSessionKey = pickDefaultSession(out.Sessions) out.DefaultSessionKey = pickDefaultSession(out.Sessions)
return out, nil return out, nil
} }
func sessionSource(sess store.Session, datasets map[string]DatasetInfo) string {
if sess.IsCancelled {
return ResponseSourceCancelled
}
return responseSource(datasets)
}
func weekendSource(sessions []WeekendSession) string { func weekendSource(sessions []WeekendSession) string {
if len(sessions) == 0 { if len(sessions) == 0 {
return ResponseSourceNone return ResponseSourceNone

View File

@@ -106,6 +106,12 @@ func (s *Service) GetRaceHub(sessionKey int) (RaceHub, error) {
hub.Datasets["meeting"] = availableLocal(1) hub.Datasets["meeting"] = availableLocal(1)
} }
if sess.IsCancelled || (hub.Meeting != nil && hub.Meeting.IsCancelled) {
hub.Source = ResponseSourceCancelled
hub.Datasets = cancelledDatasets()
return hub, nil
}
driverLinks, err := s.store.ListSessionDrivers(sessionKey) driverLinks, err := s.store.ListSessionDrivers(sessionKey)
if err != nil { if err != nil {
return RaceHub{}, err return RaceHub{}, err

View File

@@ -12,8 +12,8 @@ func TestCoverageCRUD(t *testing.T) {
if err != nil { if err != nil {
t.Fatalf("SchemaVersion() error = %v", err) t.Fatalf("SchemaVersion() error = %v", err)
} }
if version != 6 { if version != 7 {
t.Fatalf("SchemaVersion() = %d, want 6", version) t.Fatalf("SchemaVersion() = %d, want 7", version)
} }
// Verify session_coverage table exists // Verify session_coverage table exists

View File

@@ -16,8 +16,8 @@ func (s *Store) UpsertMeeting(m Meeting) error {
INSERT INTO meetings ( INSERT INTO meetings (
meeting_key, meeting_name, meeting_official_name, location, meeting_key, meeting_name, meeting_official_name, location,
country_code, country_name, circuit_key, circuit_short_name, country_code, country_name, circuit_key, circuit_short_name,
gmt_offset, date_start, date_end, year, updated_at gmt_offset, date_start, date_end, year, is_cancelled, updated_at
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
ON CONFLICT(meeting_key) DO UPDATE SET ON CONFLICT(meeting_key) DO UPDATE SET
meeting_name = excluded.meeting_name, meeting_name = excluded.meeting_name,
meeting_official_name = excluded.meeting_official_name, meeting_official_name = excluded.meeting_official_name,
@@ -30,6 +30,7 @@ func (s *Store) UpsertMeeting(m Meeting) error {
date_start = excluded.date_start, date_start = excluded.date_start,
date_end = excluded.date_end, date_end = excluded.date_end,
year = excluded.year, year = excluded.year,
is_cancelled = excluded.is_cancelled,
updated_at = excluded.updated_at updated_at = excluded.updated_at
`, `,
m.MeetingKey, m.MeetingKey,
@@ -44,6 +45,7 @@ func (s *Store) UpsertMeeting(m Meeting) error {
nullString(m.DateStart), nullString(m.DateStart),
nullString(m.DateEnd), nullString(m.DateEnd),
m.Year, m.Year,
boolInt(m.IsCancelled),
m.UpdatedAt.Unix(), m.UpdatedAt.Unix(),
) )
if err != nil { if err != nil {
@@ -56,6 +58,7 @@ func (s *Store) UpsertMeeting(m Meeting) error {
func (s *Store) GetMeeting(meetingKey int) (Meeting, error) { func (s *Store) GetMeeting(meetingKey int) (Meeting, error) {
var m Meeting var m Meeting
var updatedAt int64 var updatedAt int64
var isCancelled int
var officialName, location, countryCode, countryName sql.NullString var officialName, location, countryCode, countryName sql.NullString
var circuitKey sql.NullInt64 var circuitKey sql.NullInt64
var circuitShortName, gmtOffset, dateStart, dateEnd sql.NullString var circuitShortName, gmtOffset, dateStart, dateEnd sql.NullString
@@ -63,7 +66,7 @@ func (s *Store) GetMeeting(meetingKey int) (Meeting, error) {
err := s.db.QueryRow(` err := s.db.QueryRow(`
SELECT meeting_key, meeting_name, meeting_official_name, location, SELECT meeting_key, meeting_name, meeting_official_name, location,
country_code, country_name, circuit_key, circuit_short_name, country_code, country_name, circuit_key, circuit_short_name,
gmt_offset, date_start, date_end, year, updated_at gmt_offset, date_start, date_end, year, is_cancelled, updated_at
FROM meetings FROM meetings
WHERE meeting_key = ? WHERE meeting_key = ?
`, meetingKey).Scan( `, meetingKey).Scan(
@@ -79,6 +82,7 @@ func (s *Store) GetMeeting(meetingKey int) (Meeting, error) {
&dateStart, &dateStart,
&dateEnd, &dateEnd,
&m.Year, &m.Year,
&isCancelled,
&updatedAt, &updatedAt,
) )
if err != nil { if err != nil {
@@ -96,6 +100,7 @@ func (s *Store) GetMeeting(meetingKey int) (Meeting, error) {
m.GMTOffset = gmtOffset.String m.GMTOffset = gmtOffset.String
m.DateStart = dateStart.String m.DateStart = dateStart.String
m.DateEnd = dateEnd.String m.DateEnd = dateEnd.String
m.IsCancelled = isCancelled != 0
m.UpdatedAt = time.Unix(updatedAt, 0) m.UpdatedAt = time.Unix(updatedAt, 0)
return m, nil return m, nil
} }
@@ -129,7 +134,7 @@ func (s *Store) ListMeetingsByYear(year int) ([]Meeting, error) {
rows, err := s.db.Query(` rows, err := s.db.Query(`
SELECT meeting_key, meeting_name, meeting_official_name, location, SELECT meeting_key, meeting_name, meeting_official_name, location,
country_code, country_name, circuit_key, circuit_short_name, country_code, country_name, circuit_key, circuit_short_name,
gmt_offset, date_start, date_end, year, updated_at gmt_offset, date_start, date_end, year, is_cancelled, updated_at
FROM meetings FROM meetings
WHERE year = ? WHERE year = ?
ORDER BY date_start ASC, meeting_key ASC ORDER BY date_start ASC, meeting_key ASC
@@ -151,8 +156,8 @@ func (s *Store) UpsertSession(sess Session) error {
_, err := s.db.Exec(` _, err := s.db.Exec(`
INSERT INTO sessions ( INSERT INTO sessions (
session_key, meeting_key, session_name, session_type, session_key, meeting_key, session_name, session_type,
circuit_key, date_start, date_end, gmt_offset, updated_at circuit_key, date_start, date_end, gmt_offset, is_cancelled, updated_at
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?) ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
ON CONFLICT(session_key) DO UPDATE SET ON CONFLICT(session_key) DO UPDATE SET
meeting_key = excluded.meeting_key, meeting_key = excluded.meeting_key,
session_name = excluded.session_name, session_name = excluded.session_name,
@@ -161,6 +166,7 @@ func (s *Store) UpsertSession(sess Session) error {
date_start = excluded.date_start, date_start = excluded.date_start,
date_end = excluded.date_end, date_end = excluded.date_end,
gmt_offset = excluded.gmt_offset, gmt_offset = excluded.gmt_offset,
is_cancelled = excluded.is_cancelled,
updated_at = excluded.updated_at updated_at = excluded.updated_at
`, `,
sess.SessionKey, sess.SessionKey,
@@ -171,6 +177,7 @@ func (s *Store) UpsertSession(sess Session) error {
nullString(sess.DateStart), nullString(sess.DateStart),
nullString(sess.DateEnd), nullString(sess.DateEnd),
nullString(sess.GMTOffset), nullString(sess.GMTOffset),
boolInt(sess.IsCancelled),
sess.UpdatedAt.Unix(), sess.UpdatedAt.Unix(),
) )
if err != nil { if err != nil {
@@ -183,12 +190,13 @@ func (s *Store) UpsertSession(sess Session) error {
func (s *Store) GetSession(sessionKey int) (Session, error) { func (s *Store) GetSession(sessionKey int) (Session, error) {
var sess Session var sess Session
var updatedAt int64 var updatedAt int64
var isCancelled int
var circuitKey sql.NullInt64 var circuitKey sql.NullInt64
var dateStart, dateEnd, gmtOffset sql.NullString var dateStart, dateEnd, gmtOffset sql.NullString
err := s.db.QueryRow(` err := s.db.QueryRow(`
SELECT session_key, meeting_key, session_name, session_type, SELECT session_key, meeting_key, session_name, session_type,
circuit_key, date_start, date_end, gmt_offset, updated_at circuit_key, date_start, date_end, gmt_offset, is_cancelled, updated_at
FROM sessions FROM sessions
WHERE session_key = ? WHERE session_key = ?
`, sessionKey).Scan( `, sessionKey).Scan(
@@ -200,6 +208,7 @@ func (s *Store) GetSession(sessionKey int) (Session, error) {
&dateStart, &dateStart,
&dateEnd, &dateEnd,
&gmtOffset, &gmtOffset,
&isCancelled,
&updatedAt, &updatedAt,
) )
if err != nil { if err != nil {
@@ -212,6 +221,7 @@ func (s *Store) GetSession(sessionKey int) (Session, error) {
sess.DateStart = dateStart.String sess.DateStart = dateStart.String
sess.DateEnd = dateEnd.String sess.DateEnd = dateEnd.String
sess.GMTOffset = gmtOffset.String sess.GMTOffset = gmtOffset.String
sess.IsCancelled = isCancelled != 0
sess.UpdatedAt = time.Unix(updatedAt, 0) sess.UpdatedAt = time.Unix(updatedAt, 0)
return sess, nil return sess, nil
} }
@@ -220,7 +230,7 @@ func (s *Store) GetSession(sessionKey int) (Session, error) {
func (s *Store) ListSessionsByMeeting(meetingKey int) ([]Session, error) { func (s *Store) ListSessionsByMeeting(meetingKey int) ([]Session, error) {
rows, err := s.db.Query(` rows, err := s.db.Query(`
SELECT session_key, meeting_key, session_name, session_type, SELECT session_key, meeting_key, session_name, session_type,
circuit_key, date_start, date_end, gmt_offset, updated_at circuit_key, date_start, date_end, gmt_offset, is_cancelled, updated_at
FROM sessions FROM sessions
WHERE meeting_key = ? WHERE meeting_key = ?
ORDER BY date_start ASC, session_key ASC ORDER BY date_start ASC, session_key ASC
@@ -238,6 +248,7 @@ func scanMeetings(rows *sql.Rows) ([]Meeting, error) {
for rows.Next() { for rows.Next() {
var m Meeting var m Meeting
var updatedAt int64 var updatedAt int64
var isCancelled int
var officialName, location, countryCode, countryName sql.NullString var officialName, location, countryCode, countryName sql.NullString
var circuitKey sql.NullInt64 var circuitKey sql.NullInt64
var circuitShortName, gmtOffset, dateStart, dateEnd sql.NullString var circuitShortName, gmtOffset, dateStart, dateEnd sql.NullString
@@ -255,6 +266,7 @@ func scanMeetings(rows *sql.Rows) ([]Meeting, error) {
&dateStart, &dateStart,
&dateEnd, &dateEnd,
&m.Year, &m.Year,
&isCancelled,
&updatedAt, &updatedAt,
); err != nil { ); err != nil {
return nil, err return nil, err
@@ -271,6 +283,7 @@ func scanMeetings(rows *sql.Rows) ([]Meeting, error) {
m.GMTOffset = gmtOffset.String m.GMTOffset = gmtOffset.String
m.DateStart = dateStart.String m.DateStart = dateStart.String
m.DateEnd = dateEnd.String m.DateEnd = dateEnd.String
m.IsCancelled = isCancelled != 0
m.UpdatedAt = time.Unix(updatedAt, 0) m.UpdatedAt = time.Unix(updatedAt, 0)
out = append(out, m) out = append(out, m)
} }
@@ -282,6 +295,7 @@ func scanSessions(rows *sql.Rows) ([]Session, error) {
for rows.Next() { for rows.Next() {
var sess Session var sess Session
var updatedAt int64 var updatedAt int64
var isCancelled int
var circuitKey sql.NullInt64 var circuitKey sql.NullInt64
var dateStart, dateEnd, gmtOffset sql.NullString var dateStart, dateEnd, gmtOffset sql.NullString
@@ -294,6 +308,7 @@ func scanSessions(rows *sql.Rows) ([]Session, error) {
&dateStart, &dateStart,
&dateEnd, &dateEnd,
&gmtOffset, &gmtOffset,
&isCancelled,
&updatedAt, &updatedAt,
); err != nil { ); err != nil {
return nil, err return nil, err
@@ -305,6 +320,7 @@ func scanSessions(rows *sql.Rows) ([]Session, error) {
sess.DateStart = dateStart.String sess.DateStart = dateStart.String
sess.DateEnd = dateEnd.String sess.DateEnd = dateEnd.String
sess.GMTOffset = gmtOffset.String sess.GMTOffset = gmtOffset.String
sess.IsCancelled = isCancelled != 0
sess.UpdatedAt = time.Unix(updatedAt, 0) sess.UpdatedAt = time.Unix(updatedAt, 0)
out = append(out, sess) out = append(out, sess)
} }

View File

@@ -0,0 +1,3 @@
ALTER TABLE meetings ADD COLUMN is_cancelled INTEGER NOT NULL DEFAULT 0;
ALTER TABLE sessions ADD COLUMN is_cancelled INTEGER NOT NULL DEFAULT 0;

View File

@@ -68,6 +68,7 @@ type Meeting struct {
DateStart string DateStart string
DateEnd string DateEnd string
Year int Year int
IsCancelled bool
UpdatedAt time.Time UpdatedAt time.Time
} }
@@ -81,6 +82,7 @@ type Session struct {
DateStart string DateStart string
DateEnd string DateEnd string
GMTOffset string GMTOffset string
IsCancelled bool
UpdatedAt time.Time UpdatedAt time.Time
} }

View File

@@ -28,8 +28,8 @@ func TestOpenAppliesMigrations(t *testing.T) {
if err != nil { if err != nil {
t.Fatalf("SchemaVersion() error = %v", err) t.Fatalf("SchemaVersion() error = %v", err)
} }
if version != 6 { if version != 7 {
t.Fatalf("SchemaVersion() = %d, want 6", version) t.Fatalf("SchemaVersion() = %d, want 7", version)
} }
tables := []string{ tables := []string{
@@ -105,6 +105,18 @@ func TestMigrationsAreIdempotent(t *testing.T) {
if count != 1 { if count != 1 {
t.Fatalf("schema_migrations v5 count = %d, want 1", count) t.Fatalf("schema_migrations v5 count = %d, want 1", count)
} }
if err := s.db.QueryRow(`SELECT COUNT(*) FROM schema_migrations WHERE version = 6`).Scan(&count); err != nil {
t.Fatalf("count schema_migrations v6: %v", err)
}
if count != 1 {
t.Fatalf("schema_migrations v6 count = %d, want 1", count)
}
if err := s.db.QueryRow(`SELECT COUNT(*) FROM schema_migrations WHERE version = 7`).Scan(&count); err != nil {
t.Fatalf("count schema_migrations v7: %v", err)
}
if count != 1 {
t.Fatalf("schema_migrations v7 count = %d, want 1", count)
}
} }
func TestRawPayloadInsertAndRead(t *testing.T) { func TestRawPayloadInsertAndRead(t *testing.T) {