diff --git a/canvas_modules/common-canvas/__tests__/common-properties/components/title-editor-test.js b/canvas_modules/common-canvas/__tests__/common-properties/components/title-editor-test.js
index d6275de62d..8857e4d0ba 100644
--- a/canvas_modules/common-canvas/__tests__/common-properties/components/title-editor-test.js
+++ b/canvas_modules/common-canvas/__tests__/common-properties/components/title-editor-test.js
@@ -1,5 +1,5 @@
/*
- * Copyright 2017-2023 Elyra Authors
+ * Copyright 2017-2024 Elyra Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -21,9 +21,12 @@ import { expect } from "chai";
import { expect as expectJest } from "@jest/globals";
import sinon from "sinon";
import { renderWithIntl } from "../../_utils_/intl-utils";
-import { fireEvent } from "@testing-library/react";
+import { fireEvent, within } from "@testing-library/react";
-const controller = new Controller();
+import propertyUtilsRTL from "../../_utils_/property-utilsRTL";
+import ACTION_PARAMDEF from "../../test_resources/paramDefs/action_paramDef.json";
+
+let controller = new Controller();
controller.setTitle("test title");
const form = {
componentId: "test-id"
@@ -400,3 +403,32 @@ describe("title-editor renders correctly", () => {
expect(titleEditor).to.have.length(0);
});
});
+
+describe("Title editor actions", () => {
+ let renderedObject;
+ beforeEach(() => {
+ renderedObject = propertyUtilsRTL.flyoutEditorForm(ACTION_PARAMDEF);
+ controller = new Controller();
+ });
+
+ it("title editor should render actions if defined", () => {
+ const spyPropertyActionHandler = sinon.spy();
+ renderedObject = propertyUtilsRTL.flyoutEditorForm(ACTION_PARAMDEF, null, { actionHandler: spyPropertyActionHandler }, { appData: appData });
+ const { container } = renderedObject.wrapper;
+ const actions = container.querySelector(".properties-title-editor-actions");
+ const buttons = within(actions).getAllByRole("button");
+ expect(buttons.length).to.equal(2);
+
+ expect(buttons[0].textContent).to.equal("Increment");
+ expect(buttons[1].textContent).to.equal("Decrement");
+
+ fireEvent.click(buttons[0]);
+
+ expect(spyPropertyActionHandler.calledOnce).to.equal(true);
+ expect(spyPropertyActionHandler.calledWith(
+ ACTION_PARAMDEF.uihints.title_info.action_refs[0],
+ appData,
+ { parameter_ref: "number" }
+ )).to.be.true;
+ });
+});
diff --git a/canvas_modules/common-canvas/__tests__/test_resources/paramDefs/action_paramDef.json b/canvas_modules/common-canvas/__tests__/test_resources/paramDefs/action_paramDef.json
index 899be402d2..d6934ecbae 100644
--- a/canvas_modules/common-canvas/__tests__/test_resources/paramDefs/action_paramDef.json
+++ b/canvas_modules/common-canvas/__tests__/test_resources/paramDefs/action_paramDef.json
@@ -155,6 +155,12 @@
"label": {
"default": "Action Test"
},
+ "title_info": {
+ "action_refs": [
+ "increment",
+ "decrement"
+ ]
+ },
"parameter_info": [
{
"parameter_ref": "number",
@@ -360,7 +366,6 @@
}
],
"action_info": [
-
{
"id": "increment",
"label": {
diff --git a/canvas_modules/common-canvas/src/common-properties/actions/button/button.jsx b/canvas_modules/common-canvas/src/common-properties/actions/button/button.jsx
index 8de19976e1..3181cdcb20 100644
--- a/canvas_modules/common-canvas/src/common-properties/actions/button/button.jsx
+++ b/canvas_modules/common-canvas/src/common-properties/actions/button/button.jsx
@@ -82,6 +82,7 @@ class ButtonAction extends React.Component {
kind={actionButtonKind}
onClick={this.applyAction}
disabled={disabled}
+ title={this.props.action.label.text}
>
{this.props.action.label.text}
diff --git a/canvas_modules/common-canvas/src/common-properties/components/title-editor/title-editor.jsx b/canvas_modules/common-canvas/src/common-properties/components/title-editor/title-editor.jsx
index 86cb26b0a6..c1d985184a 100644
--- a/canvas_modules/common-canvas/src/common-properties/components/title-editor/title-editor.jsx
+++ b/canvas_modules/common-canvas/src/common-properties/components/title-editor/title-editor.jsx
@@ -1,5 +1,5 @@
/*
- * Copyright 2017-2023 Elyra Authors
+ * Copyright 2017-2024 Elyra Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -17,14 +17,16 @@
import React, { Component } from "react";
import PropTypes from "prop-types";
import { connect } from "react-redux";
-import { setTitle } from "./../../actions";
import Isvg from "react-inlinesvg";
import { get } from "lodash";
+import classNames from "classnames";
+import { Help, Edit, Close } from "@carbon/react/icons";
import { TextInput, Button, Layer } from "@carbon/react";
+
+import { setTitle } from "./../../actions";
import { MESSAGE_KEYS, CONDITION_MESSAGE_TYPE } from "./../../constants/constants";
import * as PropertyUtils from "./../../util/property-utils";
-import classNames from "classnames";
-import { Help, Edit, Close } from "@carbon/react/icons";
+import ActionFactory from "../../actions/action-factory.js";
class TitleEditor extends Component {
@@ -92,6 +94,24 @@ class TitleEditor extends Component {
this.props.setTitle(newTitle);
}
+ createTitleActions() {
+ if (this.props.titleInfo && this.props.titleInfo.Title.actionIds().length > 0) {
+ this.actionFactory = new ActionFactory(this.props.controller);
+ const actions = [];
+ this.props.titleInfo.Title.actionIds().forEach((actionRef, idx) => {
+ const actionMetadata = this.props.controller.getAction({ name: actionRef });
+ const action = this.actionFactory.generateAction(`${actionRef}-${idx}`, actionMetadata);
+ actions.push(
+ {action}
+
);
+ });
+ return (
+ {actions}
+
);
+ }
+ return null;
+ }
+
render() {
if (this.props.title === null) {
return null;
@@ -204,6 +224,7 @@ class TitleEditor extends Component {
{titleValidationTypes.includes(get(this.state.titleValidation, "type")) ? null : propertiesTitleEdit}
{!this.headingEnabled && !titleValidationTypes.includes(get(this.state.titleValidation, "type")) ? helpButton : null}
+ {this.createTitleActions()}
);
}
@@ -219,6 +240,7 @@ TitleEditor.propTypes = {
heading: PropTypes.string,
showHeading: PropTypes.bool,
rightFlyoutTabsView: PropTypes.bool,
+ titleInfo: PropTypes.object,
title: PropTypes.string, // set by redux
setTitle: PropTypes.func // set by redux
};
diff --git a/canvas_modules/common-canvas/src/common-properties/components/title-editor/title-editor.scss b/canvas_modules/common-canvas/src/common-properties/components/title-editor/title-editor.scss
index df62a552da..f2c2852dcf 100644
--- a/canvas_modules/common-canvas/src/common-properties/components/title-editor/title-editor.scss
+++ b/canvas_modules/common-canvas/src/common-properties/components/title-editor/title-editor.scss
@@ -1,5 +1,5 @@
/*
- * Copyright 2017-2023 Elyra Authors
+ * Copyright 2017-2024 Elyra Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -18,6 +18,32 @@
font-size: 0;
padding: $spacing-05 0;
border-bottom: 1px $layer-accent-01 solid;
+
+ .properties-title-editor-actions {
+ padding: $spacing-05 0 0 $spacing-05;
+
+ .properties-action-button {
+ padding-bottom: $spacing-05;
+ width: 100%;
+
+ button {
+ width: 100%;
+ overflow: hidden;
+ white-space: nowrap;
+ display: block;
+ text-overflow: ellipsis;
+ }
+
+ .cds--btn { // Override Carbon padding within a button to allow more text to display
+ --cds-layout-density-padding-inline-local: 0;
+ padding: 6px 15px;
+ }
+ }
+
+ .properties-action-image {
+ padding: 0;
+ }
+ }
}
.properties-title-right-flyout-tabs-view {
diff --git a/canvas_modules/common-canvas/src/common-properties/form/Form.js b/canvas_modules/common-canvas/src/common-properties/form/Form.js
index 826d1746f2..639915b3d6 100644
--- a/canvas_modules/common-canvas/src/common-properties/form/Form.js
+++ b/canvas_modules/common-canvas/src/common-properties/form/Form.js
@@ -24,7 +24,7 @@ import { Size } from "../constants/form-constants";
import { CONTAINER_TYPE } from "../constants/constants";
export default class Form {
- constructor(componentId, label, labelEditable, help, editorSize, pixelWidth, uiItems, buttons, data, conditions, resources, icon, heading) {
+ constructor(componentId, label, labelEditable, help, editorSize, pixelWidth, uiItems, buttons, data, conditions, resources, icon, heading, title) {
this.componentId = componentId;
this.label = label;
this.labelEditable = labelEditable;
@@ -38,6 +38,9 @@ export default class Form {
this.resources = resources;
this.icon = icon;
this.heading = heading;
+ if (title) {
+ this.title = title;
+ }
}
/**
@@ -79,7 +82,8 @@ export default class Form {
translateMessages(conditions, l10nProvider),
resources,
propDef.icon,
- l10nProvider.l10nResource(propDef.heading)
+ l10nProvider.l10nResource(propDef.heading),
+ propDef.titleMetadata
);
}
return null;
diff --git a/canvas_modules/common-canvas/src/common-properties/form/PropertyDef.js b/canvas_modules/common-canvas/src/common-properties/form/PropertyDef.js
index 531d816566..37ea96bd8a 100644
--- a/canvas_modules/common-canvas/src/common-properties/form/PropertyDef.js
+++ b/canvas_modules/common-canvas/src/common-properties/form/PropertyDef.js
@@ -14,6 +14,7 @@
* limitations under the License.
*/
+import { TitleMetadata } from "./TitleInfo";
import { GroupMetadata } from "./GroupInfo";
import { ActionMetadata } from "./ActionInfo";
import { ParameterMetadata } from "./ParameterInfo";
@@ -24,7 +25,9 @@ import { ResourceDef } from "../util/L10nProvider";
import { propertyOf } from "lodash";
export class PropertyDef {
- constructor(cname, icon, editorSize, pixelWidth, label, labelEditable, help, description, structureMetadata, parameterMetadata, groupMetadata, actionMetadata, heading) {
+ constructor(cname, icon, editorSize, pixelWidth, label, labelEditable, help,
+ description, structureMetadata, parameterMetadata, groupMetadata,
+ actionMetadata, heading, titleMetadata) {
this.name = cname;
this.icon = icon;
this.editorSize = editorSize;
@@ -38,6 +41,7 @@ export class PropertyDef {
this.groupMetadata = groupMetadata;
this.actionMetadata = actionMetadata;
this.heading = heading;
+ this.titleMetadata = titleMetadata;
}
/**
@@ -61,6 +65,7 @@ export class PropertyDef {
propertyOf(uihints)("parameter_info"), propertyOf(uihints)("ui_parameters"));
const actionMetadata = ActionMetadata.makeActionMetadata(propertyOf(uihints)("action_info"));
const groupMetadata = GroupMetadata.makeGroupMetadata(propertyOf(uihints)("group_info"), parameterMetadata.getParameters());
+ const titleMetadata = TitleMetadata.makeTitleMetadata(propertyOf(uihints)("title_info"));
const label = titleDefinition && titleDefinition.title ? titleDefinition.title : null;
const labelEditable = titleDefinition && typeof titleDefinition.editable !== "undefined" ? titleDefinition.editable : DEFAULT_LABEL_EDITABLE;
@@ -78,7 +83,8 @@ export class PropertyDef {
parameterMetadata,
groupMetadata,
actionMetadata,
- propertyOf(uihints)("label")
+ propertyOf(uihints)("label"),
+ titleMetadata
);
}
}
diff --git a/canvas_modules/common-canvas/src/common-properties/form/TitleInfo.js b/canvas_modules/common-canvas/src/common-properties/form/TitleInfo.js
new file mode 100644
index 0000000000..43b4dca2bb
--- /dev/null
+++ b/canvas_modules/common-canvas/src/common-properties/form/TitleInfo.js
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2024 Elyra Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { propertyOf } from "lodash";
+
+class Title {
+ constructor(title) {
+ this.actions = title.action_refs || [];
+ }
+
+ static makeTitle(uiGroup) {
+ if (uiGroup) {
+ return new Title(
+ propertyOf(uiGroup)("action_refs")
+ );
+ }
+ return null;
+ }
+
+ actionIds() {
+ return this.actions;
+ }
+}
+
+export class TitleMetadata {
+ constructor(title) {
+ this.Title = title;
+ }
+
+ static makeTitleMetadata(title) {
+ if (title) {
+ return new TitleMetadata(new Title(title));
+ }
+ return null;
+ }
+}
diff --git a/canvas_modules/common-canvas/src/common-properties/properties-main/properties-main.jsx b/canvas_modules/common-canvas/src/common-properties/properties-main/properties-main.jsx
index 98a4b7172d..01a7ef77c6 100644
--- a/canvas_modules/common-canvas/src/common-properties/properties-main/properties-main.jsx
+++ b/canvas_modules/common-canvas/src/common-properties/properties-main/properties-main.jsx
@@ -477,6 +477,7 @@ class PropertiesMain extends React.Component {
icon={formData.icon}
heading={formData.heading}
showHeading={this.props.propertiesConfig.heading}
+ titleInfo={formData.title}
rightFlyoutTabsView={this.props.propertiesConfig.categoryView === CATEGORY_VIEW.TABS}
/>);
diff --git a/canvas_modules/common-canvas/src/common-properties/properties-main/properties-main.scss b/canvas_modules/common-canvas/src/common-properties/properties-main/properties-main.scss
index 422e0578e9..9264ff8280 100644
--- a/canvas_modules/common-canvas/src/common-properties/properties-main/properties-main.scss
+++ b/canvas_modules/common-canvas/src/common-properties/properties-main/properties-main.scss
@@ -25,6 +25,9 @@ $properties-modal-buttons-height: $spacing-10;
overflow: hidden;
border-left: 1px solid $layer-accent-01;
outline: none;
+ display: flex;
+ flex-direction: column;
+
&.properties-small {
width: $common-properties-small-flyout-width;
}
diff --git a/canvas_modules/harness/test_resources/parameterDefs/action_paramDef.json b/canvas_modules/harness/test_resources/parameterDefs/action_paramDef.json
index 899be402d2..d6934ecbae 100644
--- a/canvas_modules/harness/test_resources/parameterDefs/action_paramDef.json
+++ b/canvas_modules/harness/test_resources/parameterDefs/action_paramDef.json
@@ -155,6 +155,12 @@
"label": {
"default": "Action Test"
},
+ "title_info": {
+ "action_refs": [
+ "increment",
+ "decrement"
+ ]
+ },
"parameter_info": [
{
"parameter_ref": "number",
@@ -360,7 +366,6 @@
}
],
"action_info": [
-
{
"id": "increment",
"label": {