Skip to content

Commit ff6df52

Browse files
authored
NIFI-15853 - Support fetchable allowable values in the connector wizard. added searchable select component. (#11159)
* NIFI-15853 - Support fetchable allowable values in the connector wizard. added searchable select component. * Align searchable-select with Tailwind 4 utility names Replace text-md (no-op in v3, undefined in v4) with text-base and flex-shrink-0 (renamed in v4) with shrink-0 in the searchable-select template. Update the spec selector to match. * Replace static title attribute with EllipsisTooltipDirective in searchable-select Add a shared EllipsisTooltipDirective that mounts a MatTooltip on the host and only enables it when the rendered text is actually clipped (horizontal or vertical overflow). Replace the always-on native [title] attributes in the searchable-select option labels and footer messages so the tooltip is themed, positioned consistently, and only appears when needed. Observers attach lazily on hover/focus to keep large option lists cheap. * Address review feedback on searchable-select and multi-select-option Hoist option, panel, search-filter, and form-field styling into global rules in _app.scss so every mat-select benefits from the same typography, radius, padding, and disabled treatment. Switch the trigger and search clear controls to mat-icon-button with the primary-icon-button class. Trim multi-select-option's host bindings and SCSS to layout-only since typography and state styling now flow through the global mat-option rules. Drop connector-property-input's redundant host class binding. Replace the spec's class-name selector for option labels with a stable data-qa hook (data-qa="searchable-select-option-label") so styling class renames cannot break the test in the future. * add highlighting to searchable select panel (hover and keyboard) * align searchable select styling with mat-select (hover, keyboard) * review feedback
1 parent c0cca2b commit ff6df52

17 files changed

Lines changed: 5315 additions & 86 deletions

nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/connectors/ui/connector-configure/connector-configure.component.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ import { ContextErrorBanner } from '../../../../ui/common/context-error-banner/c
4747
import { ErrorContextKey } from '../../../../state/error';
4848
import { ConnectorMessageHost } from '../../service/connector-message-host.service';
4949

50-
function runtimeWizardConfig(): ConnectorWizardConfig {
50+
function connectorWizardConfigFactory(): ConnectorWizardConfig {
5151
const router = inject(Router);
5252
const clusterConnectionService = inject(ClusterConnectionService);
5353
return {
@@ -78,7 +78,7 @@ function runtimeWizardConfig(): ConnectorWizardConfig {
7878
providers: [
7979
StandardConnectorWizardStore,
8080
{ provide: ConnectorWizardStore, useExisting: StandardConnectorWizardStore },
81-
{ provide: CONNECTOR_WIZARD_CONFIG, useFactory: runtimeWizardConfig }
81+
{ provide: CONNECTOR_WIZARD_CONFIG, useFactory: connectorWizardConfigFactory }
8282
],
8383
host: {
8484
class: 'block h-full'

nifi-frontend/src/main/frontend/libs/shared/src/assets/styles/_app.scss

Lines changed: 107 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -262,13 +262,6 @@
262262
min-height: 2.25rem;
263263
}
264264

265-
// mat-select mat-option ellipsis
266-
.mat-mdc-option .mdc-list-item__primary-text {
267-
overflow: hidden;
268-
text-overflow: ellipsis;
269-
white-space: nowrap !important;
270-
}
271-
272265
.mat-typography.text-base {
273266
font-family: var(--mat-sys-body-medium-font);
274267
line-height: var(--mat-sys-body-medium-line-height);
@@ -667,4 +660,111 @@
667660
.markdown-clipboard-toolbar.hover {
668661
opacity: 1;
669662
}
663+
664+
.searchable-overlay {
665+
.mat-mdc-form-field {
666+
width: 100%;
667+
}
668+
669+
.search-filter {
670+
.mdc-text-field--focused {
671+
outline: none;
672+
border-radius: 6px;
673+
border-color: var(--mat-sys-primary);
674+
}
675+
676+
@include mat.form-field-overrides(
677+
(
678+
outlined-container-shape: 6px
679+
)
680+
);
681+
682+
button.primary-icon-button {
683+
@include mat.icon-button-overrides(
684+
(
685+
state-layer-size: 16px,
686+
icon-size: 10px
687+
)
688+
);
689+
}
690+
691+
.fa {
692+
display: flex;
693+
justify-content: center;
694+
color: inherit;
695+
}
696+
}
697+
698+
&.mat-mdc-select-panel,
699+
.mat-mdc-menu-content {
700+
padding: 0 0 8px 0;
701+
}
702+
}
703+
704+
//form-field
705+
.mat-mdc-form-field-icon-prefix {
706+
.fa {
707+
font-size: 16px;
708+
color: var(--themed-reusable-text-primary);
709+
padding: 0 4px 0 0;
710+
}
711+
}
712+
713+
.mdc-text-field--outlined,
714+
.mat-mdc-form-field-has-icon-prefix .mat-mdc-text-field-wrapper {
715+
padding-left: 12px;
716+
padding-right: 12px;
717+
}
718+
719+
// mat-select
720+
@include mat.select-overrides(
721+
(
722+
trigger-text-size: var(--mat-sys-body-medium-size),
723+
trigger-text-line-height: 16px,
724+
trigger-text-tracking: normal
725+
)
726+
);
727+
728+
// mat-select panel appearance
729+
.mat-mdc-select-panel {
730+
margin-top: 8px;
731+
border-color: var(--mat-sys-outline);
732+
border-width: 1px;
733+
border-top-left-radius: 6px !important;
734+
border-top-right-radius: 6px !important;
735+
border-bottom-left-radius: 6px;
736+
border-bottom-right-radius: 6px;
737+
}
738+
739+
// mat-option
740+
.mat-mdc-option[disabled] {
741+
color: var(--mat-sys-on-surface-variant);
742+
opacity: 1;
743+
}
744+
745+
// mat-select mat-option sizing, alignment, typography
746+
.mat-mdc-option {
747+
min-height: 32px;
748+
padding-top: 8px;
749+
padding-bottom: 8px;
750+
padding-left: 12px;
751+
padding-right: 12px;
752+
font-weight: 400;
753+
754+
.mdc-list-item__primary-text {
755+
overflow: hidden;
756+
text-overflow: ellipsis;
757+
white-space: nowrap !important;
758+
font-weight: 500;
759+
color: inherit;
760+
}
761+
762+
.fa {
763+
font-size: 16px;
764+
min-width: 16px;
765+
flex: 0 0 16px;
766+
color: inherit;
767+
margin-right: 4px;
768+
}
769+
}
670770
}
Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
<!--
2+
~ Licensed to the Apache Software Foundation (ASF) under one or more
3+
~ contributor license agreements. See the NOTICE file distributed with
4+
~ this work for additional information regarding copyright ownership.
5+
~ The ASF licenses this file to You under the Apache License, Version 2.0
6+
~ (the "License"); you may not use this file except in compliance with
7+
~ the License. You may obtain a copy of the License at
8+
~
9+
~ http://www.apache.org/licenses/LICENSE-2.0
10+
~
11+
~ Unless required by applicable law or agreed to in writing, software
12+
~ distributed under the License is distributed on an "AS IS" BASIS,
13+
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
~ See the License for the specific language governing permissions and
15+
~ limitations under the License.
16+
-->
17+
@let prop = property();
18+
@if (prop) {
19+
@switch (prop.type) {
20+
@case ('BOOLEAN') {
21+
<mat-checkbox [formControl]="formControl" data-qa="property-input-boolean">
22+
<span class="text-sm">{{ prop.name }}</span>
23+
@if (prop.description) {
24+
<span class="text-xs tertiary-color block">{{ prop.description }}</span>
25+
}
26+
</mat-checkbox>
27+
}
28+
@default {
29+
@if (shouldUseSelect()) {
30+
<div class="w-full">
31+
<div class="text-xs tertiary-color mb-1">
32+
{{ prop.name }}
33+
@if (prop.required) {
34+
<span aria-hidden="true"> *</span>
35+
}
36+
</div>
37+
<searchable-select
38+
[formControl]="formControl"
39+
[options]="selectOptions"
40+
[multiple]="isMultiSelect()"
41+
[placeholder]="getSelectPlaceholder()"
42+
[searchPlaceholder]="'Search'"
43+
[allowClear]="!prop.required"
44+
[hint]="prop.description || ''"
45+
[showHint]="!!prop.description"
46+
[validationError]="getValidationErrorMessage()"
47+
data-qa="property-input-select">
48+
</searchable-select>
49+
@if (isDynamicValuesLoading()) {
50+
<div class="flex items-center gap-2 mt-1" data-qa="property-input-loading">
51+
<mat-progress-spinner diameter="14" mode="indeterminate"></mat-progress-spinner>
52+
<span class="text-xs tertiary-color">Loading values...</span>
53+
</div>
54+
}
55+
</div>
56+
} @else if (shouldUseTextarea()) {
57+
<mat-form-field class="w-full" subscriptSizing="dynamic">
58+
<mat-label>{{ prop.name }}</mat-label>
59+
<textarea
60+
matInput
61+
[formControl]="formControl"
62+
rows="4"
63+
placeholder="Comma-separated list of values"
64+
[required]="prop.required"
65+
(blur)="markAsTouched()"
66+
data-qa="property-input-textarea"></textarea>
67+
@if (isDynamicValuesFetchFailed()) {
68+
<mat-hint data-qa="property-input-textarea-fetch-error-hint">
69+
Unable to load predefined values
70+
</mat-hint>
71+
} @else if (isDynamicValuesFetchEmpty()) {
72+
<mat-hint data-qa="property-input-textarea-fetch-empty-hint">
73+
No predefined values available
74+
</mat-hint>
75+
} @else if (prop.description) {
76+
<mat-hint>{{ prop.description }}</mat-hint>
77+
}
78+
@if (parentControl?.hasError('required') && parentControl?.touched) {
79+
<mat-error>This field is required</mat-error>
80+
}
81+
@if (parentControl?.hasError('verificationError')) {
82+
<mat-error data-qa="verification-error">{{
83+
parentControl?.getError('verificationError')
84+
}}</mat-error>
85+
}
86+
@if (parentControl?.hasError('pattern') && parentControl?.touched) {
87+
<mat-error>Invalid format</mat-error>
88+
}
89+
@if (parentControl?.hasError('assetContentMissing') && parentControl?.touched) {
90+
<mat-error>Asset content is missing</mat-error>
91+
}
92+
</mat-form-field>
93+
} @else {
94+
<mat-form-field class="w-full" subscriptSizing="dynamic">
95+
<mat-label>{{ prop.name }}</mat-label>
96+
<input
97+
matInput
98+
[formControl]="formControl"
99+
[type]="getInputType(prop.type)"
100+
[required]="prop.required"
101+
(blur)="markAsTouched()"
102+
data-qa="property-input-text" />
103+
@if (isDynamicValuesFetchFailed()) {
104+
<mat-hint data-qa="property-input-fetch-error-hint">
105+
Unable to load predefined values
106+
</mat-hint>
107+
} @else if (isDynamicValuesFetchEmpty()) {
108+
<mat-hint data-qa="property-input-fetch-empty-hint"> No predefined values available </mat-hint>
109+
} @else if (prop.description) {
110+
<mat-hint>{{ prop.description }}</mat-hint>
111+
}
112+
@if (parentControl?.hasError('required') && parentControl?.touched) {
113+
<mat-error>This field is required</mat-error>
114+
}
115+
@if (parentControl?.hasError('verificationError')) {
116+
<mat-error data-qa="verification-error">{{
117+
parentControl?.getError('verificationError')
118+
}}</mat-error>
119+
}
120+
@if (parentControl?.hasError('pattern') && parentControl?.touched) {
121+
<mat-error>Invalid format</mat-error>
122+
}
123+
@if (parentControl?.hasError('assetContentMissing') && parentControl?.touched) {
124+
<mat-error>Asset content is missing</mat-error>
125+
}
126+
</mat-form-field>
127+
}
128+
}
129+
}
130+
}

0 commit comments

Comments
 (0)