feat: add Jira URL integration and relay functionality
- Implemented OpenBrowserURL function to open Jira links - Updated components to utilize the new Jira URL feature - Added relay server to manage real-time URL updates - Set default Jira URL in settings if not specified
This commit is contained in:
@@ -9,6 +9,7 @@ import (
|
||||
"time"
|
||||
|
||||
"daily-timer/internal/models"
|
||||
"daily-timer/internal/relay"
|
||||
"daily-timer/internal/services/updater"
|
||||
"daily-timer/internal/storage"
|
||||
"daily-timer/internal/timer"
|
||||
@@ -25,6 +26,7 @@ type App struct {
|
||||
currentLogs map[uint]*models.ParticipantLog
|
||||
participantURLs map[uint]string
|
||||
jiraBaseURL string
|
||||
relay *relay.Server
|
||||
updater *updater.Updater
|
||||
}
|
||||
|
||||
@@ -32,12 +34,20 @@ func New(store *storage.Storage) *App {
|
||||
return &App{
|
||||
store: store,
|
||||
currentLogs: make(map[uint]*models.ParticipantLog),
|
||||
relay: relay.New(),
|
||||
updater: updater.New(),
|
||||
}
|
||||
}
|
||||
|
||||
func (a *App) Startup(ctx context.Context) {
|
||||
a.ctx = ctx
|
||||
a.relay.Start()
|
||||
}
|
||||
|
||||
func (a *App) OpenBrowserURL(url string) {
|
||||
if url != "" {
|
||||
runtime.BrowserOpenURL(a.ctx, url)
|
||||
}
|
||||
}
|
||||
|
||||
func (a *App) OnDomReady(ctx context.Context) {
|
||||
@@ -205,7 +215,8 @@ func (a *App) StartMeeting(participantOrder []uint, attendance map[uint]bool) er
|
||||
}
|
||||
|
||||
if jiraURL != "" {
|
||||
runtime.EventsEmit(a.ctx, "jira:open", jiraURL)
|
||||
a.relay.Broadcast(jiraURL)
|
||||
go runtime.BrowserOpenURL(a.ctx, fmt.Sprintf("http://127.0.0.1:%d/", relay.Port))
|
||||
}
|
||||
|
||||
a.timer = timer.New(meeting.TimeLimit, warningThreshold)
|
||||
@@ -214,6 +225,10 @@ func (a *App) StartMeeting(participantOrder []uint, attendance map[uint]bool) er
|
||||
go a.handleTimerEvents()
|
||||
a.timer.Start()
|
||||
|
||||
if jiraURL != "" {
|
||||
go runtime.BrowserOpenURL(a.ctx, jiraURL)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -233,8 +248,10 @@ func (a *App) handleTimerEvents() {
|
||||
case timer.EventSpeakerChanged:
|
||||
a.saveSpeakerLog(event.State)
|
||||
if url, ok := a.participantURLs[event.State.CurrentSpeakerID]; ok && url != "" {
|
||||
a.relay.Broadcast(url)
|
||||
runtime.EventsEmit(a.ctx, "jira:open", url)
|
||||
} else if a.jiraBaseURL != "" {
|
||||
a.relay.Broadcast(a.jiraBaseURL)
|
||||
runtime.EventsEmit(a.ctx, "jira:open", a.jiraBaseURL)
|
||||
}
|
||||
runtime.EventsEmit(a.ctx, "timer:speaker_changed", event.State)
|
||||
|
||||
@@ -79,6 +79,9 @@ func (s *Storage) ensureDefaults() error {
|
||||
// Migrate old default value to new default (60 min instead of 15)
|
||||
s.db.Model(&settings).Update("default_meeting_time", 3600)
|
||||
}
|
||||
if settings.DefaultJiraUrl == "" {
|
||||
s.db.Model(&settings).Update("default_jira_url", "https://jira.ncloudtech.ru/secure/RapidBoard.jspa?rapidView=1431&projectKey=RNDIN")
|
||||
}
|
||||
|
||||
var meeting models.Meeting
|
||||
result = s.db.First(&meeting)
|
||||
|
||||
@@ -21,7 +21,7 @@ const (
|
||||
)
|
||||
|
||||
type Event struct {
|
||||
Type EventType `json:"type"`
|
||||
Type EventType `json:"type"`
|
||||
State models.TimerState `json:"state"`
|
||||
}
|
||||
|
||||
@@ -31,29 +31,29 @@ type Timer struct {
|
||||
running bool
|
||||
paused bool
|
||||
|
||||
meetingStartTime time.Time
|
||||
meetingElapsed time.Duration
|
||||
meetingLimit time.Duration
|
||||
meetingStartTime time.Time
|
||||
meetingElapsed time.Duration
|
||||
meetingLimit time.Duration
|
||||
|
||||
speakerStartTime time.Time
|
||||
speakerElapsed time.Duration
|
||||
speakerLimit time.Duration
|
||||
speakerStartTime time.Time
|
||||
speakerElapsed time.Duration
|
||||
speakerLimit time.Duration
|
||||
|
||||
currentSpeakerID uint
|
||||
currentSpeaker string
|
||||
speakingOrder int
|
||||
queue []models.QueuedSpeaker
|
||||
allSpeakers []models.SpeakerInfo
|
||||
currentSpeakerID uint
|
||||
currentSpeaker string
|
||||
speakingOrder int
|
||||
queue []models.QueuedSpeaker
|
||||
allSpeakers []models.SpeakerInfo
|
||||
|
||||
warningThreshold time.Duration
|
||||
speakerWarned bool
|
||||
warningThreshold time.Duration
|
||||
speakerWarned bool
|
||||
speakerTimeUpEmitted bool
|
||||
meetingWarned bool
|
||||
meetingWarned bool
|
||||
|
||||
eventCh chan Event
|
||||
ctx context.Context
|
||||
cancel context.CancelFunc
|
||||
pausedAt time.Time
|
||||
eventCh chan Event
|
||||
ctx context.Context
|
||||
cancel context.CancelFunc
|
||||
pausedAt time.Time
|
||||
}
|
||||
|
||||
func New(meetingLimitSec, warningThresholdSec int) *Timer {
|
||||
@@ -75,7 +75,7 @@ func (t *Timer) SetQueue(speakers []models.QueuedSpeaker) {
|
||||
t.mu.Lock()
|
||||
defer t.mu.Unlock()
|
||||
t.queue = speakers
|
||||
|
||||
|
||||
// Initialize allSpeakers with pending status
|
||||
t.allSpeakers = make([]models.SpeakerInfo, len(speakers))
|
||||
for i, s := range speakers {
|
||||
@@ -105,16 +105,8 @@ func (t *Timer) Start() {
|
||||
t.speakerWarned = false
|
||||
t.meetingWarned = false
|
||||
|
||||
hasSpeakers := len(t.queue) > 0
|
||||
if hasSpeakers {
|
||||
t.startNextSpeaker(now, 0)
|
||||
}
|
||||
t.mu.Unlock()
|
||||
|
||||
if hasSpeakers {
|
||||
t.emit(EventSpeakerChanged)
|
||||
}
|
||||
|
||||
// Не активируем участника автоматически!
|
||||
go t.tick()
|
||||
}
|
||||
|
||||
@@ -461,7 +453,9 @@ func (t *Timer) buildState() models.TimerState {
|
||||
|
||||
if t.running && !t.paused {
|
||||
now := time.Now()
|
||||
speakerElapsed = now.Sub(t.speakerStartTime)
|
||||
if t.currentSpeakerID != 0 {
|
||||
speakerElapsed = now.Sub(t.speakerStartTime)
|
||||
}
|
||||
meetingElapsed = now.Sub(t.meetingStartTime)
|
||||
}
|
||||
|
||||
@@ -472,7 +466,7 @@ func (t *Timer) buildState() models.TimerState {
|
||||
// Copy allSpeakers to avoid data race and calculate total speakers time
|
||||
allSpeakers := make([]models.SpeakerInfo, len(t.allSpeakers))
|
||||
copy(allSpeakers, t.allSpeakers)
|
||||
|
||||
|
||||
totalSpeakersTime := 0
|
||||
for _, s := range t.allSpeakers {
|
||||
totalSpeakersTime += s.TimeLimit
|
||||
@@ -519,9 +513,15 @@ func (t *Timer) tick() {
|
||||
}
|
||||
|
||||
now := time.Now()
|
||||
speakerElapsed := now.Sub(t.speakerStartTime)
|
||||
meetingElapsed := now.Sub(t.meetingStartTime)
|
||||
|
||||
if t.currentSpeakerID == 0 {
|
||||
t.mu.Unlock()
|
||||
t.emit(EventTick)
|
||||
continue
|
||||
}
|
||||
|
||||
speakerElapsed := now.Sub(t.speakerStartTime)
|
||||
remaining := t.speakerLimit - speakerElapsed
|
||||
if !t.speakerWarned && remaining <= t.warningThreshold && remaining > 0 {
|
||||
t.speakerWarned = true
|
||||
|
||||
Reference in New Issue
Block a user