Skip to content

Commit 1db16b8

Browse files
authored
Merge pull request #1171 from OpenC3/recurring
Recurring Calendar activities
2 parents 1e38323 + 934f431 commit 1db16b8

File tree

7 files changed

+489
-123
lines changed

7 files changed

+489
-123
lines changed

openc3-cosmos-cmd-tlm-api/app/controllers/activity_controller.rb

+8-5
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
# GNU Affero General Public License for more details.
1515

1616
# Modified by OpenC3, Inc.
17-
# All changes Copyright 2023, OpenC3, Inc.
17+
# All changes Copyright 2024, OpenC3, Inc.
1818
# All Rights Reserved
1919
#
2020
# This file may also be used under the terms of a commercial license
@@ -25,6 +25,7 @@
2525

2626
class ActivityController < ApplicationController
2727
def initialize
28+
super()
2829
@model_class = OpenC3::ActivityModel
2930
end
3031

@@ -84,15 +85,17 @@ def index
8485
def create
8586
return unless authorization('script_run')
8687
begin
87-
hash = params.to_unsafe_h.slice(:start, :stop, :kind, :data).to_h
88+
hash = params.to_unsafe_h.slice(:start, :stop, :kind, :data, :recurring).to_h
8889
hash['data'] ||= {}
8990
hash['data']['username'] = username()
9091
if hash['start'].nil? || hash['stop'].nil?
9192
raise ArgumentError.new 'post body must contain start and stop'
9293
end
93-
9494
hash['start'] = DateTime.parse(hash['start']).strftime('%s').to_i
9595
hash['stop'] = DateTime.parse(hash['stop']).strftime('%s').to_i
96+
if hash['recurring'] and hash['recurring']['end']
97+
hash['recurring']['end'] = DateTime.parse(hash['recurring']['end']).strftime('%s').to_i
98+
end
9699
model = @model_class.from_json(hash.symbolize_keys, name: params[:name], scope: params[:scope])
97100
model.create()
98101
OpenC3::Logger.info(
@@ -288,9 +291,9 @@ def destroy
288291
render :json => { :status => 'error', :message => 'not found' }, :status => 404
289292
return
290293
end
291-
ret = model.destroy()
294+
ret = model.destroy(recurring: params[:recurring])
292295
OpenC3::Logger.info(
293-
"Activity destroyed: #{params[:name]}",
296+
"Activity destroyed name: #{params[:name]} id:#{params[:id]} recurring:#{params[:recurring]}",
294297
scope: params[:scope],
295298
user: username()
296299
)

openc3-cosmos-cmd-tlm-api/app/controllers/timeline_controller.rb

+1-1
Original file line numberDiff line numberDiff line change
@@ -141,7 +141,7 @@ def color
141141
end
142142
end
143143

144-
# Returns hash/object of timeline name in json with a 204 no-content status code.
144+
# Delete timeline
145145
#
146146
# name [String] the timeline name, `system42`
147147
# scope [String] the scope of the timeline, `TEST`

openc3-cosmos-cmd-tlm-api/spec/controllers/activity_controller_spec.rb

-2
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,6 @@ def generate_activity_hash(start)
8080
post :create, params: hash.merge({ 'scope'=>'DEFAULT', 'name'=>'test' })
8181
ret = JSON.parse(response.body, :allow_nan => true, :create_additions => true)
8282
expect(ret['updated_at']).not_to be_nil
83-
expect(ret['duration']).to eql(3600)
8483
expect(ret['start']).not_to be_nil
8584
expect(ret['stop']).not_to be_nil
8685
expect(response).to have_http_status(:created)
@@ -160,7 +159,6 @@ def generate_activity_hash(start)
160159
expect(ret['start']).to eql(created['start'])
161160
expect(ret['stop']).to eql(created['stop'])
162161
expect(ret['updated_at']).not_to be_nil
163-
expect(ret['duration']).to eql(3600)
164162
expect(response).to have_http_status(:ok)
165163
end
166164

openc3-cosmos-init/plugins/packages/openc3-tool-common/src/tools/calendar/Dialogs/ActivityUpdateDialog.vue

+125-33
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
# GNU Affero General Public License for more details.
1414

1515
# Modified by OpenC3, Inc.
16-
# All changes Copyright 2022, OpenC3, Inc.
16+
# All changes Copyright 2024, OpenC3, Inc.
1717
# All Rights Reserved
1818
#
1919
# This file may also be used under the terms of a commercial license
@@ -23,7 +23,7 @@
2323
<template>
2424
<v-dialog v-model="show" width="600">
2525
<v-card>
26-
<form v-on:submit.prevent="updateActivity">
26+
<form @submit.prevent="updateActivity">
2727
<v-system-bar>
2828
<v-spacer />
2929
<span>
@@ -86,16 +86,77 @@
8686
data-test="activity-stop-time"
8787
/>
8888
</v-row>
89-
<v-row class="mx-2 mb-2">
90-
<v-radio-group
91-
v-model="utcOrLocal"
92-
row
93-
hide-details
94-
class="mt-0"
95-
>
96-
<v-radio label="LST" value="loc" data-test="lst-radio" />
97-
<v-radio label="UTC" value="utc" data-test="utc-radio" />
98-
</v-radio-group>
89+
<v-row style="margin-top: 0px">
90+
<v-col>
91+
<v-radio-group
92+
v-model="utcOrLocal"
93+
row
94+
hide-details
95+
class="mt-0"
96+
>
97+
<v-radio label="LST" value="loc" data-test="lst-radio" />
98+
<v-radio label="UTC" value="utc" data-test="utc-radio" />
99+
</v-radio-group>
100+
</v-col>
101+
<v-col>
102+
<v-checkbox
103+
style="padding-top: 0px; margin-top: 0px"
104+
v-model="recurring"
105+
label="Recurring"
106+
hide-details
107+
data-test="recurring"
108+
disabled
109+
>
110+
</v-checkbox>
111+
</v-col>
112+
</v-row>
113+
<v-row v-if="recurring">
114+
<v-col><div class="repeat">Repeat every</div></v-col>
115+
<v-col>
116+
<v-text-field
117+
v-model="frequency"
118+
dense
119+
outlined
120+
single-line
121+
hide-details
122+
disabled
123+
/></v-col>
124+
<v-col>
125+
<v-select
126+
:items="timeSpans"
127+
v-model="timeSpan"
128+
style="primary"
129+
hide-details
130+
dense
131+
outlined
132+
disabled
133+
data-test="cmd-param-select"
134+
/>
135+
</v-col>
136+
</v-row>
137+
<v-row v-if="recurring" style="padding-bottom: 10px">
138+
<v-col><div class="repeat">Ending</div></v-col>
139+
<v-col>
140+
<v-text-field
141+
v-model="recurringEndDate"
142+
type="date"
143+
label="End Date"
144+
class="mx-1"
145+
disabled
146+
:rules="[rules.required]"
147+
data-test="recurring-end-date"
148+
/></v-col>
149+
<v-col>
150+
<v-text-field
151+
v-model="recurringEndTime"
152+
type="time"
153+
step="1"
154+
label="End Time"
155+
class="mx-1"
156+
disabled
157+
:rules="[rules.required]"
158+
data-test="recurrning-end-time"
159+
/></v-col>
99160
</v-row>
100161
<v-row>
101162
<span
@@ -104,7 +165,7 @@
104165
v-text="timeError"
105166
/>
106167
</v-row>
107-
<v-row>
168+
<v-row class="mt-2">
108169
<v-spacer />
109170
<v-btn
110171
@click="dialogStep = 2"
@@ -118,7 +179,6 @@
118179
</div>
119180
</v-card-text>
120181
</v-stepper-content>
121-
122182
<v-stepper-step editable step="2">
123183
Activity type Input
124184
</v-stepper-step>
@@ -134,7 +194,7 @@
134194
placeholder="INST COLLECT with TYPE 0, DURATION 1, OPCODE 171, TEMP 0"
135195
prefix="cmd('"
136196
suffix="')"
137-
hint="Timeline run commands with cmd_no_hazardous_check"
197+
hint="Timeline runs commands with cmd_no_hazardous_check"
138198
data-test="activity-cmd"
139199
/>
140200
</div>
@@ -145,10 +205,10 @@
145205
<div v-else>
146206
<span class="ma-2"> No required input </span>
147207
</div>
148-
<v-row v-show="typeError">
208+
<v-row v-show="typeError" class="mt-2">
149209
<span class="ma-2 red--text" v-text="typeError" />
150210
</v-row>
151-
<v-row>
211+
<v-row class="mt-2">
152212
<v-spacer />
153213
<v-btn
154214
@click="cancelActivity"
@@ -214,6 +274,12 @@ export default {
214274
rules: {
215275
required: (value) => !!value || 'Required',
216276
},
277+
recurring: false,
278+
recurringEndDate: null,
279+
recurringEndTime: null,
280+
frequency: 90,
281+
timeSpan: 'minutes',
282+
timeSpans: ['minutes', 'hours', 'days'],
217283
}
218284
},
219285
watch: {
@@ -260,6 +326,9 @@ export default {
260326
this.kind = inputKind
261327
this.activityData = ''
262328
},
329+
fileHandler: function (event) {
330+
this.activityData = event ? event : null
331+
},
263332
updateValues: function () {
264333
const sDate = new Date(this.activity.start * 1000)
265334
const eDate = new Date(this.activity.stop * 1000)
@@ -270,45 +339,68 @@ export default {
270339
this.kind = this.activity.kind.toUpperCase()
271340
this.activityData = this.activity.data[this.activity.kind]
272341
this.activityEnvironment = this.activity.data.environment
273-
},
274-
fileHandler: function (event) {
275-
this.activityData = event ? event : null
342+
if (this.activity.recurring?.uuid) {
343+
this.recurring = true
344+
const rDate = new Date(this.activity.recurring.end * 1000)
345+
this.recurringEndDate = format(rDate, 'yyyy-MM-dd')
346+
this.recurringEndTime = format(rDate, 'HH:mm:ss')
347+
this.frequency = this.activity.recurring.frequency
348+
this.timeSpan = this.activity.recurring.span
349+
}
276350
},
277351
cancelActivity: function () {
278352
this.show = !this.show
279353
},
280354
updateActivity: function () {
281355
// Call the api to update the activity
282356
const start = this.toIsoString(
283-
Date.parse(`${this.startDate}T${this.startTime}`),
357+
Date.parse(`${this.startDate}T${this.startTime}`)
284358
)
285359
const stop = this.toIsoString(
286-
Date.parse(`${this.stopDate}T${this.stopTime}`),
360+
Date.parse(`${this.stopDate}T${this.stopTime}`)
287361
)
288362
const kind = this.kind.toLowerCase()
289363
let data = { environment: this.activityEnvironment }
290364
data[kind] = this.activityData
291365
const tName = this.activity.name
292366
const aStart = this.activity.start
367+
var recurring = {}
368+
if (this.recurring) {
369+
recurring = {
370+
frequency: this.frequency,
371+
span: this.timeSpan,
372+
end: this.toIsoString(
373+
Date.parse(`${this.recurringEndDate}T${this.recurringEndTime}`)
374+
),
375+
}
376+
}
293377
Api.put(`/openc3-api/timeline/${tName}/activity/${aStart}`, {
294-
data: { start, stop, kind, data },
295-
}).then((response) => {
296-
const activityTime = this.generateDateTime(
297-
new Date(response.data.start * 1000),
298-
)
299-
this.$notify.normal({
300-
title: 'Updated Activity',
301-
body: `${activityTime} (${response.data.start}) on timeline: ${response.data.name}`,
302-
})
378+
data: { start, stop, kind, data, recurring },
303379
})
304-
this.$emit('close')
305-
this.show = !this.show
380+
.then((response) => {
381+
const activityTime = this.generateDateTime(
382+
new Date(response.data.start * 1000)
383+
)
384+
this.$notify.normal({
385+
title: 'Updated Activity',
386+
body: `${activityTime} (${response.data.start}) on timeline: ${response.data.name}`,
387+
})
388+
this.$emit('update')
389+
this.show = !this.show
390+
})
391+
.catch((error) => {
392+
this.show = !this.show
393+
})
306394
},
307395
},
308396
}
309397
</script>
310398

311399
<style scoped>
400+
.repeat {
401+
padding-top: 10px;
402+
text-align: right;
403+
}
312404
.v-stepper--vertical .v-stepper__content {
313405
width: auto;
314406
margin: 0px 0px 0px 36px;

0 commit comments

Comments
 (0)