Skip to content

Commit cfb00b7

Browse files
bcb37Copilot
authored andcommitted
add edit-condition-weights modal (#2646)
* add edit-condition-weights modal * Update frontend/projects/upgrade/src/app/features/dashboard/experiments/modals/edit-condition-weights-modal/edit-condition-weights-modal.component.spec.ts Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * remove inaccurate comment * tighten css * changes to get closer to the spec * remove unneeded things --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
1 parent c500097 commit cfb00b7

File tree

13 files changed

+1180
-20
lines changed

13 files changed

+1180
-20
lines changed

frontend/projects/upgrade/src/app/core/experiments/experiments.service.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ import { map, filter, tap } from 'rxjs/operators';
5252
import { LocalStorageService } from '../local-storage/local-storage.service';
5353
import { ENV, Environment } from '../../../environments/environment-types';
5454
import { ExperimentSegmentListRequest } from '../segments/store/segments.model';
55+
import { ConditionWeightUpdate } from '../../features/dashboard/experiments/modals/edit-condition-weights-modal/edit-condition-weights-modal.component';
5556

5657
@Injectable()
5758
export class ExperimentService {
@@ -314,4 +315,29 @@ export class ExperimentService {
314315
deleteExperimentExclusionPrivateSegmentList(segmentId: string) {
315316
this.store$.dispatch(experimentAction.actionDeleteExperimentExclusionList({ segmentId }));
316317
}
318+
319+
updateExperimentConditionWeights(experiment: ExperimentVM, weightUpdates: ConditionWeightUpdate[]): void {
320+
// Create updated experiment with new condition weights
321+
const updatedExperiment: ExperimentVM = {
322+
...experiment,
323+
conditions: experiment.conditions.map((condition) => {
324+
const weightUpdate = weightUpdates.find((update) => update.conditionId === condition.id);
325+
326+
return weightUpdate
327+
? {
328+
...condition,
329+
assignmentWeight: weightUpdate.assignmentWeight,
330+
}
331+
: condition;
332+
}),
333+
};
334+
335+
// Dispatch the update action
336+
this.store$.dispatch(
337+
experimentAction.actionUpsertExperiment({
338+
experiment: updatedExperiment,
339+
actionType: UpsertExperimentType.UPDATE_EXPERIMENT,
340+
})
341+
);
342+
}
317343
}

frontend/projects/upgrade/src/app/core/experiments/store/experiments.model.ts

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -80,11 +80,6 @@ export enum NewExperimentPaths {
8080
POST_EXPERIMENT_RULE = 'Post Experiment Rule',
8181
}
8282

83-
export enum ExperimentDesignTypes {
84-
SIMPLE = 'Simple',
85-
FACTORIAL = 'Factorial',
86-
}
87-
8883
export enum OverviewFormWarningStatus {
8984
NO_WARNING = 'no warning',
9085
CONTEXT_CHANGED = 'context changed',
@@ -343,7 +338,7 @@ export interface ExperimentFormData {
343338
name: string;
344339
description: string;
345340
appContext: string;
346-
experimentType: ExperimentDesignTypes;
341+
experimentType: EXPERIMENT_TYPE;
347342
unitOfAssignment: ASSIGNMENT_UNIT;
348343
consistencyRule: CONSISTENCY_RULE;
349344
conditionOrder?: CONDITION_ORDER;
@@ -430,7 +425,7 @@ export interface DraftExperimentRequest {
430425
name: string;
431426
description?: string;
432427
context: string[];
433-
type: ExperimentDesignTypes;
428+
type: EXPERIMENT_TYPE;
434429
assignmentUnit: ASSIGNMENT_UNIT;
435430
state: EXPERIMENT_STATE;
436431
filterMode: FILTER_MODE;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
<app-common-dialog
2+
[title]="config.title"
3+
[cancelBtnLabel]="config.cancelBtnLabel"
4+
[primaryActionBtnLabel]="config.primaryActionBtnLabel"
5+
[primaryActionBtnColor]="config.primaryActionBtnColor"
6+
[primaryActionBtnDisabled]="isPrimaryButtonDisabled$ | async"
7+
(primaryActionBtnClicked)="onPrimaryActionBtnClicked()"
8+
>
9+
<form [formGroup]="conditionWeightForm" class="form-standard dense-3">
10+
<div class="weight-method-section dense-1">
11+
<mat-radio-group formControlName="weightingMethod">
12+
<span class="section-label ft-14-600">{{ 'experiments.edit-condition-weights-modal.method-header.text' | translate }}</span>
13+
<mat-radio-button
14+
*ngFor="let method of weightingMethods"
15+
[value]="method.value"
16+
[disabled]="method.disabled"
17+
>
18+
<div class="radio-content">
19+
<span
20+
class="radio-label ft-14-600"
21+
[class.disabled-text]="method.disabled"
22+
>
23+
{{ method.name | titlecase }}
24+
</span>
25+
<span
26+
class="radio-description ft-12-400"
27+
[class.disabled-text]="method.disabled"
28+
>
29+
{{ method.description | translate }}
30+
</span>
31+
</div>
32+
</mat-radio-button>
33+
</mat-radio-group>
34+
</div>
35+
36+
<div class="table-container">
37+
<table
38+
mat-table
39+
[dataSource]="conditions"
40+
class="conditions-table"
41+
[ngClass]="{ 'no-data': !conditions?.length }"
42+
>
43+
<!-- Condition Column -->
44+
<ng-container matColumnDef="condition">
45+
<th mat-header-cell *matHeaderCellDef class="condition-column ft-14-600">
46+
Condition
47+
</th>
48+
<td mat-cell *matCellDef="let condition" class="condition-column ft-14-400">
49+
{{ condition.conditionCode }}
50+
</td>
51+
<!-- Summary row for condition column -->
52+
<td mat-footer-cell *matFooterCellDef class="condition-column ft-14-400 summary-row">
53+
@if (getTotalWeightStatus()?.totalWeightInvalid) {
54+
<div class="error-text ft-12-400">
55+
{{ 'experiments.edit-condition-weights-modal.weights-sum-validation.text' | translate }}
56+
</div>
57+
}
58+
@if (getTotalWeightStatus()?.max || getTotalWeightStatus()?.min ) {
59+
<div class="error-text ft-12-400">
60+
{{ 'experiments.edit-condition-weights-modal.weights-range-validation.text' | translate }}
61+
</div>
62+
}
63+
@if (getTotalWeightStatus()?.invalidNumber) {
64+
<div class="error-text ft-12-400">
65+
{{ 'experiments.edit-condition-weights-modal.invalid-number-error.text' | translate }}
66+
</div>
67+
}
68+
@if (getTotalWeightStatus()?.tooManyDecimals) {
69+
<div class="error-text ft-12-400">
70+
{{ 'experiments.edit-condition-weights-modal.weights-decimal-validation.text' | translate }}
71+
</div>
72+
}
73+
</td>
74+
</ng-container>
75+
76+
<!-- Weight Column -->
77+
<ng-container matColumnDef="weight">
78+
<th mat-header-cell *matHeaderCellDef class="weight-column ft-14-600">
79+
Weight (%)
80+
</th>
81+
<td mat-cell *matCellDef="let condition; let i = index" class="weight-column ft-14-400">
82+
<mat-form-field appearance="outline" class="weight-input">
83+
<input
84+
matInput
85+
type="number"
86+
min="0"
87+
max="100"
88+
step="0.01"
89+
[formControl]="getWeightControl(i)"
90+
(input)="onWeightChange()"
91+
placeholder="0.00"
92+
/>
93+
94+
95+
</mat-form-field>
96+
</td>
97+
<!-- Summary row for weight column -->
98+
<td mat-footer-cell *matFooterCellDef class="weight-column ft-14-600 summary-row">
99+
<span class="total-weight">
100+
{{ getCurrentTotal() | number:'1.2-2' }}
101+
</span>
102+
</td>
103+
</ng-container>
104+
105+
<!-- Header and Row Definitions -->
106+
<tr mat-header-row *matHeaderRowDef="displayedColumns; sticky: true"></tr>
107+
<tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr>
108+
109+
<!-- Footer Row for Summary -->
110+
<tr mat-footer-row *matFooterRowDef="displayedColumns" class="summary-footer-row"></tr>
111+
112+
<!-- No Data Row -->
113+
<tr *matNoDataRow>
114+
<td class="ft-14-400" [attr.colspan]="displayedColumns.length">
115+
No conditions available
116+
</td>
117+
</tr>
118+
</table>
119+
120+
</div>
121+
</form>
122+
</app-common-dialog>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
.weight-input {
2+
width: 90px;
3+
4+
.mat-mdc-form-field-subscript-wrapper {
5+
display: none;
6+
}
7+
}
8+
9+
.table-container {
10+
position: relative;
11+
overflow: auto;
12+
width: 100%;
13+
14+
/* Add gap between header and body when no data exists */
15+
::ng-deep .no-data tbody:before {
16+
display: block;
17+
line-height: 8px;
18+
content: '\200C';
19+
}
20+
.conditions-table {
21+
/* Remove arrows/spinners from input type number */
22+
input[type="number"] {
23+
-webkit-appearance: textfield;
24+
-moz-appearance: textfield;
25+
26+
&::-webkit-outer-spin-button,
27+
&::-webkit-inner-spin-button {
28+
-webkit-appearance: none;
29+
margin: 0;
30+
}
31+
}
32+
33+
::ng-deep thead {
34+
background-color: var(--zircon);
35+
36+
tr.mat-mdc-header-row {
37+
height: 48px;
38+
border: 0;
39+
40+
th {
41+
padding-left: 0;
42+
color: var(--darker-grey);
43+
44+
&:first-child {
45+
border-top-left-radius: 4px;
46+
}
47+
48+
&:last-child {
49+
border-top-right-radius: 4px;
50+
}
51+
}
52+
}
53+
}
54+
55+
::ng-deep tbody {
56+
tr.mat-mdc-row {
57+
height: 56px;
58+
59+
td {
60+
min-width: 96px;
61+
padding-left: 0;
62+
color: var(--black-2);
63+
}
64+
}
65+
66+
tr.mat-mdc-no-data-row {
67+
td {
68+
height: 48px;
69+
text-align: center;
70+
border: 1.5px dashed var(--light-grey-2);
71+
color: var(--dark-grey);
72+
}
73+
}
74+
}
75+
76+
::ng-deep tfoot {
77+
tr.mat-mdc-footer-row.summary-footer-row {
78+
height: 48px;
79+
border-top: 2px solid var(--light-grey-2);
80+
background-color: var(--light-grey-0);
81+
82+
td.summary-row {
83+
min-width: 96px;
84+
padding-left: 0;
85+
font-weight: 600;
86+
87+
&.condition-column {
88+
padding-left: 32px;
89+
}
90+
91+
&.weight-column {
92+
text-align: right;
93+
padding-right: 32px;
94+
}
95+
96+
.total-weight {
97+
margin: 4px;
98+
}
99+
100+
.error-text {
101+
color: #f44336;
102+
font-weight: 500;
103+
}
104+
105+
}
106+
}
107+
}
108+
109+
.condition-column {
110+
width: 65%;
111+
padding-left: 32px;
112+
}
113+
114+
.weight-column {
115+
width: 35%;
116+
text-align: right;
117+
padding-right: 32px;
118+
119+
.weight-input {
120+
width: 90px;
121+
flex-direction: row;
122+
123+
input {
124+
text-align: right;
125+
}
126+
127+
128+
.mat-mdc-form-field-subscript-wrapper {
129+
display: none;
130+
}
131+
}
132+
}
133+
}
134+
}
135+
136+
.weight-method-section {
137+
margin-bottom: 6px;
138+
139+
.section-label {
140+
display: block;
141+
}
142+
143+
mat-radio-group {
144+
display: flex;
145+
flex-direction: column;
146+
gap: 6px;
147+
}
148+
149+
.radio-content {
150+
display: flex;
151+
flex-direction: column;
152+
153+
.radio-description {
154+
color: var(--dark-grey);
155+
}
156+
}
157+
158+
.disabled-text {
159+
opacity: 0.6;
160+
}
161+
}

0 commit comments

Comments
 (0)