• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

RoundingWell / care-ops-frontend / c8db7f44-c7fd-4a3e-8b98-d394b6031a7d

21 May 2026 06:16PM UTC coverage: 80.537% (-19.4%) from 99.963%
c8db7f44-c7fd-4a3e-8b98-d394b6031a7d

Pull #1697

circleci

paulfalgout
fix(work): include program chain on action and flow fetches that honor it

Centralize ACTION_INCLUDE / FLOW_INCLUDE in the entity service and apply
them everywhere the backend actually reads the include parameter:

- /actions/{id} (fetchAction, fetchActionWithResponses, manage:add refetch)
- /flows/{id} (fetchFlow, manage:add refetch)
- /flows/{id}/relationships/actions (fetchActionsByFlow on the flow page)

The patient-relationship list endpoints
(/patients/{id}/relationships/actions, /patients/{id}/relationships/flows)
silently drop include today, so fetchActionsByPatient and
fetchFlowsByPatient keep their original data shape. Once the backend's
ListPatientActionsResponder / ListPatientFlowsResponder learn to honor
Includes::fromRequest, those call sites can pass ACTION_INCLUDE /
FLOW_INCLUDE the same way.
Pull Request #1697: fix(work): include program relationships on action and flow fetches

1394 of 1875 branches covered (74.35%)

Branch coverage included in aggregate %.

11 of 11 new or added lines in 5 files covered. (100.0%)

1071 existing lines in 100 files now uncovered.

5028 of 6099 relevant lines covered (82.44%)

139.21 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

98.39
/src/js/views/programs/program/flow/flow_views.js
1
import Radio from 'backbone.radio';
2
import hbs from 'handlebars-inline-precompile';
3
import { View, CollectionView } from 'marionette';
4

5
import PreloadRegion from 'js/regions/preload_region';
6

7
import { OwnerComponent as FlowOwnerComponent, FlowBehaviorComponent } from 'js/views/programs/shared/flows_views';
8
import { DueDayComponent, OwnerComponent, BehaviorComponent } from 'js/views/programs/shared/actions_views';
9
import SortableList from 'js/behaviors/sortable-list';
10

11
import ActionItemTemplate from './action-item.hbs';
12
import HeaderTemplate from './header.hbs';
13

14
import 'scss/domain/action-icons.scss';
15
import './program-flow.scss';
16

17
const ContextTrailView = View.extend({
91✔
18
  modelEvents: {
19
    'change:name': 'render',
20
  },
21
  initialize({ program }) {
22
    this.program = program;
12✔
23

24
    this.listenTo(this.program, 'change:name', this.render);
12✔
25
  },
26
  className: 'program-flow__context-trail',
27
  template: hbs`
28
    {{#if hasLatestList}}
29
      <a class="js-back program-flow__context-link">
30
        {{fas "chevron-left"}}{{ @intl.programs.program.flow.flowViews.contextBackBtn }}
31
      </a>
32
      {{fas "chevron-right"}}
33
    {{/if}}
34
    <a class="js-program program-flow__context-link">{{ programName }}</a>{{fas "chevron-right"}}{{ name }}
35
  `,
36
  triggers: {
37
    'click .js-back': 'click:back',
38
    'click .js-program': 'click:program',
39
  },
40
  onClickBack() {
41
    Radio.request('history', 'go:latestList');
1✔
42
  },
43
  onClickProgram() {
44
    Radio.trigger('event-router', 'program:details', this.program.id);
1✔
45
  },
46
  templateContext() {
47
    return {
13✔
48
      hasLatestList: Radio.request('history', 'has:latestList'),
49
      programName: this.program.get('name'),
50
    };
51
  },
52
});
53

54
const HeaderView = View.extend({
91✔
55
  className: 'program-flow__header',
56
  modelEvents: {
57
    'editing': 'onEditing',
58
    'change': 'render',
59
  },
60
  onEditing(isEditing) {
UNCOV
61
    this.$el.toggleClass('is-selected', isEditing);
×
62
  },
63
  template: HeaderTemplate,
64
  regions: {
65
    behavior: '[data-behavior-region]',
66
    owner: '[data-owner-region]',
67
  },
68
  triggers: {
69
    'click': 'edit',
70
  },
71
  onRender() {
72
    this.showBehavior();
18✔
73
    this.showOwner();
18✔
74
  },
75
  showBehavior() {
76
    const behaviorComponent = new FlowBehaviorComponent({
18✔
77
      behavior: this.model.get('behavior'),
78
      isCompact: true,
79
    });
80

81
    this.listenTo(behaviorComponent, 'change:status', ({ behavior }) => {
18✔
82
      this.model.save({ behavior });
2✔
83
    });
84

85
    this.showChildView('behavior', behaviorComponent);
18✔
86
  },
87
  showOwner() {
88
    const ownerComponent = new FlowOwnerComponent({ owner: this.model.getOwner(), isCompact: true });
18✔
89

90
    this.listenTo(ownerComponent, 'change:owner', owner => {
18✔
91
      this.model.saveOwner(owner);
1✔
92
    });
93

94
    this.showChildView('owner', ownerComponent);
18✔
95
  },
96
});
97

98
const AddActionView = View.extend({
91✔
99
  className: 'program-flow__actions',
100
  template: hbs`
101
    <button class="button-primary js-add-action">
102
      {{far "circle-plus"}}<span>{{ @intl.programs.program.flow.flowViews.addActionBtn }}</span>
103
    </button>
104
  `,
105
  triggers: {
106
    'click .js-add-action': 'click:addAction',
107
  },
108
});
109

110
const EmptyView = View.extend({
91✔
111
  className: 'table-list__empty-list',
112
  template: hbs`<h2>{{ @intl.programs.program.flow.flowViews.emptyView }}</h2>`,
113
});
114

115
const ActionItemView = View.extend({
91✔
116
  modelEvents: {
117
    'change': 'render',
118
    'editing': 'onEditing',
119
  },
120
  className() {
121
    const className = 'table-list__item program-flow__action-item js-draggable';
36✔
122
    if (this.model.isNew()) return `${ className } is-selected`;
36✔
123

124
    return className;
34✔
125
  },
126
  template: ActionItemTemplate,
127
  templateContext() {
128
    return {
62✔
129
      hasForm: this.model.getForm(),
130
      icon: this.model.hasOutreach() ? 'share-from-square' : 'file-lines',
62✔
131
    };
132
  },
133
  regions: {
134
    behavior: '[data-behavior-region]',
135
    owner: '[data-owner-region]',
136
    due: '[data-due-region]',
137
  },
138
  triggers: {
139
    'click': 'click',
140
  },
141
  onClick() {
142
    if (this.model.isNew()) {
6✔
143
      Radio.trigger('event-router', 'programFlow:action:new', this.model.getProgramFlow().id);
1✔
144
      return;
1✔
145
    }
146
    Radio.trigger('event-router', 'programFlow:action', this.model.getProgramFlow().id, this.model.id);
5✔
147
  },
148
  onEditing(isEditing) {
149
    this.$el.toggleClass('is-selected', isEditing);
19✔
150
  },
151
  onRender() {
152
    this.showBehavior();
62✔
153
    this.showOwner();
62✔
154
    this.showDue();
62✔
155
  },
156
  showDue() {
157
    const isDisabled = this.model.isNew();
62✔
158
    const dueDayComponent = new DueDayComponent({ day: this.model.get('days_until_due'), isCompact: true, state: { isDisabled } });
62✔
159

160
    this.listenTo(dueDayComponent, 'change:day', day => {
62✔
161
      this.model.save({ days_until_due: day });
2✔
162
    });
163

164
    this.showChildView('due', dueDayComponent);
62✔
165
  },
166
  showBehavior() {
167
    const isDisabled = this.model.isNew();
62✔
168
    const behaviorComponent = new BehaviorComponent({
62✔
169
      behavior: this.model.get('behavior'),
170
      isCompact: true,
171
      state: { isDisabled },
172
    });
173

174
    this.listenTo(behaviorComponent, 'change:status', ({ behavior }) => {
62✔
175
      this.model.save({ behavior });
1✔
176
    });
177

178
    this.showChildView('behavior', behaviorComponent);
62✔
179
  },
180
  showOwner() {
181
    const isDisabled = this.model.isNew();
62✔
182
    const isFromFlow = !!this.model.getProgramFlow();
62✔
183
    const ownerComponent = new OwnerComponent({ owner: this.model.getOwner(), isFromFlow, isCompact: true, state: { isDisabled } });
62✔
184

185
    this.listenTo(ownerComponent, 'change:owner', owner => {
62✔
186
      this.model.saveOwner(owner);
1✔
187
    });
188

189
    this.showChildView('owner', ownerComponent);
62✔
190
  },
191
});
192

193
const ListView = CollectionView.extend({
91✔
194
  behaviors: [
195
    {
196
      behaviorClass: SortableList,
197
      shouldDisable() {
198
        return this.view.collection.length < 2 || this.view.collection.last().isNew();
17✔
199
      },
200
    },
201
  ],
202
  collectionEvents: {
203
    'change:id': 'onChangeId',
204
  },
205
  className: 'table-list__list list-page__list program-flow__list',
206
  childView: ActionItemView,
207
  emptyView: EmptyView,
208
  onDragEnd() {
209
    this.collection.updateSequences();
1✔
210
  },
211
  onChangeId() {
212
    this.collection.updateSequences();
1✔
213
  },
214
});
215

216
const LayoutView = View.extend({
91✔
217
  className: 'program-flow__frame',
218
  template: hbs`
219
    <div class="program-flow__layout">
220
      <div data-context-trail-region></div>
221
      <div data-header-region></div>
222
      <div data-add-action-region></div>
223
      <div class="table-list program-flow__table-list">
224
        <div class="table-list__header list-page__list-header"></div>
225
        <div class="table-list__list" data-action-list-region></div>
226
      </div>
227
    </div>
228
    <div class="program-flow__sidebar" data-sidebar-region></div>
229
  `,
230
  regions: {
231
    contextTrail: {
232
      el: '[data-context-trail-region]',
233
      replaceElement: true,
234
    },
235
    header: '[data-header-region]',
236
    addAction: '[data-add-action-region]',
237
    sidebar: '[data-sidebar-region]',
238
    actionList: {
239
      el: '[data-action-list-region]',
240
      regionClass: PreloadRegion,
241
      replaceElement: true,
242
    },
243
  },
244
});
245

246
export {
247
  LayoutView,
248
  ContextTrailView,
249
  HeaderView,
250
  AddActionView,
251
  ListView,
252
};
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc