From 38c859f2baf90399a928a94d560415b436a1d8cd Mon Sep 17 00:00:00 2001 From: yapei Date: Mon, 30 Dec 2024 10:39:56 +0800 Subject: [PATCH 001/618] OCPBUGS-46404: i18n translations for Export as CSV button --- frontend/public/components/factory/Table/VirtualizedTable.tsx | 4 +++- frontend/public/locales/en/public.json | 1 + 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/frontend/public/components/factory/Table/VirtualizedTable.tsx b/frontend/public/components/factory/Table/VirtualizedTable.tsx index 090234ad983..f8443ccb969 100644 --- a/frontend/public/components/factory/Table/VirtualizedTable.tsx +++ b/frontend/public/components/factory/Table/VirtualizedTable.tsx @@ -1,6 +1,7 @@ import * as React from 'react'; import * as _ from 'lodash'; import { useNavigate } from 'react-router-dom-v5-compat'; +import { useTranslation } from 'react-i18next'; import { Button } from '@patternfly/react-core'; import { TableGridBreakpoint, @@ -90,6 +91,7 @@ const VirtualizedTable: VirtualizedTableFC = ({ csvData, onRowsRendered, }) => { + const { t } = useTranslation(); const navigate = useNavigate(); const columnShift = onSelect ? 1 : 0; //shift indexes by 1 if select provided const [sortBy, setSortBy] = React.useState<{ @@ -197,7 +199,7 @@ const VirtualizedTable: VirtualizedTableFC = ({
{csvData && ( )} diff --git a/frontend/public/locales/en/public.json b/frontend/public/locales/en/public.json index e04516be3a9..7c2171278ce 100644 --- a/frontend/public/locales/en/public.json +++ b/frontend/public/locales/en/public.json @@ -562,6 +562,7 @@ "Reset": "Reset", "Row select": "Row select", "Actions": "Actions", + "Export as CSV": "Export as CSV", "Back": "Back", "Bug Reported": "Bug Reported", "Close": "Close", From ebd4cafd81802d2226fdca28381486d8b0a85c61 Mon Sep 17 00:00:00 2001 From: Steve Goodwin Date: Thu, 9 Jan 2025 13:59:37 -0500 Subject: [PATCH 002/618] Prevent yaml editor crash --- .../components/deployments/utils/deployment-utils.ts | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/frontend/packages/dev-console/src/components/deployments/utils/deployment-utils.ts b/frontend/packages/dev-console/src/components/deployments/utils/deployment-utils.ts index 21f2028b5c6..cd97fa13270 100644 --- a/frontend/packages/dev-console/src/components/deployments/utils/deployment-utils.ts +++ b/frontend/packages/dev-console/src/components/deployments/utils/deployment-utils.ts @@ -351,9 +351,13 @@ export const getTriggersAndImageStreamValues = ( }; } - imageTrigger = JSON.parse( - deployment?.metadata?.annotations?.['image.openshift.io/triggers'] ?? '[]', - )?.[0]; + try { + const triggersAnnotation = + deployment?.metadata?.annotations?.['image.openshift.io/triggers'] ?? '[]'; + imageTrigger = JSON.parse(triggersAnnotation)?.[0]; + } catch (error) { + imageTrigger = undefined; + } imageName = imageTrigger?.from?.name?.split(':') ?? []; return { ...data, From 2e9a641b9e9780ad3fd3dddd3a656426483d212d Mon Sep 17 00:00:00 2001 From: Andreas Gerstmayr Date: Thu, 23 Jan 2025 15:03:28 +0100 Subject: [PATCH 003/618] Show Observe section without PROMETHEUS and MONITORING flags Currently, the Observe section is only shown to users which have the `PROMETHEUS`, `MONITORING` and `CAN_GET_NS` flags. Some dynamic plugins, for example distributed tracing and logging, add a new link to the Observe section, which should be visible to all users - even without the `PROMETHEUS`, `MONITORING` and `CAN_GET_NS` flags. Therefore this PR removes the requirement on this flags. I checked the links in the section, and the monitoring-related links (Alerting, Metrics, Dashboards, Targets) set the required permissions (`PROMETHEUS`, `MONITORING` and `CAN_GET_NS`) on the `console.navigation/href` already: https://github.com/openshift/monitoring-plugin/blob/6b92084514ea9007d7d797f7699eab19e0c2f2fc/web/console-extensions.json In case the Observe section is empty, it is hidden: https://github.com/openshift/console/blob/855f949121cefb3ea63b17ebf91e6bdcdc60d9c8/frontend/packages/console-app/src/components/nav/NavSection.tsx#L57-L60 Signed-off-by: Andreas Gerstmayr --- frontend/packages/console-app/console-extensions.json | 3 --- 1 file changed, 3 deletions(-) diff --git a/frontend/packages/console-app/console-extensions.json b/frontend/packages/console-app/console-extensions.json index 0c902d68828..8b72cdc483d 100644 --- a/frontend/packages/console-app/console-extensions.json +++ b/frontend/packages/console-app/console-extensions.json @@ -736,9 +736,6 @@ "dataAttributes": { "data-quickstart-id": "qs-nav-monitoring" } - }, - "flags": { - "required": ["PROMETHEUS", "MONITORING", "CAN_GET_NS"] } }, { From 109a73cba8a183097c3aa529edf4e62417ba0ba5 Mon Sep 17 00:00:00 2001 From: Vikram Raj Date: Fri, 20 Dec 2024 20:58:51 +0530 Subject: [PATCH 004/618] Show perspective preferences option if more than one perspective are available --- frontend/packages/console-app/console-extensions.json | 9 +++++++++ .../src/components/user-preferences/perspective/index.ts | 1 + .../perspective/usePerspectivesAvailable.ts | 7 +++++++ 3 files changed, 17 insertions(+) create mode 100644 frontend/packages/console-app/src/components/user-preferences/perspective/usePerspectivesAvailable.ts diff --git a/frontend/packages/console-app/console-extensions.json b/frontend/packages/console-app/console-extensions.json index 0c902d68828..804aa6f2218 100644 --- a/frontend/packages/console-app/console-extensions.json +++ b/frontend/packages/console-app/console-extensions.json @@ -465,6 +465,12 @@ ] } }, + { + "type": "console.flag/hookProvider", + "properties": { + "handler": { "$codeRef": "userPreferences.usePerspectivesAvailable" } + } + }, { "type": "console.user-preference/group", "properties": { @@ -527,6 +533,9 @@ "component": { "$codeRef": "userPreferences.PreferredPerspectiveSelect" } }, "insertAfter": "console.theme" + }, + "flags": { + "required": ["FLAG_PERSPECTIVES_AVAILABLE"] } }, { diff --git a/frontend/packages/console-app/src/components/user-preferences/perspective/index.ts b/frontend/packages/console-app/src/components/user-preferences/perspective/index.ts index 8fc0148296a..b779815338f 100644 --- a/frontend/packages/console-app/src/components/user-preferences/perspective/index.ts +++ b/frontend/packages/console-app/src/components/user-preferences/perspective/index.ts @@ -1,2 +1,3 @@ export { default as PreferredPerspectiveSelect } from './PreferredPerspectiveSelect'; export * from './usePreferredPerspective'; +export * from './usePerspectivesAvailable'; diff --git a/frontend/packages/console-app/src/components/user-preferences/perspective/usePerspectivesAvailable.ts b/frontend/packages/console-app/src/components/user-preferences/perspective/usePerspectivesAvailable.ts new file mode 100644 index 00000000000..26b62b0d4a9 --- /dev/null +++ b/frontend/packages/console-app/src/components/user-preferences/perspective/usePerspectivesAvailable.ts @@ -0,0 +1,7 @@ +import { SetFeatureFlag } from '@console/dynamic-plugin-sdk/src'; +import { usePerspectives } from '@console/shared/src'; + +export const usePerspectivesAvailable = (setFeatureFlag: SetFeatureFlag) => { + const perspectives = usePerspectives(); + setFeatureFlag('FLAG_PERSPECTIVES_AVAILABLE', perspectives.length > 1); +}; From 498405991ed89dfc41223e6baa8b69f21c14f3c5 Mon Sep 17 00:00:00 2001 From: Jakub Hadvig Date: Mon, 27 Jan 2025 19:08:50 +0100 Subject: [PATCH 005/618] OCPBUGS-37101: Remove logoutOpenShift method call --- frontend/public/components/masthead-toolbar.jsx | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/frontend/public/components/masthead-toolbar.jsx b/frontend/public/components/masthead-toolbar.jsx index 1d9c10f01f1..c64c664da5a 100644 --- a/frontend/public/components/masthead-toolbar.jsx +++ b/frontend/public/components/masthead-toolbar.jsx @@ -625,11 +625,7 @@ const MastheadToolbarContents = ({ consoleLinks, cv, isMastheadStacked }) => { setLastConsoleActivityTimestamp(); clearTimeout(userInactivityTimeout.current); userInactivityTimeout.current = setTimeout(() => { - if (openshiftFlag) { - authSvc.logoutOpenShift(isKubeAdmin); - } else { - authSvc.logout(); - } + authSvc.logout('', isKubeAdmin); }, window.SERVER_FLAGS.inactivityTimeout * 1000); }, [openshiftFlag, isKubeAdmin]); From 4dd630c55dacd55f938953e1999ddabb0642269d Mon Sep 17 00:00:00 2001 From: Mylanos Date: Tue, 19 Nov 2024 14:04:35 +0100 Subject: [PATCH 006/618] CONSOLE-4080: Refactor KeyValueEntryForm to functional component, move the typing definitions, rename to OpaqueSecretFormEntry --- .../create-secret/GenericSecretForm.tsx | 4 +- .../create-secret/OpaqueSecretFormEntry.tsx | 68 +++++++++++++++++++ .../secrets/create-secret/index.tsx | 2 +- 3 files changed, 71 insertions(+), 3 deletions(-) create mode 100644 frontend/public/components/secrets/create-secret/OpaqueSecretFormEntry.tsx diff --git a/frontend/public/components/secrets/create-secret/GenericSecretForm.tsx b/frontend/public/components/secrets/create-secret/GenericSecretForm.tsx index a5842562612..78b1f22c2cb 100644 --- a/frontend/public/components/secrets/create-secret/GenericSecretForm.tsx +++ b/frontend/public/components/secrets/create-secret/GenericSecretForm.tsx @@ -8,7 +8,7 @@ import { MinusCircleIcon } from '@patternfly/react-icons/dist/esm/icons/minus-ci import { PlusCircleIcon } from '@patternfly/react-icons/dist/esm/icons/plus-circle-icon'; import { isBinary } from 'istextorbinary'; import { KeyValueEntry, SecretSubFormProps } from './types'; -import { KeyValueEntryForm } from './KeyValueEntryForm'; +import { OpaqueSecretFormEntry } from './OpaqueSecretFormEntry'; class GenericSecretFormWithTranslation extends React.Component< SecretSubFormProps & WithT, @@ -117,7 +117,7 @@ class GenericSecretFormWithTranslation extends React.Component<
)} - + ); }); diff --git a/frontend/public/components/secrets/create-secret/OpaqueSecretFormEntry.tsx b/frontend/public/components/secrets/create-secret/OpaqueSecretFormEntry.tsx new file mode 100644 index 00000000000..db72ef1f148 --- /dev/null +++ b/frontend/public/components/secrets/create-secret/OpaqueSecretFormEntry.tsx @@ -0,0 +1,68 @@ +import * as React from 'react'; +import { useTranslation } from 'react-i18next'; +import { DroppableFileInput } from '.'; +import { KeyValueEntryFormProps, KeyValueEntryFormState } from './types'; + +export const OpaqueSecretFormEntry: React.FC = ({ + onChange, + entry, + id, +}) => { + const [state, setState] = React.useState(entry); + const { t } = useTranslation(); + + const onValueChange = (fileData, isBinary) => { + setState((prevState) => ({ + ...prevState, + value: fileData, + isBinary, + })); + }; + + const onKeyChange = (event) => { + setState((prevState) => ({ + ...prevState, + key: event.target.value, + })); + }; + + React.useEffect(() => { + onChange(state, id); + }, [state]); + + return ( +
+
+ +
+ +
+
+
+
+ +
+
+
+ ); +}; diff --git a/frontend/public/components/secrets/create-secret/index.tsx b/frontend/public/components/secrets/create-secret/index.tsx index bd1b3578154..6239ae8fd47 100644 --- a/frontend/public/components/secrets/create-secret/index.tsx +++ b/frontend/public/components/secrets/create-secret/index.tsx @@ -10,7 +10,7 @@ export * from './AuthSecretForm'; export * from './BasicAuthSubform'; export * from './SSHAuthSubform'; export * from './GenericSecretForm'; -export * from './KeyValueEntryForm'; +export * from './OpaqueSecretFormEntry'; export * from './EditSecret'; export * from './CreateSecret'; export * from './DropableFileInput'; From c5c5156d85b594d874fea1a057798b79aa5297a5 Mon Sep 17 00:00:00 2001 From: Mylanos Date: Wed, 20 Nov 2024 13:58:28 +0100 Subject: [PATCH 007/618] CONSOLE-4079: Refactor GenericSecretForm.tsx into functional component, decouple some of the functions to utils file, rename the component to OpaqueSecretForm.tsx, consolidate typings --- .../create-secret/GenericSecretForm.tsx | 149 ------------------ .../create-secret/KeyValueEntryForm.tsx | 85 ---------- .../create-secret/OpaqueSecretForm.tsx | 77 +++++++++ .../create-secret/OpaqueSecretFormEntry.tsx | 68 ++++---- .../create-secret/SecretFormWrapper.tsx | 7 +- .../secrets/create-secret/SecretSubForm.tsx | 4 +- .../secrets/create-secret/index.tsx | 1 - .../components/secrets/create-secret/types.ts | 17 +- .../components/secrets/create-secret/utils.ts | 56 ++++++- 9 files changed, 186 insertions(+), 278 deletions(-) delete mode 100644 frontend/public/components/secrets/create-secret/GenericSecretForm.tsx delete mode 100644 frontend/public/components/secrets/create-secret/KeyValueEntryForm.tsx create mode 100644 frontend/public/components/secrets/create-secret/OpaqueSecretForm.tsx diff --git a/frontend/public/components/secrets/create-secret/GenericSecretForm.tsx b/frontend/public/components/secrets/create-secret/GenericSecretForm.tsx deleted file mode 100644 index 78b1f22c2cb..00000000000 --- a/frontend/public/components/secrets/create-secret/GenericSecretForm.tsx +++ /dev/null @@ -1,149 +0,0 @@ -import * as _ from 'lodash-es'; -import * as React from 'react'; -import { withTranslation } from 'react-i18next'; -import { WithT } from 'i18next'; -import { Base64 } from 'js-base64'; -import { Button } from '@patternfly/react-core'; -import { MinusCircleIcon } from '@patternfly/react-icons/dist/esm/icons/minus-circle-icon'; -import { PlusCircleIcon } from '@patternfly/react-icons/dist/esm/icons/plus-circle-icon'; -import { isBinary } from 'istextorbinary'; -import { KeyValueEntry, SecretSubFormProps } from './types'; -import { OpaqueSecretFormEntry } from './OpaqueSecretFormEntry'; - -class GenericSecretFormWithTranslation extends React.Component< - SecretSubFormProps & WithT, - GenericSecretFormState -> { - constructor(props) { - super(props); - this.state = { - secretEntriesArray: this.genericSecretObjectToArray(this.props.stringData), - }; - this.onDataChanged = this.onDataChanged.bind(this); - } - newGenericSecretEntry() { - return { - entry: { - key: '', - value: '', - }, - uid: _.uniqueId(), - }; - } - genericSecretObjectToArray(genericSecretObject) { - if (_.isEmpty(genericSecretObject)) { - return [this.newGenericSecretEntry()]; - } - return _.map(genericSecretObject, (value, key) => { - const contentIsBinary = isBinary(null, value); - return { - uid: _.uniqueId(), - entry: { - key, - value: contentIsBinary ? Base64.encode(value) : value, - isBase64: contentIsBinary, - isBinary: contentIsBinary, - }, - }; - }); - } - genericSecretArrayToObject(genericSecretArray) { - return _.reduce( - genericSecretArray, - (acc, k) => - _.assign(acc, { - [k.entry.key]: - k.entry?.isBase64 || k.entry?.isBinary ? k.entry.value : Base64.encode(k.entry.value), - }), - {}, - ); - } - onDataChanged(updatedEntry, entryID) { - const updatedSecretEntriesArray = [...this.state.secretEntriesArray]; - const updatedEntryData = { - uid: updatedSecretEntriesArray[entryID].uid, - entry: updatedEntry, - }; - updatedSecretEntriesArray[entryID] = updatedEntryData; - this.setState( - { - secretEntriesArray: updatedSecretEntriesArray, - }, - () => - this.props.onChange({ - base64StringData: this.genericSecretArrayToObject(this.state.secretEntriesArray), - }), - ); - } - removeEntry(entryID) { - const updatedSecretEntriesArray = [...this.state.secretEntriesArray]; - updatedSecretEntriesArray.splice(entryID, 1); - this.setState( - { - secretEntriesArray: updatedSecretEntriesArray, - }, - () => - this.props.onChange({ - base64StringData: this.genericSecretArrayToObject(this.state.secretEntriesArray), - }), - ); - } - addEntry() { - this.setState( - { - secretEntriesArray: _.concat(this.state.secretEntriesArray, this.newGenericSecretEntry()), - }, - () => - this.props.onChange({ - base64StringData: this.genericSecretArrayToObject(this.state.secretEntriesArray), - }), - ); - } - render() { - const { t } = this.props; - const secretEntriesList = _.map(this.state.secretEntriesArray, (entryData, index) => { - return ( -
- {_.size(this.state.secretEntriesArray) > 1 && ( -
- -
- )} - -
- ); - }); - return ( - <> - {secretEntriesList} - - - ); - } -} - -export const GenericSecretForm = withTranslation()(GenericSecretFormWithTranslation); - -type GenericSecretFormState = { - secretEntriesArray: { - entry: KeyValueEntry; - uid: string; - }[]; -}; diff --git a/frontend/public/components/secrets/create-secret/KeyValueEntryForm.tsx b/frontend/public/components/secrets/create-secret/KeyValueEntryForm.tsx deleted file mode 100644 index a2d8355f5e2..00000000000 --- a/frontend/public/components/secrets/create-secret/KeyValueEntryForm.tsx +++ /dev/null @@ -1,85 +0,0 @@ -import * as React from 'react'; -import { withTranslation } from 'react-i18next'; -import { WithT } from 'i18next'; -import { DroppableFileInput } from './DropableFileInput'; -import { KeyValueEntry } from './types'; - -export class KeyValueEntryFormWithTranslation extends React.Component< - KeyValueEntryFormProps & WithT, - KeyValueEntry -> { - constructor(props) { - super(props); - this.state = { - key: props.entry.key, - value: props.entry.value, - isBinary: props.entry.isBinary, - }; - this.onValueChange = this.onValueChange.bind(this); - this.onKeyChange = this.onKeyChange.bind(this); - } - onValueChange(fileData, isBinary) { - this.setState( - { - value: fileData, - isBase64: isBinary, - }, - () => this.props.onChange(this.state, this.props.id), - ); - } - onKeyChange(event) { - this.setState( - { - key: event.target.value, - }, - () => this.props.onChange(this.state, this.props.id), - ); - } - render() { - const { t } = this.props; - return ( -
-
- -
- - - -
-
-
-
- -
-
-
- ); - } -} - -export const KeyValueEntryForm = withTranslation()(KeyValueEntryFormWithTranslation); - -type KeyValueEntryFormProps = { - entry: KeyValueEntry; - id: number; - onChange: Function; -}; diff --git a/frontend/public/components/secrets/create-secret/OpaqueSecretForm.tsx b/frontend/public/components/secrets/create-secret/OpaqueSecretForm.tsx new file mode 100644 index 00000000000..01dc063ce08 --- /dev/null +++ b/frontend/public/components/secrets/create-secret/OpaqueSecretForm.tsx @@ -0,0 +1,77 @@ +import * as React from 'react'; +import { useTranslation } from 'react-i18next'; +import { Button } from '@patternfly/react-core'; +import { MinusCircleIcon } from '@patternfly/react-icons/dist/esm/icons/minus-circle-icon'; +import { PlusCircleIcon } from '@patternfly/react-icons/dist/esm/icons/plus-circle-icon'; +import { SecretSubFormProps, OpaqueDataEntry } from './types'; +import { OpaqueSecretFormEntry } from './OpaqueSecretFormEntry'; +import { opaqueSecretObjectToArray, newOpaqueSecretEntry, opaqueEntriesToObject } from './utils'; +import { size, map } from 'lodash'; + +export const OpaqueSecretForm: React.FC = ({ + onChange, + stringData, + base64StringData, +}) => { + const { t } = useTranslation(); + const [opaqueDataEntries, setOpaqueDataEntries] = React.useState( + opaqueSecretObjectToArray(stringData, base64StringData), + ); + + const onDataChanged = (newEntries: OpaqueDataEntry[]) => { + setOpaqueDataEntries(newEntries); + onChange(opaqueEntriesToObject(newEntries)); + }; + + const updateEntry = (updatedEntry: OpaqueDataEntry, entryIndex: number) => { + onDataChanged( + opaqueDataEntries.map((entry, index) => + index === entryIndex ? { ...updatedEntry, uid: entry.uid } : entry, + ), + ); + }; + + const removeEntry = (entryID: number) => { + onDataChanged(opaqueDataEntries.filter((_, index) => index !== entryID)); + }; + + const addEntry = () => { + onDataChanged([...opaqueDataEntries, newOpaqueSecretEntry()]); + }; + + const secretEntriesList = map(opaqueDataEntries, (entry, index) => { + return ( +
+ {size(opaqueDataEntries) > 1 && ( +
+ +
+ )} + +
+ ); + }); + return ( + <> + {secretEntriesList} + + + ); +}; diff --git a/frontend/public/components/secrets/create-secret/OpaqueSecretFormEntry.tsx b/frontend/public/components/secrets/create-secret/OpaqueSecretFormEntry.tsx index db72ef1f148..092c2611fbc 100644 --- a/frontend/public/components/secrets/create-secret/OpaqueSecretFormEntry.tsx +++ b/frontend/public/components/secrets/create-secret/OpaqueSecretFormEntry.tsx @@ -1,65 +1,65 @@ import * as React from 'react'; import { useTranslation } from 'react-i18next'; -import { DroppableFileInput } from '.'; -import { KeyValueEntryFormProps, KeyValueEntryFormState } from './types'; +import { DroppableFileInput } from './DropableFileInput'; +import { OpaqueSecretFormEntryProps } from './types'; -export const OpaqueSecretFormEntry: React.FC = ({ +export const OpaqueSecretFormEntry: React.FC = ({ onChange, entry, - id, + index, }) => { - const [state, setState] = React.useState(entry); const { t } = useTranslation(); - const onValueChange = (fileData, isBinary) => { - setState((prevState) => ({ - ...prevState, + const handleValueChange = (fileData: string, isBinary: boolean) => { + const updatedEntry = { + ...entry, value: fileData, - isBinary, - })); + isBinary_: isBinary, + }; + onChange(updatedEntry, index); }; - const onKeyChange = (event) => { - setState((prevState) => ({ - ...prevState, - key: event.target.value, - })); + const handleKeyChange = (event: React.ChangeEvent) => { + onChange( + { + ...entry, + key: event.target.value, + }, + index, + ); }; - React.useEffect(() => { - onChange(state, id); - }, [state]); - return (
-
diff --git a/frontend/public/components/secrets/create-secret/SecretFormWrapper.tsx b/frontend/public/components/secrets/create-secret/SecretFormWrapper.tsx index c57a349d7f5..197a2391ada 100644 --- a/frontend/public/components/secrets/create-secret/SecretFormWrapper.tsx +++ b/frontend/public/components/secrets/create-secret/SecretFormWrapper.tsx @@ -19,6 +19,7 @@ import { useSecretDescription, } from './utils'; import { SecretSubForm } from './SecretSubForm'; +import { isBinary } from 'istextorbinary'; export const SecretFormWrapper: React.FC = (props) => { const { isCreate, modal, onCancel } = props; @@ -44,10 +45,13 @@ export const SecretFormWrapper: React.FC = (props) => { const [error, setError] = React.useState(); const [stringData, setStringData] = React.useState( _.mapValues(_.get(props.obj, 'data'), (value) => { + if (isBinary(null, Buffer.from(value, 'base64'))) { + return null; + } return value ? Base64.decode(value) : ''; }), ); - const [base64StringData, setBase64StringData] = React.useState({}); + const [base64StringData, setBase64StringData] = React.useState(props?.obj?.data ?? {}); const [disableForm, setDisableForm] = React.useState(false); const title = useSecretTitle(isCreate, secretTypeAbstraction); const helptext = useSecretDescription(secretTypeAbstraction); @@ -146,6 +150,7 @@ export const SecretFormWrapper: React.FC = (props) => { stringData={stringData} secretType={secret.type} isCreate={isCreate} + base64StringData={base64StringData} /> ); diff --git a/frontend/public/components/secrets/create-secret/SecretSubForm.tsx b/frontend/public/components/secrets/create-secret/SecretSubForm.tsx index 57ea9cbc3a5..06c6bfae531 100644 --- a/frontend/public/components/secrets/create-secret/SecretSubForm.tsx +++ b/frontend/public/components/secrets/create-secret/SecretSubForm.tsx @@ -3,7 +3,7 @@ import { SecretSubFormProps, SecretTypeAbstraction } from './types'; import { AuthSecretForm } from './AuthSecretForm'; import { PullSecretForm } from './PullSecretForm'; import { WebHookSecretForm } from './WebHookSecretForm'; -import { GenericSecretForm } from './GenericSecretForm'; +import { OpaqueSecretForm } from './OpaqueSecretForm'; export const SecretSubForm: React.FC< SecretSubFormProps & { typeAbstraction: SecretTypeAbstraction } @@ -16,6 +16,6 @@ export const SecretSubForm: React.FC< case SecretTypeAbstraction.webhook: return ; default: - return ; + return ; } }; diff --git a/frontend/public/components/secrets/create-secret/index.tsx b/frontend/public/components/secrets/create-secret/index.tsx index 6239ae8fd47..786e930c0d0 100644 --- a/frontend/public/components/secrets/create-secret/index.tsx +++ b/frontend/public/components/secrets/create-secret/index.tsx @@ -9,7 +9,6 @@ export * from './WebHookSecretForm'; export * from './AuthSecretForm'; export * from './BasicAuthSubform'; export * from './SSHAuthSubform'; -export * from './GenericSecretForm'; export * from './OpaqueSecretFormEntry'; export * from './EditSecret'; export * from './CreateSecret'; diff --git a/frontend/public/components/secrets/create-secret/types.ts b/frontend/public/components/secrets/create-secret/types.ts index 4fbdd5625dc..960355ccdf5 100644 --- a/frontend/public/components/secrets/create-secret/types.ts +++ b/frontend/public/components/secrets/create-secret/types.ts @@ -24,11 +24,14 @@ export type SecretSubFormProps = { isCreate?: boolean; }; +type Base64String = string; +export type SecretStringData = { [key: string]: string }; +export type Base64StringData = { [key: string]: Base64String }; + export type SecretChangeData = { stringData?: SecretStringData; base64StringData?: SecretStringData; }; -export type SecretStringData = { [key: string]: string }; export type OnSecretChange = (stringData: SecretChangeData) => void; @@ -41,9 +44,15 @@ export type PullSecretCredential = { uid: string; }; -export type KeyValueEntry = { - isBase64?: boolean; - isBinary?: boolean; +export type OpaqueDataEntry = { + isBinary_?: boolean; key: string; value: string; + uid: string; +}; + +export type OpaqueSecretFormEntryProps = { + entry: OpaqueDataEntry; + index: number; + onChange: Function; }; diff --git a/frontend/public/components/secrets/create-secret/utils.ts b/frontend/public/components/secrets/create-secret/utils.ts index ff795be0359..02513a52733 100644 --- a/frontend/public/components/secrets/create-secret/utils.ts +++ b/frontend/public/components/secrets/create-secret/utils.ts @@ -1,8 +1,17 @@ import * as _ from 'lodash-es'; import { useTranslation } from 'react-i18next'; -import { Base64 } from 'js-base64'; import { WebHookSecretKey } from './const'; -import { SecretTypeAbstraction, SecretType, PullSecretCredential } from './types'; +import { + SecretTypeAbstraction, + SecretType, + PullSecretCredential, + Base64StringData, + OpaqueDataEntry, + SecretStringData, + SecretChangeData, +} from './types'; +import { isBinary } from 'istextorbinary'; +import { Base64 } from 'js-base64'; export const toDefaultSecretType = (typeAbstraction: SecretTypeAbstraction): SecretType => { switch (typeAbstraction) { @@ -178,3 +187,46 @@ type DockerCfg = { }; type PullSecretData = DockerCfg | DockerConfigJSON; + +export const opaqueEntriesToObject = (opaqueEntriesArray: OpaqueDataEntry[]): SecretChangeData => { + return (opaqueEntriesArray ?? []).reduce( + (acc, { key, value, isBinary_ }) => { + return { + stringData: { + ...acc.stringData, + [key]: isBinary_ ? null : value, + }, + base64StringData: isBinary_ + ? { ...acc.base64StringData, [key]: value } + : acc.base64StringData, + }; + }, + { stringData: {}, base64StringData: {} }, + ); +}; + +export const newOpaqueSecretEntry = (): OpaqueDataEntry => { + return { + key: '', + value: '', + isBinary_: false, + uid: _.uniqueId(), + }; +}; + +export const opaqueSecretObjectToArray = ( + stringData: SecretStringData, + base64StringData?: Base64StringData, +): OpaqueDataEntry[] => { + if (_.isEmpty(stringData)) { + return [newOpaqueSecretEntry()]; + } + return _.map(stringData, (value, key) => { + return { + key, + value: value ?? base64StringData[key], + isBinary_: value ? false : isBinary(null, Buffer.from(base64StringData[key] || '', 'base64')), + uid: _.uniqueId(), + }; + }); +}; From 9afa5677fc70d9717ce62c511a3330d4d5fcdc94 Mon Sep 17 00:00:00 2001 From: Mylanos Date: Mon, 6 Jan 2025 16:22:50 +0100 Subject: [PATCH 008/618] CONSOLE-4079: track the input state solely in the base64StringData --- .../create-secret/OpaqueSecretForm.tsx | 8 ++----- .../create-secret/OpaqueSecretFormEntry.tsx | 5 ++-- .../components/secrets/create-secret/types.ts | 1 + .../components/secrets/create-secret/utils.ts | 24 +++++++------------ 4 files changed, 14 insertions(+), 24 deletions(-) diff --git a/frontend/public/components/secrets/create-secret/OpaqueSecretForm.tsx b/frontend/public/components/secrets/create-secret/OpaqueSecretForm.tsx index 01dc063ce08..ca70781a9a4 100644 --- a/frontend/public/components/secrets/create-secret/OpaqueSecretForm.tsx +++ b/frontend/public/components/secrets/create-secret/OpaqueSecretForm.tsx @@ -8,14 +8,10 @@ import { OpaqueSecretFormEntry } from './OpaqueSecretFormEntry'; import { opaqueSecretObjectToArray, newOpaqueSecretEntry, opaqueEntriesToObject } from './utils'; import { size, map } from 'lodash'; -export const OpaqueSecretForm: React.FC = ({ - onChange, - stringData, - base64StringData, -}) => { +export const OpaqueSecretForm: React.FC = ({ onChange, base64StringData }) => { const { t } = useTranslation(); const [opaqueDataEntries, setOpaqueDataEntries] = React.useState( - opaqueSecretObjectToArray(stringData, base64StringData), + opaqueSecretObjectToArray(base64StringData), ); const onDataChanged = (newEntries: OpaqueDataEntry[]) => { diff --git a/frontend/public/components/secrets/create-secret/OpaqueSecretFormEntry.tsx b/frontend/public/components/secrets/create-secret/OpaqueSecretFormEntry.tsx index 092c2611fbc..fbd28b6b4b6 100644 --- a/frontend/public/components/secrets/create-secret/OpaqueSecretFormEntry.tsx +++ b/frontend/public/components/secrets/create-secret/OpaqueSecretFormEntry.tsx @@ -1,5 +1,6 @@ import * as React from 'react'; import { useTranslation } from 'react-i18next'; +import { Base64 } from 'js-base64'; import { DroppableFileInput } from './DropableFileInput'; import { OpaqueSecretFormEntryProps } from './types'; @@ -13,7 +14,7 @@ export const OpaqueSecretFormEntry: React.FC = ({ const handleValueChange = (fileData: string, isBinary: boolean) => { const updatedEntry = { ...entry, - value: fileData, + value: isBinary ? fileData : Base64.encode(fileData), isBinary_: isBinary, }; onChange(updatedEntry, index); @@ -53,7 +54,7 @@ export const OpaqueSecretFormEntry: React.FC = ({
{ return (opaqueEntriesArray ?? []).reduce( - (acc, { key, value, isBinary_ }) => { + (acc, { key, value }) => { return { - stringData: { - ...acc.stringData, - [key]: isBinary_ ? null : value, - }, - base64StringData: isBinary_ - ? { ...acc.base64StringData, [key]: value } - : acc.base64StringData, + base64StringData: { ...acc.base64StringData, [key]: value }, }; }, - { stringData: {}, base64StringData: {} }, + { base64StringData: {} }, ); }; @@ -215,17 +208,16 @@ export const newOpaqueSecretEntry = (): OpaqueDataEntry => { }; export const opaqueSecretObjectToArray = ( - stringData: SecretStringData, - base64StringData?: Base64StringData, + base64StringData: Base64StringData, ): OpaqueDataEntry[] => { - if (_.isEmpty(stringData)) { + if (_.isEmpty(base64StringData)) { return [newOpaqueSecretEntry()]; } - return _.map(stringData, (value, key) => { + return _.map(base64StringData, (value, key) => { return { key, - value: value ?? base64StringData[key], - isBinary_: value ? false : isBinary(null, Buffer.from(base64StringData[key] || '', 'base64')), + value, + isBinary_: isBinary(null, Buffer.from(value || '', 'base64')), uid: _.uniqueId(), }; }); From f1edbfdafd46f55b8eca1d119f84f784aa651d22 Mon Sep 17 00:00:00 2001 From: Mylanos Date: Sun, 12 Jan 2025 21:16:25 +0100 Subject: [PATCH 009/618] CONSOLE-4079,CONSOLE-4080: address review comments --- .../create-secret/OpaqueSecretForm.tsx | 40 +++++++++---------- .../create-secret/OpaqueSecretFormEntry.tsx | 22 +++++----- .../components/secrets/create-secret/types.ts | 7 ++-- .../components/secrets/create-secret/utils.ts | 8 ++-- 4 files changed, 37 insertions(+), 40 deletions(-) diff --git a/frontend/public/components/secrets/create-secret/OpaqueSecretForm.tsx b/frontend/public/components/secrets/create-secret/OpaqueSecretForm.tsx index ca70781a9a4..2cd7b784d30 100644 --- a/frontend/public/components/secrets/create-secret/OpaqueSecretForm.tsx +++ b/frontend/public/components/secrets/create-secret/OpaqueSecretForm.tsx @@ -6,7 +6,6 @@ import { PlusCircleIcon } from '@patternfly/react-icons/dist/esm/icons/plus-circ import { SecretSubFormProps, OpaqueDataEntry } from './types'; import { OpaqueSecretFormEntry } from './OpaqueSecretFormEntry'; import { opaqueSecretObjectToArray, newOpaqueSecretEntry, opaqueEntriesToObject } from './utils'; -import { size, map } from 'lodash'; export const OpaqueSecretForm: React.FC = ({ onChange, base64StringData }) => { const { t } = useTranslation(); @@ -35,26 +34,25 @@ export const OpaqueSecretForm: React.FC = ({ onChange, base6 onDataChanged([...opaqueDataEntries, newOpaqueSecretEntry()]); }; - const secretEntriesList = map(opaqueDataEntries, (entry, index) => { - return ( -
- {size(opaqueDataEntries) > 1 && ( -
- -
- )} - -
- ); - }); + const secretEntriesList = opaqueDataEntries.map((entry, index) => ( +
+ {opaqueDataEntries.length > 1 && ( +
+ +
+ )} + +
+ )); + return ( <> {secretEntriesList} diff --git a/frontend/public/components/secrets/create-secret/OpaqueSecretFormEntry.tsx b/frontend/public/components/secrets/create-secret/OpaqueSecretFormEntry.tsx index fbd28b6b4b6..9a52f7f5f71 100644 --- a/frontend/public/components/secrets/create-secret/OpaqueSecretFormEntry.tsx +++ b/frontend/public/components/secrets/create-secret/OpaqueSecretFormEntry.tsx @@ -51,18 +51,16 @@ export const OpaqueSecretFormEntry: React.FC = ({
-
- -
+
); diff --git a/frontend/public/components/secrets/create-secret/types.ts b/frontend/public/components/secrets/create-secret/types.ts index 15eca636ac7..578e386259e 100644 --- a/frontend/public/components/secrets/create-secret/types.ts +++ b/frontend/public/components/secrets/create-secret/types.ts @@ -25,9 +25,8 @@ export type SecretSubFormProps = { base64StringData?: Base64StringData; }; -type Base64String = string; -export type SecretStringData = { [key: string]: string }; -export type Base64StringData = { [key: string]: Base64String }; +export type SecretStringData = Record; +export type Base64StringData = Record; export type SecretChangeData = { stringData?: SecretStringData; @@ -55,5 +54,5 @@ export type OpaqueDataEntry = { export type OpaqueSecretFormEntryProps = { entry: OpaqueDataEntry; index: number; - onChange: Function; + onChange: (entry: OpaqueDataEntry, index: number) => void; }; diff --git a/frontend/public/components/secrets/create-secret/utils.ts b/frontend/public/components/secrets/create-secret/utils.ts index 29eada310ec..3a5e7ff007f 100644 --- a/frontend/public/components/secrets/create-secret/utils.ts +++ b/frontend/public/components/secrets/create-secret/utils.ts @@ -187,8 +187,10 @@ type DockerCfg = { type PullSecretData = DockerCfg | DockerConfigJSON; -export const opaqueEntriesToObject = (opaqueEntriesArray: OpaqueDataEntry[]): SecretChangeData => { - return (opaqueEntriesArray ?? []).reduce( +export const opaqueEntriesToObject = ( + opaqueEntriesArray: OpaqueDataEntry[] = [], +): SecretChangeData => { + return opaqueEntriesArray.reduce( (acc, { key, value }) => { return { base64StringData: { ...acc.base64StringData, [key]: value }, @@ -213,7 +215,7 @@ export const opaqueSecretObjectToArray = ( if (_.isEmpty(base64StringData)) { return [newOpaqueSecretEntry()]; } - return _.map(base64StringData, (value, key) => { + return Object.entries(base64StringData).map(([key, value]) => { return { key, value, From b6eb796b1e14e5ae49014e75268d88494fe65a25 Mon Sep 17 00:00:00 2001 From: Gabriel Bernal Date: Fri, 7 Feb 2025 13:31:13 +0100 Subject: [PATCH 010/618] add missing contextId to be able to inject tabs from plugins --- .../src/extensions/console-types.ts | 1 + frontend/public/components/utils/horizontal-nav.tsx | 11 ++++++++++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/frontend/packages/console-dynamic-plugin-sdk/src/extensions/console-types.ts b/frontend/packages/console-dynamic-plugin-sdk/src/extensions/console-types.ts index 6c38cf92f90..6d04468af40 100644 --- a/frontend/packages/console-dynamic-plugin-sdk/src/extensions/console-types.ts +++ b/frontend/packages/console-dynamic-plugin-sdk/src/extensions/console-types.ts @@ -291,6 +291,7 @@ export type HorizontalNavProps = { resource?: K8sResourceCommon; pages: NavPage[]; customData?: object; + contextId?: string; }; export type TableColumn = ICell & { diff --git a/frontend/public/components/utils/horizontal-nav.tsx b/frontend/public/components/utils/horizontal-nav.tsx index b0aff1fd3f2..525214c64e3 100644 --- a/frontend/public/components/utils/horizontal-nav.tsx +++ b/frontend/public/components/utils/horizontal-nav.tsx @@ -377,10 +377,19 @@ export const HorizontalNavFacade: React.FC = ({ resource, pages, customData, + contextId, }) => { const obj = { data: resource, loaded: true }; - return ; + return ( + + ); }; export type PodsComponentProps = { From afc30de390408f77cacff16147c0e255f9c74c5a Mon Sep 17 00:00:00 2001 From: Vikram Raj Date: Mon, 27 Jan 2025 20:36:14 +0530 Subject: [PATCH 011/618] Store Topology sidebar alert in the localStorage --- .../components/side-bar/components/SideBarAlerts.tsx | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/frontend/packages/topology/src/components/side-bar/components/SideBarAlerts.tsx b/frontend/packages/topology/src/components/side-bar/components/SideBarAlerts.tsx index 401114354cb..256ef4d31f4 100644 --- a/frontend/packages/topology/src/components/side-bar/components/SideBarAlerts.tsx +++ b/frontend/packages/topology/src/components/side-bar/components/SideBarAlerts.tsx @@ -8,6 +8,7 @@ import { useResolvedExtensions, } from '@console/dynamic-plugin-sdk'; import { USERSETTINGS_PREFIX, useUserSettings } from '@console/shared'; +import { useUserSettingsLocalStorage } from '@console/shared/src/hooks/useUserSettingsLocalStorage'; const SIDEBAR_ALERTS = 'sideBarAlerts'; @@ -16,14 +17,19 @@ const ResolveResourceAlerts: React.FC<{ useResourceAlertsContent?: (element: GraphElement) => DetailsResourceAlertContent; element: GraphElement; }> = observer(function ResolveResourceAlerts({ id, useResourceAlertsContent, element }) { - const [showAlert, setShowAlert, loaded] = useUserSettings( + const [showAlertFromConfigMap, , loaded] = useUserSettings( `${USERSETTINGS_PREFIX}.${SIDEBAR_ALERTS}.${id}.${element.getId()}`, true, ); + const [showAlert, setShowAlert] = useUserSettingsLocalStorage( + `${USERSETTINGS_PREFIX}/${SIDEBAR_ALERTS}/${id}`, + `${element.getId()}`, + true, + ); const alertConfigs = useResourceAlertsContent(element); if (!alertConfigs) return null; const { variant, content, actionLinks, dismissible, title } = alertConfigs; - return loaded && showAlert ? ( + return showAlert || (showAlertFromConfigMap && loaded) ? ( Date: Tue, 11 Feb 2025 17:50:37 +0530 Subject: [PATCH 012/618] set default as set in userSetting configMap --- .../src/hooks/useGetUserSettingConfigMap.ts | 47 +++++++++++++++++++ .../side-bar/components/SideBarAlerts.tsx | 25 +++++++--- 2 files changed, 65 insertions(+), 7 deletions(-) create mode 100644 frontend/packages/console-shared/src/hooks/useGetUserSettingConfigMap.ts diff --git a/frontend/packages/console-shared/src/hooks/useGetUserSettingConfigMap.ts b/frontend/packages/console-shared/src/hooks/useGetUserSettingConfigMap.ts new file mode 100644 index 00000000000..9083844a039 --- /dev/null +++ b/frontend/packages/console-shared/src/hooks/useGetUserSettingConfigMap.ts @@ -0,0 +1,47 @@ +import { createHash } from 'crypto'; +import * as React from 'react'; +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: FIXME out-of-sync @types/react-redux version as new types cause many build errors +import { useSelector } from 'react-redux'; +import { getImpersonate, getUser, K8sResourceKind } from '@console/dynamic-plugin-sdk/src'; +import { useK8sWatchResource } from '@console/internal/components/utils/k8s-watch-hook'; +import { ConfigMapModel } from '@console/internal/models'; +import { RootState } from '@console/internal/redux'; +import { USER_SETTING_CONFIGMAP_NAMESPACE } from '../utils/user-settings'; + +export const useGetUserSettingConfigMap = () => { + const hashNameOrKubeadmin = (name: string): string | null => { + if (!name) { + return null; + } + + if (name === 'kube:admin') { + return 'kubeadmin'; + } + const hash = createHash('sha256'); + hash.update(name); + return hash.digest('hex'); + }; + // User and impersonate + const userUid = useSelector((state: RootState) => { + const impersonateName = getImpersonate(state)?.name; + const { uid, username } = getUser(state) ?? {}; + const hashName = hashNameOrKubeadmin(username); + return impersonateName || uid || hashName || ''; + }); + + const configMapResource = React.useMemo( + () => + !userUid + ? null + : { + kind: ConfigMapModel.kind, + namespace: USER_SETTING_CONFIGMAP_NAMESPACE, + isList: false, + name: `user-settings-${userUid}`, + }, + [userUid], + ); + const [cfData, cfLoaded, cfLoadError] = useK8sWatchResource(configMapResource); + return [cfData, cfLoaded, cfLoadError]; +}; diff --git a/frontend/packages/topology/src/components/side-bar/components/SideBarAlerts.tsx b/frontend/packages/topology/src/components/side-bar/components/SideBarAlerts.tsx index 256ef4d31f4..581b1c04569 100644 --- a/frontend/packages/topology/src/components/side-bar/components/SideBarAlerts.tsx +++ b/frontend/packages/topology/src/components/side-bar/components/SideBarAlerts.tsx @@ -7,8 +7,10 @@ import { isDetailsResourceAlert, useResolvedExtensions, } from '@console/dynamic-plugin-sdk'; -import { USERSETTINGS_PREFIX, useUserSettings } from '@console/shared'; +import { USERSETTINGS_PREFIX } from '@console/shared'; +import { useGetUserSettingConfigMap } from '@console/shared/src/hooks/useGetUserSettingConfigMap'; import { useUserSettingsLocalStorage } from '@console/shared/src/hooks/useUserSettingsLocalStorage'; +import { deseralizeData } from '@console/shared/src/utils/user-settings'; const SIDEBAR_ALERTS = 'sideBarAlerts'; @@ -17,19 +19,28 @@ const ResolveResourceAlerts: React.FC<{ useResourceAlertsContent?: (element: GraphElement) => DetailsResourceAlertContent; element: GraphElement; }> = observer(function ResolveResourceAlerts({ id, useResourceAlertsContent, element }) { - const [showAlertFromConfigMap, , loaded] = useUserSettings( - `${USERSETTINGS_PREFIX}.${SIDEBAR_ALERTS}.${id}.${element.getId()}`, - true, - ); + const [cfData, cfLoaded, cfLoadError] = useGetUserSettingConfigMap(); const [showAlert, setShowAlert] = useUserSettingsLocalStorage( `${USERSETTINGS_PREFIX}/${SIDEBAR_ALERTS}/${id}`, `${element.getId()}`, - true, + deseralizeData( + cfData?.data?.[`${USERSETTINGS_PREFIX}.${SIDEBAR_ALERTS}.${id}.${element.getId()}`], + ) || true, ); + + React.useEffect(() => { + if (cfData && cfLoaded && !cfLoadError) { + const alertSetting = deseralizeData( + cfData?.data?.[`${USERSETTINGS_PREFIX}.${SIDEBAR_ALERTS}.${id}.${element.getId()}`], + ); + setShowAlert(alertSetting); + } + }, [setShowAlert, cfData, cfLoaded, cfLoadError, id, element]); + const alertConfigs = useResourceAlertsContent(element); if (!alertConfigs) return null; const { variant, content, actionLinks, dismissible, title } = alertConfigs; - return showAlert || (showAlertFromConfigMap && loaded) ? ( + return showAlert ? ( Date: Mon, 10 Feb 2025 16:14:52 -0500 Subject: [PATCH 013/618] CONSOLE-4476: Use upstream `DefaultGroup` and remove workaround --- .../components/groups/Application.tsx | 11 +- .../groups/ApplicationGroupExpanded.tsx | 241 ------------------ 2 files changed, 5 insertions(+), 247 deletions(-) delete mode 100644 frontend/packages/topology/src/components/graph-view/components/groups/ApplicationGroupExpanded.tsx diff --git a/frontend/packages/topology/src/components/graph-view/components/groups/Application.tsx b/frontend/packages/topology/src/components/graph-view/components/groups/Application.tsx index 8c3e48c3ce7..3ca003ad565 100644 --- a/frontend/packages/topology/src/components/graph-view/components/groups/Application.tsx +++ b/frontend/packages/topology/src/components/graph-view/components/groups/Application.tsx @@ -1,13 +1,14 @@ import * as React from 'react'; import { + DefaultGroup, Node, - observer, + WithContextMenuProps, WithDndDropProps, WithDragNodeProps, WithSelectionProps, - WithContextMenuProps, - useHover, + observer, useCombineRefs, + useHover, } from '@patternfly/react-topology'; import classNames from 'classnames'; import { useSearchFilter } from '../../../../filters'; @@ -16,7 +17,6 @@ import { ApplicationModel } from '../../../../models'; import { SHOW_GROUPING_HINT_EVENT } from '../../../../topology-types'; import { getKindStringAndAbbreviation } from '../nodes/nodeUtils'; import RegroupHint from '../RegroupHint'; -import ApplicationGroupExpanded from './ApplicationGroupExpanded'; import GroupNode from './GroupNode'; import './Application.scss'; @@ -80,9 +80,8 @@ const Application: React.FC = ({ ); } - // Use local version of DefaultGroupExpanded until we have a fix for https://github.com/patternfly/patternfly-react/issues/7300 return ( - ; - -type PointWithSize = [number, number, number]; - -// Return the point whose Y is the largest value. -// If multiple points are found, compute the center X between them -// export for testing only -export function computeLabelLocation(points: PointWithSize[]): PointWithSize { - let lowPoints: PointWithSize[]; - const threshold = 5; - - _.forEach(points, (p) => { - const delta = !lowPoints ? Infinity : Math.round(p[1]) - Math.round(lowPoints[0][1]); - if (delta > threshold) { - lowPoints = [p]; - } else if (Math.abs(delta) <= threshold) { - lowPoints.push(p); - } - }); - return [ - (_.minBy(lowPoints, (p) => p[0])[0] + _.maxBy(lowPoints, (p) => p[0])[0]) / 2, - lowPoints[0][1], - // use the max size value - _.maxBy(lowPoints, (p) => p[2])[2], - ]; -} - -const ApplicationGroupExpanded: React.FunctionComponent = ({ - className, - element, - collapsible, - selected, - onSelect, - hover, - label, - secondaryLabel, - showLabel = true, - truncateLength, - dndDropRef, - droppable, - canDrop, - dropTarget, - onContextMenu, - contextMenuOpen, - dragging, - dragNodeRef, - badge, - badgeColor, - badgeTextColor, - badgeBorderColor, - badgeClassName, - badgeLocation, - labelIconClass, - labelIcon, - labelIconPadding, - onCollapseChange, -}) => { - const [hovered, hoverRef] = useHover(); - const [labelHover, labelHoverRef] = useHover(); - const dragLabelRef = useDragNode()[1]; - const refs = useCombineRefs(hoverRef, dragNodeRef); - const isHover = hover !== undefined ? hover : hovered; - const anchorRef = useSvgAnchor(); - const outlineRef = useCombineRefs(dndDropRef, anchorRef); - const labelLocation = React.useRef(); - const pathRef = React.useRef(); - - let parent = element.getParent(); - let altGroup = false; - while (!isGraph(parent)) { - altGroup = !altGroup; - parent = parent.getParent(); - } - - // cast to number and coerce - const padding = maxPadding(element.getStyle().padding); - const hullPadding = (point: PointWithSize | PointTuple) => (point[2] || 0) + padding; - - if (!droppable || !pathRef.current || !labelLocation.current) { - const children = element.getNodes().filter((c) => c.isVisible()); - if (children.length === 0) { - return null; - } - const points: (PointWithSize | PointTuple)[] = []; - _.forEach(children, (c) => { - if (c.getNodeShape() === NodeShape.circle) { - const bounds = c.getBounds(); - const { width, height } = bounds; - const { x, y } = bounds.getCenter(); - const radius = Math.max(width, height) / 2; - points.push([x, y, radius] as PointWithSize); - } else { - // add all 4 corners - const { width, height, x, y } = c.getBounds(); - points.push([x, y, 0] as PointWithSize); - points.push([x + width, y, 0] as PointWithSize); - points.push([x, y + height, 0] as PointWithSize); - points.push([x + width, y + height, 0] as PointWithSize); - } - }); - const hullPoints: (PointWithSize | PointTuple)[] = - points.length > 2 ? polygonHull(points as PointTuple[]) : (points as PointTuple[]); - if (!hullPoints) { - return null; - } - - // change the box only when not dragging - pathRef.current = hullPath(hullPoints as PointTuple[], hullPadding); - - // Compute the location of the group label. - labelLocation.current = computeLabelLocation(hullPoints as PointWithSize[]); - } - - const groupClassName = css( - styles.topologyGroup, - className, - altGroup && 'pf-m-alt-group', - canDrop && 'pf-m-highlight', - dragging && 'pf-m-dragging', - selected && 'pf-m-selected', - ); - const innerGroupClassName = css( - styles.topologyGroup, - className, - altGroup && 'pf-m-alt-group', - canDrop && 'pf-m-highlight', - dragging && 'pf-m-dragging', - selected && 'pf-m-selected', - (isHover || labelHover) && 'pf-m-hover', - canDrop && dropTarget && 'pf-m-drop-target', - ); - - return ( - - - - - - - {showLabel && ( - : undefined} - onActionIconClick={() => onCollapseChange(element, true)} - > - {label || element.getLabel()} - - )} - - ); -}; - -export default observer(ApplicationGroupExpanded); From 633c6f0f37b5d79690654b12300bb54c43fe9dc8 Mon Sep 17 00:00:00 2001 From: logonoff Date: Mon, 10 Feb 2025 16:15:14 -0500 Subject: [PATCH 014/618] CONSOLE-4476: Remove dummyProps in CatalogFilters --- .../src/components/catalog/catalog-view/CatalogFilters.tsx | 3 --- 1 file changed, 3 deletions(-) diff --git a/frontend/packages/console-shared/src/components/catalog/catalog-view/CatalogFilters.tsx b/frontend/packages/console-shared/src/components/catalog/catalog-view/CatalogFilters.tsx index 273ca008193..4f54fd5ae77 100644 --- a/frontend/packages/console-shared/src/components/catalog/catalog-view/CatalogFilters.tsx +++ b/frontend/packages/console-shared/src/components/catalog/catalog-view/CatalogFilters.tsx @@ -41,8 +41,6 @@ const CatalogFilters: React.FC = ({ const renderFilterItem = (filter: CatalogFilterItem, filterName: string, groupName: string) => { const { label, active } = filter; const count = filterGroupCounts[groupName]?.[filterName] ?? 0; - // TODO remove when adopting https://github.com/patternfly/patternfly-react/issues/5139 - const dummyProps = {} as any; return ( = ({ onFilterChange(groupName, filterName, e.target.checked) } data-test={`${groupName}-${_.kebabCase(filterName)}`} - {...dummyProps} > {label} From fdf3b86432b1d508645746a22393182b27889582 Mon Sep 17 00:00:00 2001 From: logonoff Date: Mon, 10 Feb 2025 16:15:57 -0500 Subject: [PATCH 015/618] CONSOLE-4476: Remove comment in NamespaceDropdown bug has been fixed and I verified that the Create project button in the footer can be accessed through keyboard --- .../src/components/namespace/NamespaceDropdown.tsx | 3 --- 1 file changed, 3 deletions(-) diff --git a/frontend/packages/console-shared/src/components/namespace/NamespaceDropdown.tsx b/frontend/packages/console-shared/src/components/namespace/NamespaceDropdown.tsx index 3e477ccdb82..e98e14e6662 100644 --- a/frontend/packages/console-shared/src/components/namespace/NamespaceDropdown.tsx +++ b/frontend/packages/console-shared/src/components/namespace/NamespaceDropdown.tsx @@ -179,9 +179,6 @@ export const NamespaceGroup: React.FC<{ /* ****************************************** */ -// The items in the footer are not accessible via the keyboard. -// This is being tracked in: https://github.com/patternfly/patternfly-react/issues/6031 - export const Footer: React.FC<{ canCreateNew: boolean; isProject?: boolean; From fa8223b9d36e873ddb5211a0750cb3ded0c6d575 Mon Sep 17 00:00:00 2001 From: logonoff Date: Mon, 10 Feb 2025 16:43:33 -0500 Subject: [PATCH 016/618] CONSOLE-4476: Remove workaround for `MenuList` I can no longer observe a runtime error when opening this menu up, so I think it's safe to remove --- .../dropdown-with-switch/DropdownWithSwitchMenu.tsx | 8 -------- 1 file changed, 8 deletions(-) diff --git a/frontend/packages/console-shared/src/components/dropdown/dropdown-with-switch/DropdownWithSwitchMenu.tsx b/frontend/packages/console-shared/src/components/dropdown/dropdown-with-switch/DropdownWithSwitchMenu.tsx index 6600c977c06..63c2eaf38a4 100644 --- a/frontend/packages/console-shared/src/components/dropdown/dropdown-with-switch/DropdownWithSwitchMenu.tsx +++ b/frontend/packages/console-shared/src/components/dropdown/dropdown-with-switch/DropdownWithSwitchMenu.tsx @@ -4,8 +4,6 @@ import { Menu, MenuContent, MenuSearch, - MenuItem, - MenuList, Switch, MenuSearchInput, } from '@patternfly/react-core'; @@ -50,12 +48,6 @@ const DropdownWithSwitchMenu: React.FC = ({ - {/* PatternFly expects Menu to contain a MenuList with a MenuItem - see https://github.com/patternfly/patternfly-react/issues/7365 - hack to workaround this bug by adding a hidden MenuList */} - - - From 0492f8e4c724101d508eb16a92b80bd2f2a27a91 Mon Sep 17 00:00:00 2001 From: logonoff Date: Mon, 10 Feb 2025 19:02:34 -0500 Subject: [PATCH 017/618] CONSOLE-4476: Remove workaround in QueryBrowser --- .../src/components/query-browser/QueryBrowser.tsx | 3 --- 1 file changed, 3 deletions(-) diff --git a/frontend/packages/console-shared/src/components/query-browser/QueryBrowser.tsx b/frontend/packages/console-shared/src/components/query-browser/QueryBrowser.tsx index a67f8c61c65..a605548aff9 100644 --- a/frontend/packages/console-shared/src/components/query-browser/QueryBrowser.tsx +++ b/frontend/packages/console-shared/src/components/query-browser/QueryBrowser.tsx @@ -328,10 +328,7 @@ const TooltipWrapped: React.FC = ({ const Tooltip = withFallback(TooltipWrapped); const graphContainer = ( - // Set activateData to false to work around VictoryVoronoiContainer crash (see - // https://github.com/FormidableLabs/victory/issues/1314) } labels={() => ' '} mouseFollowTooltips From cb2786b5dedcf1b51bd44fcec06b806292d6c54c Mon Sep 17 00:00:00 2001 From: logonoff Date: Tue, 11 Feb 2025 08:25:54 -0500 Subject: [PATCH 018/618] CONSOLE-4476: Remove workaround in NamespaceDropdown --- .../components/namespace/NamespaceDropdown.scss | 17 ----------------- .../components/namespace/NamespaceDropdown.tsx | 3 ++- 2 files changed, 2 insertions(+), 18 deletions(-) diff --git a/frontend/packages/console-shared/src/components/namespace/NamespaceDropdown.scss b/frontend/packages/console-shared/src/components/namespace/NamespaceDropdown.scss index 87630754155..2c59903ce34 100644 --- a/frontend/packages/console-shared/src/components/namespace/NamespaceDropdown.scss +++ b/frontend/packages/console-shared/src/components/namespace/NamespaceDropdown.scss @@ -56,20 +56,3 @@ color: var(--pf-t--global--text--color--disabled); } } - -// The PatternFly switch is displayed to the left of the label -// The design for the namespace/project drop down has the switch to -// the right of the label. These styles allow for the variance to the -// normal PatternFly display. -// Tracked in https://github.com/patternfly/patternfly-react/issues/6059 - -.co-namespace-dropdown__switch { - .pf-v6-c-switch__label { - padding-right: 60px; // Ensure switch doesn't land on top of text - } - - .pf-v6-c-switch__toggle { - position: absolute; - right: 0; - } -} diff --git a/frontend/packages/console-shared/src/components/namespace/NamespaceDropdown.tsx b/frontend/packages/console-shared/src/components/namespace/NamespaceDropdown.tsx index e98e14e6662..b2066c4544b 100644 --- a/frontend/packages/console-shared/src/components/namespace/NamespaceDropdown.tsx +++ b/frontend/packages/console-shared/src/components/namespace/NamespaceDropdown.tsx @@ -129,7 +129,8 @@ const SystemSwitch: React.FC<{ } isChecked={isChecked} onChange={(_, value) => onChange(value)} - className="pf-v6-c-select__menu-item pf-m-action co-namespace-dropdown__switch" + className="pf-v6-c-select__menu-item pf-m-action" + isReversed /> From aedd6024143241a10b8ebcd32d6394e733e3e64b Mon Sep 17 00:00:00 2001 From: logonoff Date: Tue, 11 Feb 2025 08:40:10 -0500 Subject: [PATCH 019/618] CONSOLE-4476: Remove workaround in about modal ![before](https://i.imgur.com/XBSwrdW.png) ![after](https://i.imgur.com/P9LWnQG.png) --- frontend/public/components/about-modal.tsx | 2 +- frontend/public/style/_common.scss | 8 -------- 2 files changed, 1 insertion(+), 9 deletions(-) diff --git a/frontend/public/components/about-modal.tsx b/frontend/public/components/about-modal.tsx index 7068001ecdb..69dd5c0236a 100644 --- a/frontend/public/components/about-modal.tsx +++ b/frontend/public/components/about-modal.tsx @@ -53,7 +53,7 @@ const DynamicPlugins: React.FC = () => { }, [pluginInfoEntries]); return items.length > 0 ? ( - + {items} ) : ( diff --git a/frontend/public/style/_common.scss b/frontend/public/style/_common.scss index 6ac87de22d0..351d619fefc 100644 --- a/frontend/public/style/_common.scss +++ b/frontend/public/style/_common.scss @@ -341,14 +341,6 @@ dl.co-inline { padding: 0 5px; } -// PatternFly's TextList lacks a plain variant -// see https://github.com/patternfly/patternfly-react/issues/8434 -.co-text-list-plain { - list-style: none !important; - margin-left: 0 !important; - padding-left: 0 !important; -} - .co-toolbar { align-items: stretch; display: flex; From 071417a87cc946cb858b71af5ddba0f1ab9ab3e5 Mon Sep 17 00:00:00 2001 From: logonoff Date: Tue, 11 Feb 2025 08:42:38 -0500 Subject: [PATCH 020/618] CONSOLE-4476: Remove Card CSS override --- frontend/public/style/_overrides.scss | 5 ----- 1 file changed, 5 deletions(-) diff --git a/frontend/public/style/_overrides.scss b/frontend/public/style/_overrides.scss index 93be6a27f7a..53a5f437e20 100644 --- a/frontend/public/style/_overrides.scss +++ b/frontend/public/style/_overrides.scss @@ -237,11 +237,6 @@ form.pf-v6-c-form { } } -// Remove when upstream issue is addressed. https://github.com/patternfly/patternfly/issues/4889 -.pf-v6-theme-dark .pf-v6-c-card { - --pf-v6-c-card--BoxShadow: var(--pf-t--global--box-shadow--md); -} - .pf-v6-c-wizard__nav-list > ul { list-style: none; padding-left: 0; From 7fdd9d0d1beced57a0d455ac387574e5db719574 Mon Sep 17 00:00:00 2001 From: cyril-ui-developer Date: Mon, 10 Feb 2025 11:59:28 -0500 Subject: [PATCH 021/618] Workloads-DeploymentConfigs-Add storage: i18n misses --- .../public/components/container-selector.tsx | 36 +++++++++++-------- frontend/public/locales/en/public.json | 1 + 2 files changed, 22 insertions(+), 15 deletions(-) diff --git a/frontend/public/components/container-selector.tsx b/frontend/public/components/container-selector.tsx index 7519f25e694..827f7216cc9 100644 --- a/frontend/public/components/container-selector.tsx +++ b/frontend/public/components/container-selector.tsx @@ -1,4 +1,5 @@ import * as React from 'react'; +import { useTranslation } from 'react-i18next'; import { ContainerSpec } from '../module/k8s'; import { Checkbox } from '@patternfly/react-core'; @@ -7,21 +8,26 @@ export const ContainerSelector: React.FC = ({ containers, onChange, selected, -}) => ( -
- {containers.map((container: ContainerSpec) => ( - - ))} -
-); - +}) => { + const { t } = useTranslation(); + return ( +
+ {containers.map((container: ContainerSpec) => ( + + ))} +
+ ); +}; export type ContainerSelectorProps = { containers: ContainerSpec[]; onChange: (event: React.FormEvent, checked: boolean) => void; diff --git a/frontend/public/locales/en/public.json b/frontend/public/locales/en/public.json index 9aab8b34c98..47bf407a4bb 100644 --- a/frontend/public/locales/en/public.json +++ b/frontend/public/locales/en/public.json @@ -310,6 +310,7 @@ "BinaryData contains the binary data that is not in UTF-8 range": "BinaryData contains the binary data that is not in UTF-8 range", "Create ConfigMap": "Create ConfigMap", "Edit ConfigMap": "Edit ConfigMap", + "{{containerName}} from image {{containerImage}}": "{{containerName}} from image {{containerImage}}", "PostStart: {{postStartLabel}} <3>{{postStart}}": "PostStart: {{postStartLabel}} <3>{{postStart}}", "PreStop: {{preStopLabel}} <3>{{preStop}}": "PreStop: {{preStopLabel}} <3>{{preStop}}", "No ports have been exposed": "No ports have been exposed", From 631278f0cfb76e02d6b2810f62e02837116bfd54 Mon Sep 17 00:00:00 2001 From: Robb Hamilton Date: Thu, 13 Feb 2025 09:03:09 -0500 Subject: [PATCH 022/618] OCPBUGS-50665: fix bug where Demo Plugin tab url is always appended --- dynamic-demo-plugin/console-extensions.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dynamic-demo-plugin/console-extensions.json b/dynamic-demo-plugin/console-extensions.json index 4f124ac050c..9113537d084 100644 --- a/dynamic-demo-plugin/console-extensions.json +++ b/dynamic-demo-plugin/console-extensions.json @@ -196,7 +196,7 @@ }, "page": { "name": "Demo Plugin", - "href": "/demo-plugin" + "href": "demo-plugin" }, "component": { "$codeRef": "projectTabContent" } } From 480264ff9f6a200fe005f51e4712f709513d249a Mon Sep 17 00:00:00 2001 From: logonoff Date: Fri, 20 Dec 2024 13:33:54 -0500 Subject: [PATCH 023/618] CONSOLE-4407: Update monaco and YAML language server Remove `umd-compat-loader` --- frontend/package.json | 12 +- .../src/extensions/console-types.ts | 3 +- .../src/components/editor/CodeEditor.tsx | 16 +- .../components/editor/CodeEditorSidebar.tsx | 3 +- .../src/components/editor/theme.ts | 33 --- .../components/editor/yaml-editor-utils.ts | 248 ++++------------ .../integration-tests/support/pages/app.ts | 1 + .../buildconfig/sections/EditorField.tsx | 1 - frontend/public/components/edit-yaml.jsx | 1 - frontend/webpack.config.ts | 18 +- frontend/yarn.lock | 273 ++++++++---------- 11 files changed, 198 insertions(+), 411 deletions(-) delete mode 100644 frontend/packages/console-shared/src/components/editor/theme.ts diff --git a/frontend/package.json b/frontend/package.json index e38c942812d..55b12cbcbc3 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -132,6 +132,7 @@ "resolver": "./jest-resolver.js" }, "dependencies": { + "@monaco-editor/react": "^4.6.0", "@patternfly-5/patternfly": "npm:@patternfly/patternfly@5.4.2", "@patternfly/patternfly": "^6.2.0-prerelease.2", "@patternfly/quickstarts": "^6.2.0-prerelease.4", @@ -180,7 +181,7 @@ "js-yaml": "^3.13.1", "json-schema": "^0.3.0", "lodash-es": "^4.17.21", - "monaco-languageclient": "^0.13.0", + "monaco-yaml": "^5.2.3", "murmurhash-js": "1.0.x", "node-polyfill-webpack-plugin": "^4.0.0", "pluralize": "^8.0.0", @@ -197,7 +198,7 @@ "react-linkify": "^0.2.2", "react-measure": "^2.2.6", "react-modal": "^3.12.1", - "react-monaco-editor": "0.46.x", + "react-monaco-editor": "^0.56.2", "react-redux": "7.2.2", "react-router": "5.3.x", "react-router-dom": "5.3.x", @@ -219,12 +220,10 @@ "typesafe-actions": "^4.2.1", "url-search-params-polyfill": "2.x", "victory": "^37.3.6", - "vscode-languageserver-types": "^3.10.0", "whatwg-fetch": "2.x", "xterm": "^4.10.0", "xterm-addon-attach": "0.6.0", "xterm-addon-fit": "0.5.0", - "yaml-language-server": "0.13.0", "yup": "^0.27.0" }, "devDependencies": { @@ -305,8 +304,8 @@ "mochawesome-merge": "^4.1.0", "mochawesome-report-generator": "^5.1.0", "mock-socket": "^9.0.3", - "monaco-editor": "^0.28.1", - "monaco-editor-webpack-plugin": "^4.2.0", + "monaco-editor": "^0.36.1", + "monaco-editor-webpack-plugin": "^7.1.0", "prettier": "2.0.5", "react-refresh": "^0.10.0", "read-pkg": "5.x", @@ -319,7 +318,6 @@ "ts-jest": "21.x", "ts-node": "10.9.2", "typescript": "5.7.2", - "umd-compat-loader": "^2.1.2", "val-loader": "^6.0.0", "webpack": "^5.75.0", "webpack-bundle-analyzer": "4.10.2", diff --git a/frontend/packages/console-dynamic-plugin-sdk/src/extensions/console-types.ts b/frontend/packages/console-dynamic-plugin-sdk/src/extensions/console-types.ts index 5848425dab8..f9f361e04a7 100644 --- a/frontend/packages/console-dynamic-plugin-sdk/src/extensions/console-types.ts +++ b/frontend/packages/console-dynamic-plugin-sdk/src/extensions/console-types.ts @@ -3,7 +3,6 @@ import { QuickStartContextValues } from '@patternfly/quickstarts'; import { ButtonProps } from '@patternfly/react-core'; import { ICell, OnSelect, SortByDirection, TableGridBreakpoint } from '@patternfly/react-table'; import { LocationDescriptor } from 'history'; -import MonacoEditor from 'react-monaco-editor/lib/editor'; import { ExtensionK8sGroupKindModel, K8sModel, @@ -647,7 +646,7 @@ export type CodeEditorProps = { }; export type CodeEditorRef = { - editor?: MonacoEditor['editor']; + editor?: any; }; export type ResourceYAMLEditorProps = { diff --git a/frontend/packages/console-shared/src/components/editor/CodeEditor.tsx b/frontend/packages/console-shared/src/components/editor/CodeEditor.tsx index bbe9d8b4c9f..c0223859688 100644 --- a/frontend/packages/console-shared/src/components/editor/CodeEditor.tsx +++ b/frontend/packages/console-shared/src/components/editor/CodeEditor.tsx @@ -1,14 +1,13 @@ import * as React from 'react'; import Measure from 'react-measure'; -import MonacoEditor from 'react-monaco-editor'; +import MonacoEditor, { EditorDidMount } from 'react-monaco-editor'; import { CodeEditorProps } from '@console/dynamic-plugin-sdk'; import { ThemeContext } from '@console/internal/components/ThemeProvider'; import CodeEditorToolbar from './CodeEditorToolbar'; import { registerYAMLinMonaco, defaultEditorOptions } from './yaml-editor-utils'; -import './theme'; import './CodeEditor.scss'; -const CodeEditor = React.forwardRef((props, ref) => { +const CodeEditor = React.forwardRef((props, ref) => { const { value, options = defaultEditorOptions, @@ -23,9 +22,9 @@ const CodeEditor = React.forwardRef((props, ref) const theme = React.useContext(ThemeContext); const [usesValue] = React.useState(value !== undefined); - const editorDidMount = React.useCallback( + const editorDidMount: EditorDidMount = React.useCallback( (editor, monaco) => { - const currentLanguage = editor.getModel()?.getModeId(); + const currentLanguage = editor.getModel()?.getLanguageId(); editor.layout(); editor.focus(); switch (currentLanguage) { @@ -39,7 +38,7 @@ const CodeEditor = React.forwardRef((props, ref) break; } monaco.editor.getModels()[0]?.updateOptions({ tabSize: 2 }); - onSave && editor.addCommand(monaco.KeyMod.CtrlCmd | monaco.KeyCode.KEY_S, onSave); // eslint-disable-line no-bitwise + onSave && editor.addCommand(monaco.KeyMod.CtrlCmd | monaco.KeyCode.KeyS, onSave); // eslint-disable-line no-bitwise }, [onSave, usesValue], ); @@ -59,11 +58,10 @@ const CodeEditor = React.forwardRef((props, ref) {({ measureRef, contentRect }) => (
-
+
; + editorRef: React.MutableRefObject; model?: K8sKind; samples?: Sample[]; schema?: JSONSchema7; diff --git a/frontend/packages/console-shared/src/components/editor/theme.ts b/frontend/packages/console-shared/src/components/editor/theme.ts deleted file mode 100644 index b0c41f490af..00000000000 --- a/frontend/packages/console-shared/src/components/editor/theme.ts +++ /dev/null @@ -1,33 +0,0 @@ -(window as any).monaco.editor.defineTheme('console-light', { - base: 'vs', - inherit: true, - colors: { - 'editor.background': '#fff', - 'editorGutter.background': '#f5f5f5', // black-150 - 'editorLineNumber.activeForeground': '#151515', - 'editorLineNumber.foreground': '#151515', - }, - rules: [ - { token: 'number', foreground: '486b00' }, // light-green-600 - { token: 'type', foreground: '795600' }, // gold-500 - { token: 'string', foreground: '004080' }, // blue-600 - { token: 'keyword', foreground: '40199a' }, // purple-600 - ], -}); - -(window as any).monaco.editor.defineTheme('console-dark', { - base: 'vs-dark', - inherit: true, - colors: { - 'editor.background': '#151515', - 'editorGutter.background': '#292e34', // no pf token defined - 'editorLineNumber.activeForeground': '#fff', - 'editorLineNumber.foreground': '#f0f0f0', - }, - rules: [ - { token: 'number', foreground: 'ace12e' }, // light-green-600 - { token: 'type', foreground: '73bcf7' }, // blue-200 - { token: 'string', foreground: 'f0ab00' }, // gold-400 - { token: 'keyword', foreground: 'cbc1ff' }, // purple-100 - ], -}); diff --git a/frontend/packages/console-shared/src/components/editor/yaml-editor-utils.ts b/frontend/packages/console-shared/src/components/editor/yaml-editor-utils.ts index fdc7645e917..0e4e6794f90 100644 --- a/frontend/packages/console-shared/src/components/editor/yaml-editor-utils.ts +++ b/frontend/packages/console-shared/src/components/editor/yaml-editor-utils.ts @@ -1,131 +1,15 @@ -import * as URL from 'url'; -import { Uri, Range } from 'monaco-editor'; -import { - MonacoToProtocolConverter, - ProtocolToMonacoConverter, -} from 'monaco-languageclient/lib/monaco-converter'; +import { Range } from 'monaco-editor'; +import * as monaco from 'monaco-editor/esm/vs/editor/editor.api'; +import { configureMonacoYaml } from 'monaco-yaml'; import * as yaml from 'yaml-ast-parser'; -import { getLanguageService, TextDocument } from 'yaml-language-server'; import { openAPItoJSONSchema } from '@console/internal/module/k8s/openapi-to-json-schema'; import { getSwaggerDefinitions } from '@console/internal/module/k8s/swagger'; export const defaultEditorOptions = { readOnly: false, scrollBeyondLastLine: false }; -const MODEL_URI = 'inmemory://model.yaml'; -const MONACO_URI = Uri.parse(MODEL_URI); - -const createDocument = (model) => { - return TextDocument.create( - MODEL_URI, - model?.getModeId(), - model?.getVersionId(), - model?.getValue(), - ); -}; - -// Unfortunately, `editor.focus()` doesn't work when hiding the shortcuts -// popover. We need to find the actual DOM element. -export const hackyFocusEditor = () => - setTimeout(() => document.querySelector('.monaco-editor textarea')?.focus()); - -export const registerYAMLLanguage = (monaco) => { - // register the YAML language with Monaco - monaco.languages.register({ - id: 'yaml', - extensions: ['.yml', '.yaml'], - aliases: ['YAML', 'yaml'], - mimetypes: ['application/yaml'], - }); -}; - -export const createYAMLService = () => { - const resolveSchema = (url: string): Promise => { - const promise = new Promise((resolve, reject) => { - const xhr = new XMLHttpRequest(); - xhr.onload = () => resolve(xhr.responseText); - xhr.onerror = () => reject(xhr.statusText); - xhr.open('GET', url, true); - xhr.send(); - }); - return promise as Promise; - }; - - const workspaceContext = { - resolveRelativePath: (relativePath, resource) => URL.resolve(resource, relativePath), - }; - - const yamlService = getLanguageService(resolveSchema, workspaceContext); - - // Prepare the schema - const yamlOpenAPI = getSwaggerDefinitions(); - - // Convert the openAPI schema to something the language server understands - const kubernetesJSONSchema = openAPItoJSONSchema(yamlOpenAPI); - - const schemas = [ - { - uri: 'inmemory:yaml', - fileMatch: ['*'], - schema: kubernetesJSONSchema, - }, - ]; - yamlService.configure({ - validate: true, - schemas, - hover: true, - completion: true, - }); - return yamlService; -}; - -export const registerYAMLCompletion = (languageID, monaco, m2p, p2m, yamlService) => { - monaco.languages.registerCompletionItemProvider(languageID, { - provideCompletionItems(model, position) { - const document = createDocument(model); - return yamlService - .doComplete(document, m2p.asPosition(position.lineNumber, position.column), true) - .then((list) => { - return p2m.asCompletionResult(list); - }); - }, - }); -}; - -export const registerYAMLDocumentSymbols = (languageID, monaco, p2m, yamlService) => { - monaco.languages.registerDocumentSymbolProvider(languageID, { - provideDocumentSymbols(model) { - const document = createDocument(model); - return p2m.asSymbolInformations(yamlService.findDocumentSymbols(document)); - }, - }); -}; - -export const registerYAMLHover = (languageID, monaco, m2p, p2m, yamlService) => { - monaco.languages.registerHoverProvider(languageID, { - provideHover(model, position) { - const doc = createDocument(model); - return yamlService - .doHover(doc, m2p.asPosition(position.lineNumber, position.column), true) - .then((hover) => { - return p2m.asHover(hover); - }) - .then((e) => { - for (const el of document.getElementsByClassName('monaco-editor-hover')) { - el.onclick = (event) => event.preventDefault(); - el.onauxclick = (event) => { - window.open(event.target.getAttribute('data-href'), '_blank').opener = null; - event.preventDefault(); - }; - } - return e; - }); - }, - }); -}; - -const findManagedMetadata = (model) => { - const document = createDocument(model); - const doc = yaml.safeLoad(document.getText()); +const findManagedMetadata = (model: monaco.editor.ITextModel) => { + const modelValue = model.getValue(); + const doc = yaml.safeLoad(modelValue); const rootMappings = doc?.mappings || []; for (const rootElement of rootMappings) { const rootKey = rootElement.key; @@ -139,8 +23,8 @@ const findManagedMetadata = (model) => { // Search for managedFields if (childKey.value === 'managedFields') { - const startLine = document.positionAt(metadataChildren.startPosition).line + 1; - const endLine = document.positionAt(metadataChildren.endPosition).line; + const startLine = model.getPositionAt(metadataChildren.startPosition).lineNumber; + const endLine = model.getPositionAt(metadataChildren.endPosition).lineNumber; return { start: startLine, end: endLine, @@ -155,7 +39,11 @@ const findManagedMetadata = (model) => { }; }; -export const fold = (editor, model, resetMouseLocation: boolean): void => { +export const fold = ( + editor: monaco.editor.IStandaloneCodeEditor, + model: monaco.editor.ITextModel, + resetMouseLocation: boolean, +): void => { const managedLocation = findManagedMetadata(model); const { start } = managedLocation; const { end } = managedLocation; @@ -176,49 +64,20 @@ export const fold = (editor, model, resetMouseLocation: boolean): void => { } }; -// TODO: These functions are not part of React Component LifeCycle, will need refactoring -export const enableYAMLValidation = ( - editor, - monaco, - p2m, - monacoURI, - yamlService, +/** + * Register auto fold for the editor + * This should probably be a React hook + */ +export const registerAutoFold = ( + editor: monaco.editor.IStandaloneCodeEditor, + model: monaco.editor.ITextModel, alreadyInUse: boolean = false, ) => { - const pendingValidationRequests = new Map(); - - const getModel = () => monaco.editor?.getModels()[0]; - - const cleanPendingValidation = (document) => { - const request = pendingValidationRequests.get(document.uri); - if (request !== undefined) { - clearTimeout(request); - pendingValidationRequests.delete(document.uri); - } - }; - - const cleanDiagnostics = () => - monaco.editor.setModelMarkers(monaco.editor.getModel(monacoURI), 'default', []); - - const doValidate = (document) => { - if (document.getText().length === 0) { - cleanDiagnostics(); - return; - } - yamlService - .doValidation(document, true) - .then((diagnostics) => { - const markers = p2m.asDiagnostics(diagnostics); - monaco.editor.setModelMarkers(getModel(), 'default', markers); - }) - .catch(() => {}); - }; - let initialFoldingTriggered = false; const tryFolding = () => { - const document = createDocument(getModel()); - if (!initialFoldingTriggered && document.getText() !== '') { - setTimeout(() => fold(editor, getModel(), true)); + const document = model.getValue(); + if (!initialFoldingTriggered && document !== '') { + setTimeout(() => fold(editor, model, true)); initialFoldingTriggered = true; } }; @@ -226,33 +85,16 @@ export const enableYAMLValidation = ( tryFolding(); } - getModel()?.onDidChangeContent(() => { + model.onDidChangeContent(() => { tryFolding(); - - const document = createDocument(getModel()); - cleanPendingValidation(document); - pendingValidationRequests.set( - document.uri, - setTimeout(() => { - pendingValidationRequests.delete(document.uri); - doValidate(document); - }), - ); }); }; -export const registerYAMLinMonaco = (editor, monaco, alreadyInUse: boolean = false) => { - const LANGUAGE_ID = 'yaml'; - - const m2p = new MonacoToProtocolConverter(); - const p2m = new ProtocolToMonacoConverter(); - - const yamlService = createYAMLService(); - - // validation is not a 'registered' feature like the others, it relies on calling the yamlService - // directly for validation results when content in the editor has changed - enableYAMLValidation(editor, monaco, p2m, MONACO_URI, yamlService, alreadyInUse); - +export const registerYAMLinMonaco = ( + editor: monaco.editor.IStandaloneCodeEditor, + monacoInstance: typeof monaco, + alreadyInUse: boolean = false, +) => { /** * This exists because react-monaco-editor passes the same monaco * object each time. Without it you would be registering all the features again and @@ -264,12 +106,32 @@ export const registerYAMLinMonaco = (editor, monaco, alreadyInUse: boolean = fal * We check that > 1 YAML language exists because one is the default and one is the initial register * that setups our features. */ - if (monaco.languages.getLanguages().filter((x) => x.id === LANGUAGE_ID).length > 1) { - return; + if (monacoInstance.languages.getLanguages().filter((x) => x.id === 'yaml').length <= 1) { + // Prepare the schema + const yamlOpenAPI = getSwaggerDefinitions(); + + // Convert the openAPI schema to something the language server understands + const kubernetesJSONSchema = openAPItoJSONSchema(yamlOpenAPI); + + const schemas = [ + { + uri: 'inmemory:yaml', + fileMatch: ['*'], + schema: kubernetesJSONSchema, + }, + ]; + + configureMonacoYaml(monacoInstance, { + isKubernetes: true, + validate: true, + schemas, + hover: true, + completion: true, + }); } - registerYAMLLanguage(monaco); // register the YAML language with monaco - registerYAMLCompletion(LANGUAGE_ID, monaco, m2p, p2m, yamlService); - registerYAMLDocumentSymbols(LANGUAGE_ID, monaco, p2m, yamlService); - registerYAMLHover(LANGUAGE_ID, monaco, m2p, p2m, yamlService); + if (!alreadyInUse) { + const model = editor.getModel(); + registerAutoFold(editor, model); + } }; diff --git a/frontend/packages/dev-console/integration-tests/support/pages/app.ts b/frontend/packages/dev-console/integration-tests/support/pages/app.ts index bedc03e60f4..260fa04b9ba 100644 --- a/frontend/packages/dev-console/integration-tests/support/pages/app.ts +++ b/frontend/packages/dev-console/integration-tests/support/pages/app.ts @@ -394,6 +394,7 @@ export const yamlEditor = { isLoaded: () => { app.waitForLoad(); cy.get(yamlPO.yamlEditor).should('be.visible'); + cy.window().its('monaco.editor.getModels').should('exist'); }, clearYAMLEditor: () => { diff --git a/frontend/packages/dev-console/src/components/buildconfig/sections/EditorField.tsx b/frontend/packages/dev-console/src/components/buildconfig/sections/EditorField.tsx index 0d9081aa9d6..4a328f8793e 100644 --- a/frontend/packages/dev-console/src/components/buildconfig/sections/EditorField.tsx +++ b/frontend/packages/dev-console/src/components/buildconfig/sections/EditorField.tsx @@ -4,7 +4,6 @@ import { FormikValues, useFormikContext } from 'formik'; import MonacoEditor, { ChangeHandler, MonacoEditorProps } from 'react-monaco-editor'; import { ThemeContext } from '@console/internal/components/ThemeProvider'; import { RedExclamationCircleIcon, useDebounceCallback } from '@console/shared/src'; -import '@console/shared/src/components/editor/theme'; type EditorFieldProps = { name: string; diff --git a/frontend/public/components/edit-yaml.jsx b/frontend/public/components/edit-yaml.jsx index a52a5560e78..302e2c7955b 100644 --- a/frontend/public/components/edit-yaml.jsx +++ b/frontend/public/components/edit-yaml.jsx @@ -27,7 +27,6 @@ import { import CodeEditor from '@console/shared/src/components/editor/CodeEditor'; import CodeEditorSidebar from '@console/shared/src/components/editor/CodeEditorSidebar'; -import '@console/shared/src/components/editor/theme'; import { fold } from '@console/shared/src/components/editor/yaml-editor-utils'; import { downloadYaml } from '@console/shared/src/components/editor/yaml-download-utils'; import { isYAMLTemplate, getImpersonate } from '@console/dynamic-plugin-sdk'; diff --git a/frontend/webpack.config.ts b/frontend/webpack.config.ts index ebfff943107..ddc1142b1f5 100644 --- a/frontend/webpack.config.ts +++ b/frontend/webpack.config.ts @@ -147,14 +147,6 @@ const config: Configuration = { }, ], }, - { - test: /node_modules[\\\\|/](yaml-language-server)/, - loader: 'umd-compat-loader', - }, - { - test: /node_modules[\\\\|/](vscode-json-languageservice)/, - loader: 'umd-compat-loader', - }, { test: /\.s?css$/, exclude: /node_modules\/(?!(@patternfly(-\S+)?|@console\/plugin-shared)\/).*/, @@ -265,6 +257,16 @@ const config: Configuration = { new MonacoWebpackPlugin({ languages: ['yaml', 'dockerfile', 'json', 'plaintext'], globalAPI: true, + customLanguages: [ + { + label: 'yaml', + entry: 'monaco-yaml', + worker: { + id: 'monaco-yaml/yamlWorker', + entry: 'monaco-yaml/yaml.worker', + }, + }, + ], }), new NodePolyfillPlugin({ additionalAliases: ['process'], diff --git a/frontend/yarn.lock b/frontend/yarn.lock index 161484e59ed..e042e413c50 100644 --- a/frontend/yarn.lock +++ b/frontend/yarn.lock @@ -1747,6 +1747,20 @@ resolved "https://registry.yarnpkg.com/@microsoft/tsdoc/-/tsdoc-0.14.2.tgz#c3ec604a0b54b9a9b87e9735dfc59e1a5da6a5fb" integrity sha512-9b8mPpKrfeGRuhFH5iO1iwCLeIIsV6+H1sRfxbkoGXIyQE2BTsPd9zqSqQJ+pv5sJ/hT5M1zvOFL02MnEezFug== +"@monaco-editor/loader@^1.4.0": + version "1.4.0" + resolved "https://registry.yarnpkg.com/@monaco-editor/loader/-/loader-1.4.0.tgz#f08227057331ec890fa1e903912a5b711a2ad558" + integrity sha512-00ioBig0x642hytVspPl7DbQyaSWRaolYie/UFNjoTdvoKPzo6xrXLhTk9ixgIKcLH5b5vDOjVNiGyY+uDCUlg== + dependencies: + state-local "^1.0.6" + +"@monaco-editor/react@^4.6.0": + version "4.6.0" + resolved "https://registry.yarnpkg.com/@monaco-editor/react/-/react-4.6.0.tgz#bcc68671e358a21c3814566b865a54b191e24119" + integrity sha512-RFkU9/i7cN2bsq/iTkurMWOEErmYcY6JiQI3Jn+WeR/FGISH8JbHERjpS9oRuSOPvDMJI0Z8nJeKkbOs9sBYQw== + dependencies: + "@monaco-editor/loader" "^1.4.0" + "@nodelib/fs.scandir@2.1.3": version "2.1.3" resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.3.tgz#3a582bdb53804c6ba6d146579c46e52130cf4a3b" @@ -3939,16 +3953,6 @@ ast-types-flow@0.0.7, ast-types-flow@^0.0.7: resolved "https://registry.yarnpkg.com/ast-types-flow/-/ast-types-flow-0.0.7.tgz#f70b735c6bca1a5c9c22d982c3e39e7feba3bdad" integrity sha1-9wtzXGvKGlycItmCw+Oef+ujva0= -ast-types@0.9.6: - version "0.9.6" - resolved "https://registry.yarnpkg.com/ast-types/-/ast-types-0.9.6.tgz#102c9e9e9005d3e7e3829bf0c4fa24ee862ee9b9" - integrity sha1-ECyenpAF0+fjgpvwxPok7oYu6bk= - -ast-types@^0.9.2: - version "0.9.14" - resolved "https://registry.yarnpkg.com/ast-types/-/ast-types-0.9.14.tgz#d34ba5dffb9d15a44351fd2a9d82e4ab2838b5ba" - integrity sha512-Ebvx7/0lLboCdyEmAw/4GqwBeKIijPveXNiVGhCGCNxc7z26T5he7DC6ARxu8ByKuzUZZcLog+VP8GMyZrBzJw== - astral-regex@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/astral-regex/-/astral-regex-1.0.0.tgz#6c8c3fb827dd43ee3918f27b82782ab7658a6fd9" @@ -7886,7 +7890,7 @@ espree@^6.1.2: acorn-jsx "^5.1.0" eslint-visitor-keys "^1.1.0" -esprima@^3.1.3, esprima@~3.1.0: +esprima@^3.1.3: version "3.1.3" resolved "https://registry.yarnpkg.com/esprima/-/esprima-3.1.3.tgz#fdca51cee6133895e3c88d535ce49dbff62a4633" integrity sha1-/cpRzuYTOJXjyI1TXOSdv/YqRjM= @@ -9005,11 +9009,6 @@ glob-stream@^8.0.0: normalize-path "^3.0.0" streamx "^2.12.5" -glob-to-regexp@^0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/glob-to-regexp/-/glob-to-regexp-0.3.0.tgz#8c5a1494d2066c570cc3bfe4496175acc4d502ab" - integrity sha1-jFoUlNIGbFcMw7/kSWF1rMTVAqs= - glob-to-regexp@^0.4.1: version "0.4.1" resolved "https://registry.yarnpkg.com/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz#c75297087c851b9a578bd217dd59a92f59fe546e" @@ -9639,7 +9638,7 @@ https-browserify@^1.0.0: resolved "https://registry.yarnpkg.com/https-browserify/-/https-browserify-1.0.0.tgz#ec06c10e0a34c0f2faf199f7fd7fc78fffd03c73" integrity sha1-7AbBDgo0wPL68Zn3/X/Hj//QPHM= -https-proxy-agent@^2.2.1, https-proxy-agent@^2.2.3: +https-proxy-agent@^2.2.1: version "2.2.4" resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-2.2.4.tgz#4ee7a737abd92678a293d9b34a1af4d0d08c787b" integrity sha512-OmvfoQ53WLjtA9HeYP9RNrWMJzzAz1JGaSFr1nijg0PVR1JaD/xbJq1mdEIIlxGpXp9eSe/O2LgU9DJmTPd0Eg== @@ -11140,11 +11139,16 @@ json5@^2.1.0, json5@^2.1.2, json5@^2.2.3: resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.3.tgz#78cd6f1a19bdc12b73db5ad0c61efd66c1e29283" integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg== -jsonc-parser@^2.2.1, jsonc-parser@^2.3.1: +jsonc-parser@^2.3.1: version "2.3.1" resolved "https://registry.yarnpkg.com/jsonc-parser/-/jsonc-parser-2.3.1.tgz#59549150b133f2efacca48fe9ce1ec0659af2342" integrity sha512-H8jvkz1O50L3dMZCsLqiuB2tA7muqbSg1AtGEkN0leAqGjsUzDJir3Zwr02BhqdcITPg3ei3mZ+HjMocAknhhg== +jsonc-parser@^3.0.0: + version "3.3.1" + resolved "https://registry.yarnpkg.com/jsonc-parser/-/jsonc-parser-3.3.1.tgz#f2a524b4f7fd11e3d791e559977ad60b98b798b4" + integrity sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ== + jsonfile@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-4.0.0.tgz#8771aae0799b64076b76640fca058f9c10e33ecb" @@ -11614,7 +11618,7 @@ loader-runner@^4.2.0: resolved "https://registry.yarnpkg.com/loader-runner/-/loader-runner-4.3.0.tgz#c1b4a163b99f614830353b16755e7149ac2314e1" integrity sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg== -loader-utils@^1.0.3, loader-utils@^1.1.0, loader-utils@^1.4.0: +loader-utils@^1.1.0, loader-utils@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-1.4.0.tgz#c579b5e34cb34b1a74edc6c1fb36bfa371d5a613" integrity sha512-qH0WSMBtn/oHuwjy/NucEgbx5dbxxnxup9s4PVXJUDHZBQY+s0NWA9rJf53RBnQZxfch7euUui7hpoAPvALZdA== @@ -11632,7 +11636,7 @@ loader-utils@^2.0.0: emojis-list "^3.0.0" json5 "^2.1.2" -loader-utils@^2.0.4: +loader-utils@^2.0.2, loader-utils@^2.0.4: version "2.0.4" resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-2.0.4.tgz#8b5cb38b5c34a9a018ee1fc0e6a066d1dfcc528c" integrity sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw== @@ -12371,27 +12375,60 @@ module-deps@^6.0.0, module-deps@^6.2.3: through2 "^2.0.0" xtend "^4.0.0" -monaco-editor-webpack-plugin@^4.2.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/monaco-editor-webpack-plugin/-/monaco-editor-webpack-plugin-4.2.0.tgz#2be76cde9cca7bd8c3418503625990f86886927b" - integrity sha512-/P3sFiEgBl+Y50he4mbknMhbLJVop5gBUZiPS86SuHUDOOnQiQ5rL1jU5lwt1XKAwMEkhwZbUwqaHxTPkb1Utw== +monaco-editor-webpack-plugin@^7.1.0: + version "7.1.0" + resolved "https://registry.yarnpkg.com/monaco-editor-webpack-plugin/-/monaco-editor-webpack-plugin-7.1.0.tgz#16f265c2b5dbb5fe08681b6b3b7d00d3c5b2ee97" + integrity sha512-ZjnGINHN963JQkFqjjcBtn1XBtUATDZBMgNQhDQwd78w2ukRhFXAPNgWuacaQiDZsUr4h1rWv5Mv6eriKuOSzA== dependencies: - loader-utils "^2.0.0" + loader-utils "^2.0.2" -monaco-editor@^0.28.1: - version "0.28.1" - resolved "https://registry.yarnpkg.com/monaco-editor/-/monaco-editor-0.28.1.tgz#732788ff2172d59e6d436b206da8cac715413940" - integrity sha512-P1vPqxB4B1ZFzTeR1ScggSp9/5NoQrLCq88fnlNUsuRAP1usEBN4TIpI2lw0AYIZNVIanHk0qwjze2uJwGOHUw== +monaco-editor@^0.36.1: + version "0.36.1" + resolved "https://registry.yarnpkg.com/monaco-editor/-/monaco-editor-0.36.1.tgz#aad528c815605307473a1634612946921d8079b5" + integrity sha512-/CaclMHKQ3A6rnzBzOADfwdSJ25BFoFT0Emxsc4zYVyav5SkK9iA6lEtIeuN/oRYbwPgviJT+t3l+sjFa28jYg== -monaco-languageclient@^0.13.0: - version "0.13.0" - resolved "https://registry.yarnpkg.com/monaco-languageclient/-/monaco-languageclient-0.13.0.tgz#59b68b42fb7633171502d6557f597c2752f6c266" - integrity sha512-aCwd33dTitwV5QwY56rpYHwzEGXei8TZ+yvZcvP3gEMd6Mizr8m3pOuoknDi2SUfLuNAHS6+ulvLgZlNQB5awg== +monaco-languageserver-types@^0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/monaco-languageserver-types/-/monaco-languageserver-types-0.4.0.tgz#8f3414c1ebba786b9c9db45857cc853e50e768e8" + integrity sha512-QQ3BZiU5LYkJElGncSNb5AKoJ/LCs6YBMCJMAz9EA7v+JaOdn3kx2cXpPTcZfKA5AEsR0vc97sAw+5mdNhVBmw== dependencies: - glob-to-regexp "^0.3.0" - vscode-jsonrpc "^5.0.0" - vscode-languageclient "^6.0.0" - vscode-uri "^2.1.1" + monaco-types "^0.1.0" + vscode-languageserver-protocol "^3.0.0" + vscode-uri "^3.0.0" + +monaco-marker-data-provider@^1.0.0: + version "1.2.4" + resolved "https://registry.yarnpkg.com/monaco-marker-data-provider/-/monaco-marker-data-provider-1.2.4.tgz#56fbeede5bd830ec2ee15b2aea9a7df62fa447a7" + integrity sha512-4DsPgsAqpTyUDs3humXRBPUJoihTv+L6v9aupQWD80X2YXaCXUd11mWYeSCYHuPgdUmjFaNWCEOjQ6ewf/QA1Q== + dependencies: + monaco-types "^0.1.0" + +monaco-types@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/monaco-types/-/monaco-types-0.1.0.tgz#3a3066aba499cb5923cd60efc736f3f14a169e10" + integrity sha512-aWK7SN9hAqNYi0WosPoMjenMeXJjwCxDibOqWffyQ/qXdzB/86xshGQobRferfmNz7BSNQ8GB0MD0oby9/5fTQ== + +monaco-worker-manager@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/monaco-worker-manager/-/monaco-worker-manager-2.0.1.tgz#f67c54dfca34ed4b225d5de84e77b24b4e36de8a" + integrity sha512-kdPL0yvg5qjhKPNVjJoym331PY/5JC11aPJXtCZNwWRvBr6jhkIamvYAyiY5P1AWFmNOy0aRDRoMdZfa71h8kg== + +monaco-yaml@^5.2.3: + version "5.2.3" + resolved "https://registry.yarnpkg.com/monaco-yaml/-/monaco-yaml-5.2.3.tgz#990fcf697bfa54e684a2d2f7df6d81a889843db4" + integrity sha512-GEplKC+YYmS0TOlJdv0FzbqkDN/IG2FSwEw+95myECVxTlhty2amwERYjzvorvJXmIagP1grd3Lleq7aQEJpPg== + dependencies: + jsonc-parser "^3.0.0" + monaco-languageserver-types "^0.4.0" + monaco-marker-data-provider "^1.0.0" + monaco-types "^0.1.0" + monaco-worker-manager "^2.0.0" + path-browserify "^1.0.0" + prettier "^2.0.0" + vscode-languageserver-textdocument "^1.0.0" + vscode-languageserver-types "^3.0.0" + vscode-uri "^3.0.0" + yaml "^2.0.0" moo@^0.5.0: version "0.5.2" @@ -13282,7 +13319,7 @@ pascalcase@^0.1.1: resolved "https://registry.yarnpkg.com/pascalcase/-/pascalcase-0.1.1.tgz#b363e55e8006ca6fe21784d2db22bd15d7917f14" integrity sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ= -path-browserify@^1.0.1: +path-browserify@^1.0.0, path-browserify@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/path-browserify/-/path-browserify-1.0.1.tgz#d98454a9c3753d5790860f16f68867b9e46be1fd" integrity sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g== @@ -13591,7 +13628,7 @@ prettier@2.0.5: resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.0.5.tgz#d6d56282455243f2f92cc1716692c08aa31522d4" integrity sha512-7PtVymN48hGcO4fGjybyBSIWDsLU4H4XlvOHfq91pz9kkGlonzwTfYkaIEwiRg/dAJF9YlbsduBAgtYLi+8cFg== -prettier@^2.6.2: +prettier@^2.0.0, prettier@^2.6.2: version "2.8.8" resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.8.8.tgz#e8c5d7e98a4305ffe3de2e1fc4aca1a71c28b1da" integrity sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q== @@ -13665,7 +13702,7 @@ prisma-yml@1.34.10: scuid "^1.0.2" yaml-ast-parser "^0.0.40" -private@^0.1.7, private@~0.1.5: +private@^0.1.7: version "0.1.8" resolved "https://registry.yarnpkg.com/private/-/private-0.1.8.tgz#2381edb3689f7a53d653190060fcf822d2f368ff" @@ -14121,12 +14158,12 @@ react-modal@^3.12.1: react-lifecycles-compat "^3.0.0" warning "^4.0.3" -react-monaco-editor@0.46.x: - version "0.46.0" - resolved "https://registry.yarnpkg.com/react-monaco-editor/-/react-monaco-editor-0.46.0.tgz#ac97d5429cd8821d466f0e8e0536ea2a90bbc6d0" - integrity sha512-/GyQ0tQLbjHAuMUNRfKecBYN68o8TwA4fnwH9P+lHbF80ayMAo0PQ60joTQH6R6j839kMn6o9Kk/cbzOxK5DzA== +react-monaco-editor@^0.56.2: + version "0.56.2" + resolved "https://registry.yarnpkg.com/react-monaco-editor/-/react-monaco-editor-0.56.2.tgz#9703424b7d956085c4b74450345cc83c8de9176c" + integrity sha512-Tp5U3QF9h92Cuf0eIhGd8Jyef8tPMlEJC2Dk1GeuR/hj6WoFn8AgjVX/2dv+3l5DvpMUpAECcFarc3eFKTBZ5w== dependencies: - prop-types "^15.7.2" + prop-types "^15.8.1" react-redux@7.2.2: version "7.2.2" @@ -14388,16 +14425,6 @@ readdirp@~3.6.0: dependencies: picomatch "^2.2.1" -recast@^0.11.17: - version "0.11.23" - resolved "https://registry.yarnpkg.com/recast/-/recast-0.11.23.tgz#451fd3004ab1e4df9b4e4b66376b2a21912462d3" - integrity sha1-RR/TAEqx5N+bTktmN2sqIZEkYtM= - dependencies: - ast-types "0.9.6" - esprima "~3.1.0" - private "~0.1.5" - source-map "~0.5.0" - rechoir@^0.8.0: version "0.8.0" resolved "https://registry.yarnpkg.com/rechoir/-/rechoir-0.8.0.tgz#49f866e0d32146142da3ad8f0eff352b3215ff22" @@ -14630,15 +14657,6 @@ replaceall@^0.1.6: resolved "https://registry.yarnpkg.com/replaceall/-/replaceall-0.1.6.tgz#81d81ac7aeb72d7f5c4942adf2697a3220688d8e" integrity sha1-gdgax663LX9cSUKt8ml6MiBojY4= -request-light@^0.2.4: - version "0.2.5" - resolved "https://registry.yarnpkg.com/request-light/-/request-light-0.2.5.tgz#38a3da7b2e56f7af8cbba57e8a94930ee2380746" - integrity sha512-eBEh+GzJAftUnex6tcL6eV2JCifY0+sZMIUpUPOVXbs2nV5hla4ZMmO3icYKGuGVuQ2zHE9evh4OrRcH4iyYYw== - dependencies: - http-proxy-agent "^2.1.0" - https-proxy-agent "^2.2.3" - vscode-nls "^4.1.1" - request-progress@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/request-progress/-/request-progress-3.0.0.tgz#4ca754081c7fec63f505e4faa825aa06cd669dbe" @@ -15514,7 +15532,7 @@ source-map@^0.4.4: dependencies: amdefine ">=0.0.4" -source-map@^0.5.0, source-map@^0.5.3, source-map@^0.5.6, source-map@^0.5.7, source-map@~0.5.0, source-map@~0.5.1, source-map@~0.5.3: +source-map@^0.5.0, source-map@^0.5.3, source-map@^0.5.6, source-map@^0.5.7, source-map@~0.5.1, source-map@~0.5.3: version "0.5.7" resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc" integrity sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w= @@ -15654,6 +15672,11 @@ stacktrace-js@^2.0.0: stack-generator "^2.0.5" stacktrace-gps "^3.0.4" +state-local@^1.0.6: + version "1.0.7" + resolved "https://registry.yarnpkg.com/state-local/-/state-local-1.0.7.tgz#da50211d07f05748d53009bee46307a37db386d5" + integrity sha512-HTEHMNieakEnoe33shBYcZ7NX83ACUjCu8c40iOGEZsngj9zRnkqS9j1pqQPXwobB0ZcVTk27REb7COQ0UR59w== + static-extend@^0.1.1: version "0.1.2" resolved "https://registry.yarnpkg.com/static-extend/-/static-extend-0.1.2.tgz#60809c39cbff55337226fd5e0b520f341f1fb5c6" @@ -16611,15 +16634,6 @@ uglify-to-browserify@~1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/uglify-to-browserify/-/uglify-to-browserify-1.0.2.tgz#6e0924d6bda6b5afe349e39a6d632850a0f882b7" -umd-compat-loader@^2.1.2: - version "2.1.2" - resolved "https://registry.yarnpkg.com/umd-compat-loader/-/umd-compat-loader-2.1.2.tgz#abf89be1591940a236cf8fa87f88d6d6f5a8da35" - integrity sha512-RkTlsfrCxUISWqiTtYFFJank7b2Hhl4V2pc29nl0xOEGvvuVkpy1xnufhXfTituxgpW0HSrDk0JHlvPYZxEXKQ== - dependencies: - ast-types "^0.9.2" - loader-utils "^1.0.3" - recast "^0.11.17" - umd@^3.0.0: version "3.0.3" resolved "https://registry.yarnpkg.com/umd/-/umd-3.0.3.tgz#aa9fe653c42b9097678489c01000acb69f0b26cf" @@ -17360,7 +17374,7 @@ void-elements@3.1.0: resolved "https://registry.yarnpkg.com/void-elements/-/void-elements-3.1.0.tgz#614f7fbf8d801f0bb5f0661f5b2f5785750e4f09" integrity sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w== -vscode-json-languageservice@^3.10.0, vscode-json-languageservice@^3.3.5: +vscode-json-languageservice@^3.3.5: version "3.10.0" resolved "https://registry.yarnpkg.com/vscode-json-languageservice/-/vscode-json-languageservice-3.10.0.tgz#19eed884fd0f234f8ed2fa0a96e772f293ccc5c4" integrity sha512-8IvuRSQnjznu+obqy6Dy4S4H68Ke7a3Kb+A0FcdctyAMAWEnrORpCpMOMqEYiPLm/OTYLVWJ7ql3qToDTozu4w== @@ -17371,88 +17385,54 @@ vscode-json-languageservice@^3.10.0, vscode-json-languageservice@^3.3.5: vscode-nls "^5.0.0" vscode-uri "^2.1.2" -vscode-jsonrpc@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/vscode-jsonrpc/-/vscode-jsonrpc-4.0.0.tgz#a7bf74ef3254d0a0c272fab15c82128e378b3be9" - integrity sha512-perEnXQdQOJMTDFNv+UF3h1Y0z4iSiaN9jIlb0OqIYgosPCZGYh/MCUlkFtV2668PL69lRDO32hmvL2yiidUYg== - -vscode-jsonrpc@^5.0.0, vscode-jsonrpc@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/vscode-jsonrpc/-/vscode-jsonrpc-5.0.1.tgz#9bab9c330d89f43fc8c1e8702b5c36e058a01794" - integrity sha512-JvONPptw3GAQGXlVV2utDcHx0BiY34FupW/kI6mZ5x06ER5DdPG/tXWMVHjTNULF5uKPOUUD0SaXg5QaubJL0A== - -vscode-languageclient@^6.0.0: - version "6.1.3" - resolved "https://registry.yarnpkg.com/vscode-languageclient/-/vscode-languageclient-6.1.3.tgz#c979c5bb5855714a0307e998c18ca827c1b3953a" - integrity sha512-YciJxk08iU5LmWu7j5dUt9/1OLjokKET6rME3cI4BRpiF6HZlusm2ZwPt0MYJ0lV5y43sZsQHhyon2xBg4ZJVA== - dependencies: - semver "^6.3.0" - vscode-languageserver-protocol "^3.15.3" +vscode-jsonrpc@8.2.0: + version "8.2.0" + resolved "https://registry.yarnpkg.com/vscode-jsonrpc/-/vscode-jsonrpc-8.2.0.tgz#f43dfa35fb51e763d17cd94dcca0c9458f35abf9" + integrity sha512-C+r0eKJUIfiDIfwJhria30+TYWPtuHJXHtI7J0YlOmKAo7ogxP20T0zxB7HZQIFhIyvoBPwWskjxrvAtfjyZfA== -vscode-languageserver-protocol@3.14.1: - version "3.14.1" - resolved "https://registry.yarnpkg.com/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.14.1.tgz#b8aab6afae2849c84a8983d39a1cf742417afe2f" - integrity sha512-IL66BLb2g20uIKog5Y2dQ0IiigW0XKrvmWiOvc0yXw80z3tMEzEnHjaGAb3ENuU7MnQqgnYJ1Cl2l9RvNgDi4g== +vscode-languageserver-protocol@^3.0.0: + version "3.17.5" + resolved "https://registry.yarnpkg.com/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.17.5.tgz#864a8b8f390835572f4e13bd9f8313d0e3ac4bea" + integrity sha512-mb1bvRJN8SVznADSGWM9u/b07H7Ecg0I3OgXDuLdn307rl/J3A9YD6/eYOssqhecL27hK1IPZAsaqh00i/Jljg== dependencies: - vscode-jsonrpc "^4.0.0" - vscode-languageserver-types "3.14.0" + vscode-jsonrpc "8.2.0" + vscode-languageserver-types "3.17.5" -vscode-languageserver-protocol@^3.15.3: - version "3.15.3" - resolved "https://registry.yarnpkg.com/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.15.3.tgz#3fa9a0702d742cf7883cb6182a6212fcd0a1d8bb" - integrity sha512-zrMuwHOAQRhjDSnflWdJG+O2ztMWss8GqUUB8dXLR/FPenwkiBNkMIJJYfSN6sgskvsF0rHAoBowNQfbyZnnvw== - dependencies: - vscode-jsonrpc "^5.0.1" - vscode-languageserver-types "3.15.1" +vscode-languageserver-textdocument@^1.0.0: + version "1.0.12" + resolved "https://registry.yarnpkg.com/vscode-languageserver-textdocument/-/vscode-languageserver-textdocument-1.0.12.tgz#457ee04271ab38998a093c68c2342f53f6e4a631" + integrity sha512-cxWNPesCnQCcMPeenjKKsOCKQZ/L6Tv19DTRIGuLWe32lyzWhihGVJ/rcckZXJxfdKCFvRLS3fpBIsV/ZGX4zA== vscode-languageserver-textdocument@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/vscode-languageserver-textdocument/-/vscode-languageserver-textdocument-1.0.1.tgz#178168e87efad6171b372add1dea34f53e5d330f" integrity sha512-UIcJDjX7IFkck7cSkNNyzIz5FyvpQfY7sdzVy+wkKN/BLaD4DQ0ppXQrKePomCxTS7RrolK1I0pey0bG9eh8dA== -vscode-languageserver-types@3.14.0: - version "3.14.0" - resolved "https://registry.yarnpkg.com/vscode-languageserver-types/-/vscode-languageserver-types-3.14.0.tgz#d3b5952246d30e5241592b6dde8280e03942e743" - integrity sha512-lTmS6AlAlMHOvPQemVwo3CezxBp0sNB95KNPkqp3Nxd5VFEnuG1ByM0zlRWos0zjO3ZWtkvhal0COgiV1xIA4A== - -vscode-languageserver-types@3.15.1, vscode-languageserver-types@^3.10.0, vscode-languageserver-types@^3.15.1: - version "3.15.1" - resolved "https://registry.yarnpkg.com/vscode-languageserver-types/-/vscode-languageserver-types-3.15.1.tgz#17be71d78d2f6236d414f0001ce1ef4d23e6b6de" - integrity sha512-+a9MPUQrNGRrGU630OGbYVQ+11iOIovjCkqxajPa9w57Sd5ruK8WQNsslzpa0x/QJqC8kRc2DUxWjIFwoNm4ZQ== - vscode-languageserver-types@3.16.0-next.2: version "3.16.0-next.2" resolved "https://registry.yarnpkg.com/vscode-languageserver-types/-/vscode-languageserver-types-3.16.0-next.2.tgz#940bd15c992295a65eae8ab6b8568a1e8daa3083" integrity sha512-QjXB7CKIfFzKbiCJC4OWC8xUncLsxo19FzGVp/ADFvvi87PlmBSCAtZI5xwGjF5qE0xkLf0jjKUn3DzmpDP52Q== -vscode-languageserver@^5.2.1: - version "5.2.1" - resolved "https://registry.yarnpkg.com/vscode-languageserver/-/vscode-languageserver-5.2.1.tgz#0d2feddd33f92aadf5da32450df498d52f6f14eb" - integrity sha512-GuayqdKZqAwwaCUjDvMTAVRPJOp/SLON3mJ07eGsx/Iq9HjRymhKWztX41rISqDKhHVVyFM+IywICyZDla6U3A== - dependencies: - vscode-languageserver-protocol "3.14.1" - vscode-uri "^1.0.6" - -vscode-nls@^4.1.1, vscode-nls@^4.1.2: - version "4.1.2" - resolved "https://registry.yarnpkg.com/vscode-nls/-/vscode-nls-4.1.2.tgz#ca8bf8bb82a0987b32801f9fddfdd2fb9fd3c167" - integrity sha512-7bOHxPsfyuCqmP+hZXscLhiHwe7CSuFE4hyhbs22xPIhQ4jv99FcR4eBzfYYVLP356HNFpdvz63FFb/xw6T4Iw== +vscode-languageserver-types@3.17.5, vscode-languageserver-types@^3.0.0: + version "3.17.5" + resolved "https://registry.yarnpkg.com/vscode-languageserver-types/-/vscode-languageserver-types-3.17.5.tgz#3273676f0cf2eab40b3f44d085acbb7f08a39d8a" + integrity sha512-Ld1VelNuX9pdF39h2Hgaeb5hEZM2Z3jUrrMgWQAu82jMtZp7p3vJT3BzToKtZI7NgQssZje5o0zryOrhQvzQAg== vscode-nls@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/vscode-nls/-/vscode-nls-5.0.0.tgz#99f0da0bd9ea7cda44e565a74c54b1f2bc257840" integrity sha512-u0Lw+IYlgbEJFF6/qAqG2d1jQmJl0eyAGJHoAJqr2HT4M2BNuQYSEiSE75f52pXHSJm8AlTjnLLbBFPrdz2hpA== -vscode-uri@^1.0.6: - version "1.0.8" - resolved "https://registry.yarnpkg.com/vscode-uri/-/vscode-uri-1.0.8.tgz#9769aaececae4026fb6e22359cb38946580ded59" - integrity sha512-obtSWTlbJ+a+TFRYGaUumtVwb+InIUVI0Lu0VBUAPmj2cU5JutEXg3xUE0c2J5Tcy7h2DEKVJBFi+Y9ZSFzzPQ== - -vscode-uri@^2.1.1, vscode-uri@^2.1.2: +vscode-uri@^2.1.2: version "2.1.2" resolved "https://registry.yarnpkg.com/vscode-uri/-/vscode-uri-2.1.2.tgz#c8d40de93eb57af31f3c715dd650e2ca2c096f1c" integrity sha512-8TEXQxlldWAuIODdukIb+TR5s+9Ds40eSJrw+1iDDA9IFORPjMELarNQE3myz5XIkWWpdprmJjm1/SxMlWOC8A== +vscode-uri@^3.0.0: + version "3.0.8" + resolved "https://registry.yarnpkg.com/vscode-uri/-/vscode-uri-3.0.8.tgz#1770938d3e72588659a172d0fd4642780083ff9f" + integrity sha512-AyFQ0EVmsOZOlAnxoFOGOq1SQDWAB7C6aqMGS23svWAllfOaxbuFvcT8D1i8z3Gyn8fraVeZNNmN6e9bxxXkKw== + vue-template-compiler@^2.6.11: version "2.6.11" resolved "https://registry.yarnpkg.com/vue-template-compiler/-/vue-template-compiler-2.6.11.tgz#c04704ef8f498b153130018993e56309d4698080" @@ -18025,33 +18005,16 @@ yaml-ast-parser@^0.0.40: resolved "https://registry.yarnpkg.com/yaml-ast-parser/-/yaml-ast-parser-0.0.40.tgz#08536d4e73d322b1c9ce207ab8dd70e04d20ae6e" integrity sha1-CFNtTnPTIrHJziB6uN1w4E0grm4= -yaml-language-server-parser@0.1.1: - version "0.1.1" - resolved "https://registry.yarnpkg.com/yaml-language-server-parser/-/yaml-language-server-parser-0.1.1.tgz#02cc9c022a8b10ffa7ba92096ffee86433a50b07" - integrity sha512-2PememGb1SrPqXAxXTpBD39rwYZap6CJVSvkfULNv9uwV3VHp1TfkgpsylBb+mpuuivH0JZ52lChXPvNa6yVxw== - -yaml-language-server@0.13.0: - version "0.13.0" - resolved "https://registry.yarnpkg.com/yaml-language-server/-/yaml-language-server-0.13.0.tgz#ea18facf59618589b51675ee63e14e00c71c7906" - integrity sha512-5FHW7dUAyIjEM3mRzIplE0pZut2K30cA+K7coaOxFxi82LTk/oiVLS4/AQFnOtGicSyoi4YOiRqpMZ04vgtuew== - dependencies: - js-yaml "^3.13.1" - jsonc-parser "^2.2.1" - request-light "^0.2.4" - vscode-json-languageservice "^3.10.0" - vscode-languageserver "^5.2.1" - vscode-languageserver-types "^3.15.1" - vscode-nls "^4.1.2" - vscode-uri "^2.1.1" - yaml-language-server-parser "0.1.1" - optionalDependencies: - prettier "2.0.5" - yaml@^1.10.0, yaml@^1.7.2: version "1.10.0" resolved "https://registry.yarnpkg.com/yaml/-/yaml-1.10.0.tgz#3b593add944876077d4d683fee01081bd9fff31e" integrity sha512-yr2icI4glYaNG+KWONODapy2/jDdMSDnrONSjblABjD9B4Z5LgiircSt8m8sRZFNi08kG9Sm0uSHtEmP3zaEGg== +yaml@^2.0.0: + version "2.6.1" + resolved "https://registry.yarnpkg.com/yaml/-/yaml-2.6.1.tgz#42f2b1ba89203f374609572d5349fb8686500773" + integrity sha512-7r0XPzioN/Q9kXBro/XPnA6kznR73DHq+GXh5ON7ZozRO6aMjbmiBuKste2wslTFkC5d1dw0GooOCepZXJ2SAg== + yargs-parser@^13.1.2: version "13.1.2" resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-13.1.2.tgz#130f09702ebaeef2650d54ce6e3e5706f7a4fb38" From ae126c3c1a0b703f47e7b65e1b3272d6809340c8 Mon Sep 17 00:00:00 2001 From: logonoff Date: Fri, 3 Jan 2025 09:55:43 -0500 Subject: [PATCH 024/618] CONSOLE-4407: Reload YAML on mount --- .../src/extensions/console-types.ts | 6 +-- .../src/components/editor/CodeEditor.tsx | 22 ++++++-- .../components/editor/CodeEditorSidebar.tsx | 17 ++++--- .../components/editor/yaml-editor-utils.ts | 2 +- frontend/public/components/edit-yaml.jsx | 50 ++++++++++--------- 5 files changed, 57 insertions(+), 40 deletions(-) diff --git a/frontend/packages/console-dynamic-plugin-sdk/src/extensions/console-types.ts b/frontend/packages/console-dynamic-plugin-sdk/src/extensions/console-types.ts index f9f361e04a7..9953e17242d 100644 --- a/frontend/packages/console-dynamic-plugin-sdk/src/extensions/console-types.ts +++ b/frontend/packages/console-dynamic-plugin-sdk/src/extensions/console-types.ts @@ -3,6 +3,7 @@ import { QuickStartContextValues } from '@patternfly/quickstarts'; import { ButtonProps } from '@patternfly/react-core'; import { ICell, OnSelect, SortByDirection, TableGridBreakpoint } from '@patternfly/react-table'; import { LocationDescriptor } from 'history'; +import type { editor } from 'monaco-editor/esm/vs/editor/editor.api'; import { ExtensionK8sGroupKindModel, K8sModel, @@ -643,11 +644,10 @@ export type CodeEditorProps = { toolbarLinks?: React.ReactNodeArray; onChange?: (newValue, event) => void; onSave?: () => void; + onEditorDidMount?: (editor: editor.IStandaloneCodeEditor) => void; }; -export type CodeEditorRef = { - editor?: any; -}; +export type CodeEditorRef = { getEditor: () => editor.IStandaloneCodeEditor }; export type ResourceYAMLEditorProps = { initialResource: string | { [key: string]: any }; diff --git a/frontend/packages/console-shared/src/components/editor/CodeEditor.tsx b/frontend/packages/console-shared/src/components/editor/CodeEditor.tsx index c0223859688..8f042ebabb2 100644 --- a/frontend/packages/console-shared/src/components/editor/CodeEditor.tsx +++ b/frontend/packages/console-shared/src/components/editor/CodeEditor.tsx @@ -1,13 +1,14 @@ import * as React from 'react'; +import type { editor } from 'monaco-editor/esm/vs/editor/editor.api'; import Measure from 'react-measure'; import MonacoEditor, { EditorDidMount } from 'react-monaco-editor'; -import { CodeEditorProps } from '@console/dynamic-plugin-sdk'; +import { CodeEditorRef, CodeEditorProps } from '@console/dynamic-plugin-sdk'; import { ThemeContext } from '@console/internal/components/ThemeProvider'; import CodeEditorToolbar from './CodeEditorToolbar'; import { registerYAMLinMonaco, defaultEditorOptions } from './yaml-editor-utils'; import './CodeEditor.scss'; -const CodeEditor = React.forwardRef((props, ref) => { +const CodeEditor = React.forwardRef((props, ref) => { const { value, options = defaultEditorOptions, @@ -18,12 +19,15 @@ const CodeEditor = React.forwardRef((props, ref) => { onChange, onSave, language, + onEditorDidMount, } = props; const theme = React.useContext(ThemeContext); + const [editorRef, setEditorRef] = React.useState(null); const [usesValue] = React.useState(value !== undefined); const editorDidMount: EditorDidMount = React.useCallback( (editor, monaco) => { + setEditorRef(editor); const currentLanguage = editor.getModel()?.getLanguageId(); editor.layout(); editor.focus(); @@ -39,8 +43,9 @@ const CodeEditor = React.forwardRef((props, ref) => { } monaco.editor.getModels()[0]?.updateOptions({ tabSize: 2 }); onSave && editor.addCommand(monaco.KeyMod.CtrlCmd | monaco.KeyCode.KeyS, onSave); // eslint-disable-line no-bitwise + onEditorDidMount && onEditorDidMount(editor); }, - [onSave, usesValue], + [onSave, usesValue, onEditorDidMount], ); const editorOptions = React.useMemo(() => { @@ -52,13 +57,22 @@ const CodeEditor = React.forwardRef((props, ref) => { }; }, [options, showMiniMap]); + // use the ref of the editor to expose it to the parent component + React.useImperativeHandle( + ref, + () => ({ + getEditor: () => editorRef, + }), + [editorRef], + ); + return ( <> {({ measureRef, contentRect }) => (
-
+
; + editorRef: React.MutableRefObject; model?: K8sKind; samples?: Sample[]; schema?: JSONSchema7; @@ -27,13 +28,13 @@ const CodeEditorSidebar: React.FC = ({ sanitizeYamlContent, toggleSidebar, }) => { - const editor = editorRef.current?.editor; + const getEditor = React.useCallback(() => editorRef?.current?.getEditor(), [editorRef]); const insertYamlContent = React.useCallback( (id: string = 'default', yamlContent: string = '', kind) => { const yaml = sanitizeYamlContent ? sanitizeYamlContent(id, yamlContent, kind) : yamlContent; - const selection = editor.getSelection(); + const selection = getEditor()?.getSelection(); const range = new Range( selection.startLineNumber, selection.startColumn, @@ -63,18 +64,18 @@ const CodeEditorSidebar: React.FC = ({ ); const op = { range, text: indentedText, forceMoveMarkers: true }; - editor.executeEdits(id, [op], [newContentSelection]); - editor.focus(); + getEditor()?.executeEdits(id, [op], [newContentSelection]); + getEditor()?.focus(); }, - [editor, sanitizeYamlContent], + [sanitizeYamlContent, getEditor], ); const replaceYamlContent = React.useCallback( (id: string = 'default', yamlContent: string = '', kind: string) => { const yaml = sanitizeYamlContent ? sanitizeYamlContent(id, yamlContent, kind) : yamlContent; - editor.setValue(yaml); + getEditor()?.setValue(yaml); }, - [editor, sanitizeYamlContent], + [sanitizeYamlContent, getEditor], ); const downloadYamlContent = React.useCallback( diff --git a/frontend/packages/console-shared/src/components/editor/yaml-editor-utils.ts b/frontend/packages/console-shared/src/components/editor/yaml-editor-utils.ts index 0e4e6794f90..5b1b193adfb 100644 --- a/frontend/packages/console-shared/src/components/editor/yaml-editor-utils.ts +++ b/frontend/packages/console-shared/src/components/editor/yaml-editor-utils.ts @@ -1,5 +1,5 @@ import { Range } from 'monaco-editor'; -import * as monaco from 'monaco-editor/esm/vs/editor/editor.api'; +import type * as monaco from 'monaco-editor/esm/vs/editor/editor.api'; import { configureMonacoYaml } from 'monaco-yaml'; import * as yaml from 'yaml-ast-parser'; import { openAPItoJSONSchema } from '@console/internal/module/k8s/openapi-to-json-schema'; diff --git a/frontend/public/components/edit-yaml.jsx b/frontend/public/components/edit-yaml.jsx index 302e2c7955b..4bd5e07e9bc 100644 --- a/frontend/public/components/edit-yaml.jsx +++ b/frontend/public/components/edit-yaml.jsx @@ -124,6 +124,7 @@ const EditYAMLInner = (props) => { const [notAllowed, setNotAllowed] = React.useState(); const [displayResults, setDisplayResults] = React.useState(); const [resourceObjects, setResourceObjects] = React.useState(); + const [editorMounted, setEditorMounted] = React.useState(false); const [callbackCommand, setCallbackCommand] = React.useState(''); const [showReplaceCodeModal, setShowReplaceCodeModal] = React.useState(false); @@ -144,9 +145,8 @@ const EditYAMLInner = (props) => { const displayedVersion = React.useRef('0'); const onCancel = 'onCancel' in props ? props.onCancel : navigateBack; - const getEditor = () => { - return monacoRef.current?.editor; - }; + /** @return {import('monaco-editor').editor.IStandaloneCodeEditor | null} */ + const getEditor = () => monacoRef.current?.getEditor(); const getModel = React.useCallback( (obj) => { @@ -208,16 +208,14 @@ const EditYAMLInner = (props) => { .then((resp) => { const notAll = !resp.status.allowed; setNotAllowed(notAll); - if (monacoRef.current) { - monacoRef.current.editor?.updateOptions({ readOnly: notAll }); - } + editorMounted && getEditor()?.updateOptions({ readOnly: notAll }); }) .catch((e) => { // eslint-disable-next-line no-console console.warn('Error while check edit access', e); }); }, - [props.readOnly, props.impersonate, create, getModel], + [props.readOnly, props.impersonate, create, getModel, editorMounted], ); const appendYAMLString = React.useCallback((yaml) => { @@ -268,11 +266,11 @@ const EditYAMLInner = (props) => { const yaml = convertObjToYAMLString(obj); displayedVersion.current = _.get(obj, 'metadata.resourceVersion'); - getEditor()?.setValue(yaml); + editorMounted && getEditor()?.setValue(yaml); setInitialized(true); setStale(false); }, - [convertObjToYAMLString, initialized, props.obj], + [convertObjToYAMLString, initialized, props.obj, editorMounted], ); const handleCodeReplace = (_event) => { @@ -305,7 +303,7 @@ const EditYAMLInner = (props) => { return; } - const currentYAML = getEditor()?.getValue(); + const currentYAML = editorMounted && getEditor()?.getValue(); if (_.isEmpty(currentYAML) || currentYAML === olsCode) { getEditor()?.setValue(olsCodeBlock?.value); @@ -319,7 +317,7 @@ const EditYAMLInner = (props) => { setOLSCode(olsCodeBlock?.value); // eslint-disable-next-line react-hooks/exhaustive-deps - }, [olsCodeBlock, initialized, isCodeImportRedirect]); + }, [olsCodeBlock, initialized, isCodeImportRedirect, editorMounted]); const handleError = (err, value = null) => { setSuccess(value); @@ -361,9 +359,9 @@ const EditYAMLInner = (props) => { if (props.error) { handleError(props.error); } - loadYaml(); + editorMounted && loadYaml(); loadCSVs(); - }, [loadCSVs, loadYaml, props.error]); + }, [loadCSVs, loadYaml, props.error, editorMounted]); const prevProps = React.useRef(props); @@ -378,13 +376,14 @@ const EditYAMLInner = (props) => { setStale(s); handleError(props.error, success); if (props.sampleObj) { - loadYaml(!_.isEqual(sampleObj, props.sampleObj), props.sampleObj); + editorMounted && loadYaml(!_.isEqual(sampleObj, props.sampleObj), props.sampleObj); } else if (props.fileUpload) { - loadYaml(!_.isEqual(prevProps.current.fileUpload, props.fileUpload), props.fileUpload); + editorMounted && + loadYaml(!_.isEqual(prevProps.current.fileUpload, props.fileUpload), props.fileUpload); } else { - loadYaml(); + editorMounted && loadYaml(); } - }, [props, isOver, loadYaml, sampleObj, success]); + }, [props, isOver, loadYaml, sampleObj, success, editorMounted]); const reload = () => { loadYaml(true); @@ -507,12 +506,12 @@ const EditYAMLInner = (props) => { let obj; if (onSave) { - onSave(getEditor().getValue()); + onSave(editorMounted && getEditor()?.getValue()); return; } try { - obj = safeLoad(getEditor().getValue()); + obj = safeLoad(editorMounted && getEditor()?.getValue()); } catch (e) { handleError(t('public~Error parsing YAML: {{e}}', { e })); return; @@ -581,7 +580,7 @@ const EditYAMLInner = (props) => { } } updateYAML(obj); - }, [create, owner, t, updateYAML, validate, onSave, props.obj]); + }, [create, owner, t, updateYAML, validate, onSave, props.obj, editorMounted]); const save = () => { setErrors([]); @@ -594,7 +593,7 @@ const EditYAMLInner = (props) => { let hasErrors = false; try { - objs = safeLoadAll(getEditor().getValue()).filter((obj) => obj); + objs = safeLoadAll(editorMounted && getEditor()?.getValue()).filter((obj) => obj); } catch (e) { handleError(t('public~Error parsing YAML: {{e}}', { e })); return; @@ -646,7 +645,7 @@ const EditYAMLInner = (props) => { setResourceObjects(objs); setDisplay(true); } - }, [t, setDisplay, validate]); + }, [t, setDisplay, validate, editorMounted]); const saveAll = () => { setErrors([]); @@ -709,6 +708,10 @@ const EditYAMLInner = (props) => { return sanitizedYaml; }; + React.useEffect(() => { + editorMounted && getEditor()?.updateOptions({ hover: { enabled: showTooltips } }); + }, [showTooltips, editorMounted]); + if (!create && !props.obj) { return ; } @@ -717,8 +720,6 @@ const EditYAMLInner = (props) => { 'co-file-dropzone--drop-over': isOver, }); - monacoRef.current?.editor?.updateOptions({ hover: showTooltips }); - if (displayResults) { return ( { toolbarLinks={sidebarLink ? [tooltipCheckBox, sidebarLink] : [tooltipCheckBox]} onChange={onChange} onSave={() => (allowMultiple ? saveAll() : save())} + onEditorDidMount={() => setEditorMounted(true)} />
{customAlerts} From 1472a15070f736bafb3f7b7cab3a7ef770486ebe Mon Sep 17 00:00:00 2001 From: logonoff Date: Fri, 3 Jan 2025 10:04:51 -0500 Subject: [PATCH 025/618] CONSOLE-4407: Use @monaco-editor/react instead of react-monaco-editor e --- frontend/package.json | 3 +- .../src/components/editor/CodeEditor.tsx | 28 +++++++++---------- .../components/editor/yaml-editor-utils.ts | 12 ++++---- .../support/pageObjects/add-flow-po.ts | 2 +- .../buildconfig/sections/EditorField.tsx | 10 +++---- ...ce-from-deployment-or-deployment-config.ts | 10 ++----- .../support/page-objects/pipelines-po.ts | 2 +- .../sidebars/resource-sidebar-samples.tsx | 6 ++-- frontend/yarn.lock | 15 +++------- 9 files changed, 37 insertions(+), 51 deletions(-) diff --git a/frontend/package.json b/frontend/package.json index 55b12cbcbc3..abd4cc81d5c 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -198,7 +198,6 @@ "react-linkify": "^0.2.2", "react-measure": "^2.2.6", "react-modal": "^3.12.1", - "react-monaco-editor": "^0.56.2", "react-redux": "7.2.2", "react-router": "5.3.x", "react-router-dom": "5.3.x", @@ -304,7 +303,7 @@ "mochawesome-merge": "^4.1.0", "mochawesome-report-generator": "^5.1.0", "mock-socket": "^9.0.3", - "monaco-editor": "^0.36.1", + "monaco-editor": "^0.52.2", "monaco-editor-webpack-plugin": "^7.1.0", "prettier": "2.0.5", "react-refresh": "^0.10.0", diff --git a/frontend/packages/console-shared/src/components/editor/CodeEditor.tsx b/frontend/packages/console-shared/src/components/editor/CodeEditor.tsx index 8f042ebabb2..f95c396b826 100644 --- a/frontend/packages/console-shared/src/components/editor/CodeEditor.tsx +++ b/frontend/packages/console-shared/src/components/editor/CodeEditor.tsx @@ -1,7 +1,7 @@ import * as React from 'react'; +import Editor, { OnMount } from '@monaco-editor/react'; import type { editor } from 'monaco-editor/esm/vs/editor/editor.api'; import Measure from 'react-measure'; -import MonacoEditor, { EditorDidMount } from 'react-monaco-editor'; import { CodeEditorRef, CodeEditorProps } from '@console/dynamic-plugin-sdk'; import { ThemeContext } from '@console/internal/components/ThemeProvider'; import CodeEditorToolbar from './CodeEditorToolbar'; @@ -25,7 +25,7 @@ const CodeEditor = React.forwardRef((props, ref) const theme = React.useContext(ThemeContext); const [editorRef, setEditorRef] = React.useState(null); const [usesValue] = React.useState(value !== undefined); - const editorDidMount: EditorDidMount = React.useCallback( + const editorDidMount: OnMount = React.useCallback( (editor, monaco) => { setEditorRef(editor); const currentLanguage = editor.getModel()?.getLanguageId(); @@ -57,7 +57,7 @@ const CodeEditor = React.forwardRef((props, ref) }; }, [options, showMiniMap]); - // use the ref of the editor to expose it to the parent component + // expose the editor instance to the parent component via ref React.useImperativeHandle( ref, () => ({ @@ -72,18 +72,16 @@ const CodeEditor = React.forwardRef((props, ref) {({ measureRef, contentRect }) => (
-
- -
+
)}
diff --git a/frontend/packages/console-shared/src/components/editor/yaml-editor-utils.ts b/frontend/packages/console-shared/src/components/editor/yaml-editor-utils.ts index 5b1b193adfb..3e9c704a2b2 100644 --- a/frontend/packages/console-shared/src/components/editor/yaml-editor-utils.ts +++ b/frontend/packages/console-shared/src/components/editor/yaml-editor-utils.ts @@ -96,15 +96,15 @@ export const registerYAMLinMonaco = ( alreadyInUse: boolean = false, ) => { /** - * This exists because react-monaco-editor passes the same monaco - * object each time. Without it you would be registering all the features again and - * getting duplicate results. + * This exists because we enabled globalAPI in the webpack config. This means that the + * the monaco instance may have already been setup with the YAML language features. + * Otherwise, you would register all the features again, getting duplicate results. * - * Monaco does not provide any apis for unregistering or checking if the features have already + * Monaco does not provide any APIs for unregistering or checking if the features have already * been registered for a language. * - * We check that > 1 YAML language exists because one is the default and one is the initial register - * that setups our features. + * We check that > 1 YAML language exists because one is the default and + * the other is the initial register that setups our features. */ if (monacoInstance.languages.getLanguages().filter((x) => x.id === 'yaml').length <= 1) { // Prepare the schema diff --git a/frontend/packages/dev-console/integration-tests/support/pageObjects/add-flow-po.ts b/frontend/packages/dev-console/integration-tests/support/pageObjects/add-flow-po.ts index bd290d32f7e..ba425ac23ba 100644 --- a/frontend/packages/dev-console/integration-tests/support/pageObjects/add-flow-po.ts +++ b/frontend/packages/dev-console/integration-tests/support/pageObjects/add-flow-po.ts @@ -390,7 +390,7 @@ export const channelPO = { }; export const yamlPO = { - yamlEditor: '.react-monaco-editor-container', + yamlEditor: '.ocs-yaml-editor', }; export const uploadJarFilePO = { diff --git a/frontend/packages/dev-console/src/components/buildconfig/sections/EditorField.tsx b/frontend/packages/dev-console/src/components/buildconfig/sections/EditorField.tsx index 4a328f8793e..77f5cf6f502 100644 --- a/frontend/packages/dev-console/src/components/buildconfig/sections/EditorField.tsx +++ b/frontend/packages/dev-console/src/components/buildconfig/sections/EditorField.tsx @@ -1,7 +1,7 @@ import * as React from 'react'; +import Editor, { OnChange, EditorProps } from '@monaco-editor/react'; import { FormGroup, FormHelperText, HelperText, HelperTextItem } from '@patternfly/react-core'; import { FormikValues, useFormikContext } from 'formik'; -import MonacoEditor, { ChangeHandler, MonacoEditorProps } from 'react-monaco-editor'; import { ThemeContext } from '@console/internal/components/ThemeProvider'; import { RedExclamationCircleIcon, useDebounceCallback } from '@console/shared/src'; @@ -11,7 +11,7 @@ type EditorFieldProps = { helpText?: React.ReactNode; required?: boolean; isDisabled?: boolean; -} & MonacoEditorProps; +} & EditorProps; const EditorField: React.FC = ({ name, @@ -26,7 +26,7 @@ const EditorField: React.FC = ({ const { getFieldMeta, setFieldValue, setFieldTouched } = useFormikContext(); const { error, value } = getFieldMeta(name); - const debouncedOnChange = useDebounceCallback((newValue, event) => { + const debouncedOnChange = useDebounceCallback((newValue, event) => { if (onChange) { onChange(newValue, event); } @@ -36,11 +36,11 @@ const EditorField: React.FC = ({ return ( - diff --git a/frontend/packages/knative-plugin/integration-tests/support/step-definitions/serverless/create-knative-service-from-deployment-or-deployment-config.ts b/frontend/packages/knative-plugin/integration-tests/support/step-definitions/serverless/create-knative-service-from-deployment-or-deployment-config.ts index 29f4be684c6..04f2f1d47ad 100644 --- a/frontend/packages/knative-plugin/integration-tests/support/step-definitions/serverless/create-knative-service-from-deployment-or-deployment-config.ts +++ b/frontend/packages/knative-plugin/integration-tests/support/step-definitions/serverless/create-knative-service-from-deployment-or-deployment-config.ts @@ -213,18 +213,14 @@ When( ); Then('user is able to see value of {string} as {string}', (util: string, utilValue: string) => { - cy.get('.ocs-yaml-editor__wrapper').contains(`${util}: '${utilValue}'`).should('be.visible'); + cy.get('.ocs-yaml-editor').contains(`${util}: '${utilValue}'`).should('be.visible'); }); Then( 'user is able to see the value of {string} and {string} as {string} and {string} percent respectively', (maxScale: string, minScale: string, minScaleValue: string, maxScaleValue: string) => { - cy.get('.ocs-yaml-editor__wrapper') - .contains(`${maxScale}: '${maxScaleValue}'`) - .should('be.visible'); + cy.get('.ocs-yaml-editor').contains(`${maxScale}: '${maxScaleValue}'`).should('be.visible'); - cy.get('.ocs-yaml-editor__wrapper') - .contains(`${minScale}: '${minScaleValue}'`) - .should('be.visible'); + cy.get('.ocs-yaml-editor').contains(`${minScale}: '${minScaleValue}'`).should('be.visible'); }, ); diff --git a/frontend/packages/pipelines-plugin/integration-tests/support/page-objects/pipelines-po.ts b/frontend/packages/pipelines-plugin/integration-tests/support/page-objects/pipelines-po.ts index fcc77cf9441..f1c336b01e3 100644 --- a/frontend/packages/pipelines-plugin/integration-tests/support/page-objects/pipelines-po.ts +++ b/frontend/packages/pipelines-plugin/integration-tests/support/page-objects/pipelines-po.ts @@ -57,7 +57,7 @@ export const pipelineBuilderPO = { }, yamlView: { switchToYAMLView: '[id="form-radiobutton-editorType-yaml-field"]', - editor: 'div.react-monaco-editor-container', + editor: 'div.ocs-yaml-editor', yamlEditor: 'div.monaco-scrollable-element.editor-scrollable.vs-dark', sideBar: '[data-test="resource-sidebar"]', createButton: '[data-test-id="submit-button"]', diff --git a/frontend/public/components/sidebars/resource-sidebar-samples.tsx b/frontend/public/components/sidebars/resource-sidebar-samples.tsx index 77aa398ae76..84a3467b84f 100644 --- a/frontend/public/components/sidebars/resource-sidebar-samples.tsx +++ b/frontend/public/components/sidebars/resource-sidebar-samples.tsx @@ -1,7 +1,7 @@ import * as _ from 'lodash-es'; import * as React from 'react'; import { Button, Level, LevelItem, Title } from '@patternfly/react-core'; -import MonacoEditor from 'react-monaco-editor'; +import Editor from '@monaco-editor/react'; import { ThemeContext } from '@console/internal/components/ThemeProvider'; import { ChevronDownIcon } from '@patternfly/react-icons/dist/esm/icons/chevron-down-icon'; import { ChevronRightIcon } from '@patternfly/react-icons/dist/esm/icons/chevron-right-icon'; @@ -64,11 +64,11 @@ const PreviewYAML = ({ maxPreviewLines = 20, yaml }) => { return (
- Date: Fri, 3 Jan 2025 11:59:42 -0500 Subject: [PATCH 026/618] CONSOLE-4407: Restore custom monaco theme --- frontend/package.json | 2 +- .../src/extensions/console-types.ts | 9 ++- .../src/components/editor/CodeEditor.tsx | 16 +++-- .../src/components/editor/theme.ts | 65 +++++++++++++++++++ .../buildconfig/sections/EditorField.tsx | 11 ++-- .../sidebars/resource-sidebar-samples.tsx | 9 +-- frontend/yarn.lock | 8 +-- 7 files changed, 99 insertions(+), 21 deletions(-) create mode 100644 frontend/packages/console-shared/src/components/editor/theme.ts diff --git a/frontend/package.json b/frontend/package.json index abd4cc81d5c..3e3f423d7c7 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -303,7 +303,7 @@ "mochawesome-merge": "^4.1.0", "mochawesome-report-generator": "^5.1.0", "mock-socket": "^9.0.3", - "monaco-editor": "^0.52.2", + "monaco-editor": "^0.36.1", "monaco-editor-webpack-plugin": "^7.1.0", "prettier": "2.0.5", "react-refresh": "^0.10.0", diff --git a/frontend/packages/console-dynamic-plugin-sdk/src/extensions/console-types.ts b/frontend/packages/console-dynamic-plugin-sdk/src/extensions/console-types.ts index 9953e17242d..bdcc6012ccf 100644 --- a/frontend/packages/console-dynamic-plugin-sdk/src/extensions/console-types.ts +++ b/frontend/packages/console-dynamic-plugin-sdk/src/extensions/console-types.ts @@ -3,7 +3,7 @@ import { QuickStartContextValues } from '@patternfly/quickstarts'; import { ButtonProps } from '@patternfly/react-core'; import { ICell, OnSelect, SortByDirection, TableGridBreakpoint } from '@patternfly/react-table'; import { LocationDescriptor } from 'history'; -import type { editor } from 'monaco-editor/esm/vs/editor/editor.api'; +import type * as monaco from 'monaco-editor/esm/vs/editor/editor.api'; import { ExtensionK8sGroupKindModel, K8sModel, @@ -644,10 +644,13 @@ export type CodeEditorProps = { toolbarLinks?: React.ReactNodeArray; onChange?: (newValue, event) => void; onSave?: () => void; - onEditorDidMount?: (editor: editor.IStandaloneCodeEditor) => void; + onEditorDidMount?: (editor: monaco.editor.IStandaloneCodeEditor) => void; }; -export type CodeEditorRef = { getEditor: () => editor.IStandaloneCodeEditor }; +export type CodeEditorRef = { + getEditor: () => monaco.editor.IStandaloneCodeEditor; + getMonaco: () => typeof monaco; +}; export type ResourceYAMLEditorProps = { initialResource: string | { [key: string]: any }; diff --git a/frontend/packages/console-shared/src/components/editor/CodeEditor.tsx b/frontend/packages/console-shared/src/components/editor/CodeEditor.tsx index f95c396b826..905ba6e1bf1 100644 --- a/frontend/packages/console-shared/src/components/editor/CodeEditor.tsx +++ b/frontend/packages/console-shared/src/components/editor/CodeEditor.tsx @@ -1,10 +1,10 @@ import * as React from 'react'; import Editor, { OnMount } from '@monaco-editor/react'; -import type { editor } from 'monaco-editor/esm/vs/editor/editor.api'; +import type * as Monaco from 'monaco-editor/esm/vs/editor/editor.api'; import Measure from 'react-measure'; import { CodeEditorRef, CodeEditorProps } from '@console/dynamic-plugin-sdk'; -import { ThemeContext } from '@console/internal/components/ThemeProvider'; import CodeEditorToolbar from './CodeEditorToolbar'; +import { useConsoleMonacoTheme } from './theme'; import { registerYAMLinMonaco, defaultEditorOptions } from './yaml-editor-utils'; import './CodeEditor.scss'; @@ -22,12 +22,17 @@ const CodeEditor = React.forwardRef((props, ref) onEditorDidMount, } = props; - const theme = React.useContext(ThemeContext); - const [editorRef, setEditorRef] = React.useState(null); + const [editorRef, setEditorRef] = React.useState( + null, + ); + const [monacoRef, setMonacoRef] = React.useState(null); + useConsoleMonacoTheme(monacoRef?.editor); + const [usesValue] = React.useState(value !== undefined); const editorDidMount: OnMount = React.useCallback( (editor, monaco) => { setEditorRef(editor); + setMonacoRef(monaco); const currentLanguage = editor.getModel()?.getLanguageId(); editor.layout(); editor.focus(); @@ -62,8 +67,9 @@ const CodeEditor = React.forwardRef((props, ref) ref, () => ({ getEditor: () => editorRef, + getMonaco: () => monacoRef, }), - [editorRef], + [editorRef, monacoRef], ); return ( diff --git a/frontend/packages/console-shared/src/components/editor/theme.ts b/frontend/packages/console-shared/src/components/editor/theme.ts new file mode 100644 index 00000000000..3ca72248cbc --- /dev/null +++ b/frontend/packages/console-shared/src/components/editor/theme.ts @@ -0,0 +1,65 @@ +import { useContext, useEffect, useState } from 'react'; +import type { editor as monacoEditor } from 'monaco-editor/esm/vs/editor/editor.api'; +import { ThemeContext } from '@console/internal/components/ThemeProvider'; + +/** + * Define the themes `console-light` and `console-dark` for an instance of Monaco editor. + */ +const defineThemes = (editor: typeof monacoEditor) => { + editor.defineTheme('console-light', { + base: 'vs', + inherit: true, + colors: { + 'editor.background': '#ffffff', // global_BackgroundColor_light_100 + 'editorGutter.background': '#f5f5f5', // black-150 + 'editorLineNumber.activeForeground': '#151515', // global_Color_dark_100 + 'editorLineNumber.foreground': '#3c3f42', // global_BackgroundColor_dark_200 + }, + rules: [ + { token: 'number', foreground: '486b00' }, // light-green-600 + { token: 'type', foreground: '795600' }, // gold-500 + { token: 'string', foreground: '004080' }, // blue-600 + { token: 'keyword', foreground: '40199a' }, // purple-600 + ], + }); + + editor.defineTheme('console-dark', { + base: 'vs-dark', + inherit: true, + colors: { + 'editor.background': '#151515', // global_BackgroundColor_dark_100 + 'editorGutter.background': '#292e34', // no pf token defined + 'editorLineNumber.activeForeground': '#ffffff', // global_Color_light_100 + 'editorLineNumber.foreground': '#f0f0f0', // global_BackgroundColor_200 + }, + rules: [ + { token: 'number', foreground: 'ace12e' }, // light-green-600 + { token: 'type', foreground: '73bcf7' }, // blue-200 + { token: 'string', foreground: 'f0ab00' }, // gold-400 + { token: 'keyword', foreground: 'cbc1ff' }, // purple-100 + ], + }); +}; + +/** + * Sets the theme of a provided Monaco editor instance based on the current theme. + */ +export const useConsoleMonacoTheme = (editor: typeof monacoEditor | null) => { + const theme = useContext(ThemeContext); + const [themeLoaded, setThemeLoaded] = useState(false); + + useEffect(() => { + if (editor) { + if (!themeLoaded) { + defineThemes(editor); + setThemeLoaded(true); + } + + if (theme === 'light') { + editor.setTheme('console-light'); + } else { + editor.setTheme('console-dark'); + } + } + }, [theme, editor, themeLoaded]); +}; diff --git a/frontend/packages/dev-console/src/components/buildconfig/sections/EditorField.tsx b/frontend/packages/dev-console/src/components/buildconfig/sections/EditorField.tsx index 77f5cf6f502..7ecd9414f7b 100644 --- a/frontend/packages/dev-console/src/components/buildconfig/sections/EditorField.tsx +++ b/frontend/packages/dev-console/src/components/buildconfig/sections/EditorField.tsx @@ -1,9 +1,9 @@ import * as React from 'react'; -import Editor, { OnChange, EditorProps } from '@monaco-editor/react'; +import Editor, { OnChange, EditorProps, Monaco } from '@monaco-editor/react'; import { FormGroup, FormHelperText, HelperText, HelperTextItem } from '@patternfly/react-core'; import { FormikValues, useFormikContext } from 'formik'; -import { ThemeContext } from '@console/internal/components/ThemeProvider'; import { RedExclamationCircleIcon, useDebounceCallback } from '@console/shared/src'; +import { useConsoleMonacoTheme } from '@console/shared/src/components/editor/theme'; type EditorFieldProps = { name: string; @@ -22,9 +22,10 @@ const EditorField: React.FC = ({ onChange, ...otherProps }) => { - const theme = React.useContext(ThemeContext); const { getFieldMeta, setFieldValue, setFieldTouched } = useFormikContext(); const { error, value } = getFieldMeta(name); + const [monaco, setMonaco] = React.useState(null); + useConsoleMonacoTheme(monaco?.editor); const debouncedOnChange = useDebounceCallback((newValue, event) => { if (onChange) { @@ -40,7 +41,9 @@ const EditorField: React.FC = ({ {...otherProps} value={value} onChange={debouncedOnChange} - theme={theme === 'light' ? 'vs-light' : 'vs-dark'} + onMount={(_e, m) => { + setMonaco(m); + }} /> diff --git a/frontend/public/components/sidebars/resource-sidebar-samples.tsx b/frontend/public/components/sidebars/resource-sidebar-samples.tsx index 84a3467b84f..8add9c8a1e1 100644 --- a/frontend/public/components/sidebars/resource-sidebar-samples.tsx +++ b/frontend/public/components/sidebars/resource-sidebar-samples.tsx @@ -1,8 +1,8 @@ import * as _ from 'lodash-es'; import * as React from 'react'; import { Button, Level, LevelItem, Title } from '@patternfly/react-core'; -import Editor from '@monaco-editor/react'; -import { ThemeContext } from '@console/internal/components/ThemeProvider'; +import Editor, { Monaco } from '@monaco-editor/react'; +import { useConsoleMonacoTheme } from '@console/shared/src/components/editor/theme'; import { ChevronDownIcon } from '@patternfly/react-icons/dist/esm/icons/chevron-down-icon'; import { ChevronRightIcon } from '@patternfly/react-icons/dist/esm/icons/chevron-right-icon'; import { DownloadIcon } from '@patternfly/react-icons/dist/esm/icons/download-icon'; @@ -60,7 +60,8 @@ const ResourceSidebarSample: React.FC = ({ const lineHeight = 18; const PreviewYAML = ({ maxPreviewLines = 20, yaml }) => { - const theme = React.useContext(ThemeContext); + const [monaco, setMonaco] = React.useState(null); + useConsoleMonacoTheme(monaco?.editor); return (
@@ -68,7 +69,7 @@ const PreviewYAML = ({ maxPreviewLines = 20, yaml }) => { height={Math.min(yaml.split('\n').length, maxPreviewLines) * lineHeight} language="yaml" value={yaml} - theme={theme === 'light' ? 'vs-light' : 'vs-dark'} + onMount={(_e, m) => setMonaco(m)} options={{ lineHeight, readOnly: true, diff --git a/frontend/yarn.lock b/frontend/yarn.lock index e9bc864c118..eb73225d73f 100644 --- a/frontend/yarn.lock +++ b/frontend/yarn.lock @@ -12382,10 +12382,10 @@ monaco-editor-webpack-plugin@^7.1.0: dependencies: loader-utils "^2.0.2" -monaco-editor@^0.52.2: - version "0.52.2" - resolved "https://registry.yarnpkg.com/monaco-editor/-/monaco-editor-0.52.2.tgz#53c75a6fcc6802684e99fd1b2700299857002205" - integrity sha512-GEQWEZmfkOGLdd3XK8ryrfWz3AIP8YymVXiPHEdewrUq7mh0qrKrfHLNCXcbB6sTnMLnOZ3ztSiKcciFUkIJwQ== +monaco-editor@^0.36.1: + version "0.36.1" + resolved "https://registry.yarnpkg.com/monaco-editor/-/monaco-editor-0.36.1.tgz#aad528c815605307473a1634612946921d8079b5" + integrity sha512-/CaclMHKQ3A6rnzBzOADfwdSJ25BFoFT0Emxsc4zYVyav5SkK9iA6lEtIeuN/oRYbwPgviJT+t3l+sjFa28jYg== monaco-languageserver-types@^0.4.0: version "0.4.0" From 960e1ba0ee5f0dcf2646d69fcb3e0b41223f3dcd Mon Sep 17 00:00:00 2001 From: logonoff Date: Tue, 7 Jan 2025 16:17:48 -0500 Subject: [PATCH 027/618] CONSOLE-4407: maintain backwards compatibility with ref object --- .../packages/console-dynamic-plugin-sdk/README.md | 1 + .../src/extensions/console-types.ts | 4 ++-- .../src/components/editor/CodeEditor.tsx | 4 ++-- .../src/components/editor/CodeEditorSidebar.tsx | 14 +++++++------- .../src/components/editor/yaml-editor-utils.ts | 5 ++--- frontend/public/components/edit-yaml.jsx | 6 ++++-- 6 files changed, 18 insertions(+), 16 deletions(-) diff --git a/frontend/packages/console-dynamic-plugin-sdk/README.md b/frontend/packages/console-dynamic-plugin-sdk/README.md index a4f8c090fd4..4c5136d595a 100644 --- a/frontend/packages/console-dynamic-plugin-sdk/README.md +++ b/frontend/packages/console-dynamic-plugin-sdk/README.md @@ -159,6 +159,7 @@ This section documents notable changes in the Console provided shared modules ac - Removed `@fortawesome/font-awesome` and `openshift-logos-icon`. Plugins should use PatternFly icons from `@patternfly/react-icons` instead. The `fa-spin` class remains but is deprecated and will be removed in the future. Plugins should provide their own CSS to spin icons if needed. +- Upgraded `monaco-editor` to version `0.36.1`. - Removed PatternFly 4.x shared modules. - Upgraded PatternFly to v6. - Removed styling for generic HTML heading elements (e.g., `

`). Use PatternFly components to achieve correct styling. diff --git a/frontend/packages/console-dynamic-plugin-sdk/src/extensions/console-types.ts b/frontend/packages/console-dynamic-plugin-sdk/src/extensions/console-types.ts index bdcc6012ccf..50f2314e927 100644 --- a/frontend/packages/console-dynamic-plugin-sdk/src/extensions/console-types.ts +++ b/frontend/packages/console-dynamic-plugin-sdk/src/extensions/console-types.ts @@ -648,8 +648,8 @@ export type CodeEditorProps = { }; export type CodeEditorRef = { - getEditor: () => monaco.editor.IStandaloneCodeEditor; - getMonaco: () => typeof monaco; + editor: monaco.editor.IStandaloneCodeEditor; + monaco: typeof monaco; }; export type ResourceYAMLEditorProps = { diff --git a/frontend/packages/console-shared/src/components/editor/CodeEditor.tsx b/frontend/packages/console-shared/src/components/editor/CodeEditor.tsx index 905ba6e1bf1..b4cecfa32cc 100644 --- a/frontend/packages/console-shared/src/components/editor/CodeEditor.tsx +++ b/frontend/packages/console-shared/src/components/editor/CodeEditor.tsx @@ -66,8 +66,8 @@ const CodeEditor = React.forwardRef((props, ref) React.useImperativeHandle( ref, () => ({ - getEditor: () => editorRef, - getMonaco: () => monacoRef, + editor: editorRef, + monaco: monacoRef, }), [editorRef, monacoRef], ); diff --git a/frontend/packages/console-shared/src/components/editor/CodeEditorSidebar.tsx b/frontend/packages/console-shared/src/components/editor/CodeEditorSidebar.tsx index 9fb3df0e86f..2382c941233 100644 --- a/frontend/packages/console-shared/src/components/editor/CodeEditorSidebar.tsx +++ b/frontend/packages/console-shared/src/components/editor/CodeEditorSidebar.tsx @@ -28,13 +28,13 @@ const CodeEditorSidebar: React.FC = ({ sanitizeYamlContent, toggleSidebar, }) => { - const getEditor = React.useCallback(() => editorRef?.current?.getEditor(), [editorRef]); + const editor = editorRef.current?.editor; const insertYamlContent = React.useCallback( (id: string = 'default', yamlContent: string = '', kind) => { const yaml = sanitizeYamlContent ? sanitizeYamlContent(id, yamlContent, kind) : yamlContent; - const selection = getEditor()?.getSelection(); + const selection = editor?.getSelection(); const range = new Range( selection.startLineNumber, selection.startColumn, @@ -64,18 +64,18 @@ const CodeEditorSidebar: React.FC = ({ ); const op = { range, text: indentedText, forceMoveMarkers: true }; - getEditor()?.executeEdits(id, [op], [newContentSelection]); - getEditor()?.focus(); + editor?.executeEdits(id, [op], [newContentSelection]); + editor?.focus(); }, - [sanitizeYamlContent, getEditor], + [sanitizeYamlContent, editor], ); const replaceYamlContent = React.useCallback( (id: string = 'default', yamlContent: string = '', kind: string) => { const yaml = sanitizeYamlContent ? sanitizeYamlContent(id, yamlContent, kind) : yamlContent; - getEditor()?.setValue(yaml); + editor?.setValue(yaml); }, - [sanitizeYamlContent, getEditor], + [sanitizeYamlContent, editor], ); const downloadYamlContent = React.useCallback( diff --git a/frontend/packages/console-shared/src/components/editor/yaml-editor-utils.ts b/frontend/packages/console-shared/src/components/editor/yaml-editor-utils.ts index 3e9c704a2b2..e147edd76a5 100644 --- a/frontend/packages/console-shared/src/components/editor/yaml-editor-utils.ts +++ b/frontend/packages/console-shared/src/components/editor/yaml-editor-utils.ts @@ -70,10 +70,10 @@ export const fold = ( */ export const registerAutoFold = ( editor: monaco.editor.IStandaloneCodeEditor, - model: monaco.editor.ITextModel, alreadyInUse: boolean = false, ) => { let initialFoldingTriggered = false; + const model = editor.getModel(); const tryFolding = () => { const document = model.getValue(); if (!initialFoldingTriggered && document !== '') { @@ -131,7 +131,6 @@ export const registerYAMLinMonaco = ( } if (!alreadyInUse) { - const model = editor.getModel(); - registerAutoFold(editor, model); + registerAutoFold(editor); } }; diff --git a/frontend/public/components/edit-yaml.jsx b/frontend/public/components/edit-yaml.jsx index 4bd5e07e9bc..9503b9660ce 100644 --- a/frontend/public/components/edit-yaml.jsx +++ b/frontend/public/components/edit-yaml.jsx @@ -146,7 +146,9 @@ const EditYAMLInner = (props) => { const onCancel = 'onCancel' in props ? props.onCancel : navigateBack; /** @return {import('monaco-editor').editor.IStandaloneCodeEditor | null} */ - const getEditor = () => monacoRef.current?.getEditor(); + const getEditor = () => { + return monacoRef.current?.editor; + }; const getModel = React.useCallback( (obj) => { @@ -372,7 +374,7 @@ const EditYAMLInner = (props) => { } const newVersion = _.get(props.obj, 'metadata.resourceVersion'); - const s = displayedVersion.current !== newVersion; + const s = displayedVersion.current !== newVersion && editorMounted; setStale(s); handleError(props.error, success); if (props.sampleObj) { From f0984bc63092e3908606d5e8ed8e0cf2815e3f9c Mon Sep 17 00:00:00 2001 From: logonoff Date: Wed, 5 Feb 2025 16:25:43 -0500 Subject: [PATCH 028/618] CONSOLE-4407: remove `net` fallback --- frontend/webpack.config.ts | 3 --- 1 file changed, 3 deletions(-) diff --git a/frontend/webpack.config.ts b/frontend/webpack.config.ts index ddc1142b1f5..c6dfe06764d 100644 --- a/frontend/webpack.config.ts +++ b/frontend/webpack.config.ts @@ -95,9 +95,6 @@ const config: Configuration = { prettier: false, 'prettier/parser-yaml': false, }, - fallback: { - net: false, // for YAML language server - }, }, node: { global: true, // see https://github.com/browserify/randombytes/issues/36 From a2271fc8a2ab0d5373e88c0bfd2dd3c54ed06c63 Mon Sep 17 00:00:00 2001 From: logonoff Date: Fri, 7 Feb 2025 11:33:25 -0500 Subject: [PATCH 029/618] CONSOLE-4407: Use PF6 tokens for editor theme also verified that these new colors meet WCAG AAA standards for color contrast --- .../src/components/editor/theme.ts | 45 ++++++++++++------- 1 file changed, 29 insertions(+), 16 deletions(-) diff --git a/frontend/packages/console-shared/src/components/editor/theme.ts b/frontend/packages/console-shared/src/components/editor/theme.ts index 3ca72248cbc..5dabd23d8c4 100644 --- a/frontend/packages/console-shared/src/components/editor/theme.ts +++ b/frontend/packages/console-shared/src/components/editor/theme.ts @@ -1,4 +1,19 @@ import { useContext, useEffect, useState } from 'react'; +import { + t_color_green_70, + t_color_yellow_70, + t_color_blue_70, + t_color_purple_70, + t_color_green_30, + t_color_blue_30, + t_color_yellow_30, + t_color_purple_30, + t_color_white, + t_color_gray_20, + t_color_gray_60, + t_color_gray_90, + t_color_black, +} from '@patternfly/react-tokens'; import type { editor as monacoEditor } from 'monaco-editor/esm/vs/editor/editor.api'; import { ThemeContext } from '@console/internal/components/ThemeProvider'; @@ -10,16 +25,15 @@ const defineThemes = (editor: typeof monacoEditor) => { base: 'vs', inherit: true, colors: { - 'editor.background': '#ffffff', // global_BackgroundColor_light_100 - 'editorGutter.background': '#f5f5f5', // black-150 - 'editorLineNumber.activeForeground': '#151515', // global_Color_dark_100 - 'editorLineNumber.foreground': '#3c3f42', // global_BackgroundColor_dark_200 + 'editor.background': t_color_white.value, + 'editorLineNumber.activeForeground': t_color_black.value, + 'editorLineNumber.foreground': t_color_gray_60.value, }, rules: [ - { token: 'number', foreground: '486b00' }, // light-green-600 - { token: 'type', foreground: '795600' }, // gold-500 - { token: 'string', foreground: '004080' }, // blue-600 - { token: 'keyword', foreground: '40199a' }, // purple-600 + { token: 'number', foreground: t_color_green_70.value }, + { token: 'type', foreground: t_color_yellow_70.value }, + { token: 'string', foreground: t_color_blue_70.value }, + { token: 'keyword', foreground: t_color_purple_70.value }, ], }); @@ -27,16 +41,15 @@ const defineThemes = (editor: typeof monacoEditor) => { base: 'vs-dark', inherit: true, colors: { - 'editor.background': '#151515', // global_BackgroundColor_dark_100 - 'editorGutter.background': '#292e34', // no pf token defined - 'editorLineNumber.activeForeground': '#ffffff', // global_Color_light_100 - 'editorLineNumber.foreground': '#f0f0f0', // global_BackgroundColor_200 + 'editor.background': t_color_gray_90.value, + 'editorLineNumber.activeForeground': t_color_white.value, + 'editorLineNumber.foreground': t_color_gray_20.value, }, rules: [ - { token: 'number', foreground: 'ace12e' }, // light-green-600 - { token: 'type', foreground: '73bcf7' }, // blue-200 - { token: 'string', foreground: 'f0ab00' }, // gold-400 - { token: 'keyword', foreground: 'cbc1ff' }, // purple-100 + { token: 'number', foreground: t_color_green_30.value }, + { token: 'type', foreground: t_color_blue_30.value }, + { token: 'string', foreground: t_color_yellow_30.value }, + { token: 'keyword', foreground: t_color_purple_30.value }, ], }); }; From 1b226ec1f46419ef218c8ec59450b44a90fc733b Mon Sep 17 00:00:00 2001 From: logonoff Date: Fri, 7 Feb 2025 15:15:04 -0500 Subject: [PATCH 030/618] CONSOLE-4409: Refactor CodeEditor to use the PF equivalent --- frontend/package.json | 2 +- .../src/extensions/console-types.ts | 6 +- .../locales/en/console-shared.json | 5 +- .../src/components/editor/CodeEditor.scss | 48 +++++++++-- .../src/components/editor/CodeEditor.tsx | 61 +++++++++----- .../components/editor/CodeEditorToolbar.scss | 16 ---- .../components/editor/CodeEditorToolbar.tsx | 79 +++++++----------- .../src/components/editor/ShortcutsLink.tsx | 83 +++++++------------ .../__tests__/CodeEditorToolbar.spec.tsx | 6 -- .../src/components/editor/theme.ts | 28 ++++++- .../formik-fields/CodeEditorField.tsx | 1 - .../buildconfig/sections/EditorField.tsx | 24 ++++-- .../buildconfig/sections/HooksSection.tsx | 4 - .../buildconfig/sections/SourceSection.tsx | 9 +- .../views/yaml-editor.ts | 22 +++-- frontend/public/components/_edit-yaml.scss | 16 ---- frontend/public/components/edit-yaml.jsx | 10 +-- .../sidebars/resource-sidebar-samples.tsx | 13 +-- frontend/public/locales/en/public.json | 2 +- frontend/scripts/check-patternfly-modules.sh | 1 + frontend/yarn.lock | 20 ++++- 21 files changed, 239 insertions(+), 217 deletions(-) delete mode 100644 frontend/packages/console-shared/src/components/editor/CodeEditorToolbar.scss diff --git a/frontend/package.json b/frontend/package.json index 3e3f423d7c7..a843e96f863 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -132,12 +132,12 @@ "resolver": "./jest-resolver.js" }, "dependencies": { - "@monaco-editor/react": "^4.6.0", "@patternfly-5/patternfly": "npm:@patternfly/patternfly@5.4.2", "@patternfly/patternfly": "^6.2.0-prerelease.2", "@patternfly/quickstarts": "^6.2.0-prerelease.4", "@patternfly/react-catalog-view-extension": "^6.1.0-prerelease.3", "@patternfly/react-charts": "^8.2.0-prerelease.13", + "@patternfly/react-code-editor": "^6.1.0", "@patternfly/react-component-groups": "^6.2.0-prerelease.4", "@patternfly/react-console": "^6.0.0", "@patternfly/react-core": "^6.2.0-prerelease.15", diff --git a/frontend/packages/console-dynamic-plugin-sdk/src/extensions/console-types.ts b/frontend/packages/console-dynamic-plugin-sdk/src/extensions/console-types.ts index 50f2314e927..430198a4077 100644 --- a/frontend/packages/console-dynamic-plugin-sdk/src/extensions/console-types.ts +++ b/frontend/packages/console-dynamic-plugin-sdk/src/extensions/console-types.ts @@ -1,5 +1,6 @@ import * as React from 'react'; import { QuickStartContextValues } from '@patternfly/quickstarts'; +import { Language } from '@patternfly/react-code-editor'; import { ButtonProps } from '@patternfly/react-core'; import { ICell, OnSelect, SortByDirection, TableGridBreakpoint } from '@patternfly/react-table'; import { LocationDescriptor } from 'history'; @@ -636,7 +637,7 @@ export type UserInfo = { export type CodeEditorProps = { value?: string; - language?: string; + language?: Language; options?: object; minHeight?: string | number; showShortcuts?: boolean; @@ -645,6 +646,9 @@ export type CodeEditorProps = { onChange?: (newValue, event) => void; onSave?: () => void; onEditorDidMount?: (editor: monaco.editor.IStandaloneCodeEditor) => void; + isDownloadEnabled?: boolean; + isCopyEnabled?: boolean; + isLanguageLabelVisible?: boolean; }; export type CodeEditorRef = { diff --git a/frontend/packages/console-shared/locales/en/console-shared.json b/frontend/packages/console-shared/locales/en/console-shared.json index 6d30c3f5891..6c8ff950c35 100644 --- a/frontend/packages/console-shared/locales/en/console-shared.json +++ b/frontend/packages/console-shared/locales/en/console-shared.json @@ -108,6 +108,10 @@ "true": "true", "Select {{label}}": "Select {{label}}", "Cluster does not have resource {{groupVersionKind}}": "Cluster does not have resource {{groupVersionKind}}", + "View shortcuts": "View shortcuts", + "Copy code to clipboard": "Copy code to clipboard", + "Content copied to clipboard": "Content copied to clipboard", + "Download code": "Download code", "Ask OpenShift Lightspeed": "Ask OpenShift Lightspeed", "Accessibility help": "Accessibility help", "Shortcuts": "Shortcuts", @@ -117,7 +121,6 @@ "View document outline": "View document outline", "View property descriptions": "View property descriptions", "Save": "Save", - "View shortcuts": "View shortcuts", "Restricted access": "Restricted access", "You don't have access to this section due to cluster policy": "You don't have access to this section due to cluster policy", "Error details": "Error details", diff --git a/frontend/packages/console-shared/src/components/editor/CodeEditor.scss b/frontend/packages/console-shared/src/components/editor/CodeEditor.scss index 51a1c002fce..37d5eaabb78 100644 --- a/frontend/packages/console-shared/src/components/editor/CodeEditor.scss +++ b/frontend/packages/console-shared/src/components/editor/CodeEditor.scss @@ -1,14 +1,44 @@ +@import '../../../../../public/style/vars'; + +// match the token in theme.ts. it already matches for light theme +.pf-v6-theme-dark { + .pf-v6-c-code-editor__main { + --pf-v6-c-code-editor__main--BackgroundColor: var(--pf-t--color--gray--90); + } + + .pf-v6-c-code-editor__tab { + --pf-v6-c-code-editor__tab--BackgroundColor: var(--pf-t--color--gray--90); + } +} + .ocs-yaml-editor { - &__root { - flex: 1; - position: relative; + // makes automaticLayout work properly + .monaco-editor { + position: absolute !important; } - &__wrapper { - position: absolute; - top: 0; - right: 0; - left: 0; - bottom: 0; + .pf-v6-c-code-editor__controls, .ocs-yaml-editor-toolbar { + // needed to get the CodeEditorToolbar to place buttons on the right side + width: 100%; + height: 100% !important; + } + + .ocs-yaml-editor-toolbar { + align-items: center; + + .ocs-yaml-editor-toolbar__link { + @media (max-width: $screen-sm-max) { + display: none; + } + } + + .ocs-yaml-editor-toolbar__shortcuts { + padding: var(--pf-t--global--spacer--md) var(--pf-v6-global-gutter); + + .ocs-yaml-editor-shortcut__text { + color: var(--pf-t--global--text--color--subtle); + margin-left: var(--pf-t--global--spacer--sm); + } + } } } diff --git a/frontend/packages/console-shared/src/components/editor/CodeEditor.tsx b/frontend/packages/console-shared/src/components/editor/CodeEditor.tsx index b4cecfa32cc..21c7fc38f21 100644 --- a/frontend/packages/console-shared/src/components/editor/CodeEditor.tsx +++ b/frontend/packages/console-shared/src/components/editor/CodeEditor.tsx @@ -1,9 +1,10 @@ import * as React from 'react'; -import Editor, { OnMount } from '@monaco-editor/react'; +import { CodeEditor as PfEditor, Language } from '@patternfly/react-code-editor'; import type * as Monaco from 'monaco-editor/esm/vs/editor/editor.api'; -import Measure from 'react-measure'; +import { useTranslation } from 'react-i18next'; import { CodeEditorRef, CodeEditorProps } from '@console/dynamic-plugin-sdk'; import CodeEditorToolbar from './CodeEditorToolbar'; +import { useShortcutLink } from './ShortcutsLink'; import { useConsoleMonacoTheme } from './theme'; import { registerYAMLinMonaco, defaultEditorOptions } from './yaml-editor-utils'; import './CodeEditor.scss'; @@ -20,8 +21,12 @@ const CodeEditor = React.forwardRef((props, ref) onSave, language, onEditorDidMount, + isDownloadEnabled, + isCopyEnabled, + isLanguageLabelVisible, } = props; - + const { t } = useTranslation('console-shared'); + const shortcutPopover = useShortcutLink(); const [editorRef, setEditorRef] = React.useState( null, ); @@ -29,7 +34,7 @@ const CodeEditor = React.forwardRef((props, ref) useConsoleMonacoTheme(monacoRef?.editor); const [usesValue] = React.useState(value !== undefined); - const editorDidMount: OnMount = React.useCallback( + const editorDidMount = React.useCallback( (editor, monaco) => { setEditorRef(editor); setMonacoRef(monaco); @@ -59,6 +64,7 @@ const CodeEditor = React.forwardRef((props, ref) minimap: { enabled: showMiniMap, }, + automaticLayout: true, }; }, [options, showMiniMap]); @@ -72,26 +78,35 @@ const CodeEditor = React.forwardRef((props, ref) [editorRef, monacoRef], ); + const ToolbarLinks = React.useMemo(() => { + // fixes PF bug where empty toolbar renders if a component is null + if (!showShortcuts && !toolbarLinks?.length) return undefined; + + return ; + }, [toolbarLinks, showShortcuts]); + return ( - <> - - - {({ measureRef, contentRect }) => ( -
- -
- )} -
- + ); }); diff --git a/frontend/packages/console-shared/src/components/editor/CodeEditorToolbar.scss b/frontend/packages/console-shared/src/components/editor/CodeEditorToolbar.scss deleted file mode 100644 index 3e4ccd523e4..00000000000 --- a/frontend/packages/console-shared/src/components/editor/CodeEditorToolbar.scss +++ /dev/null @@ -1,16 +0,0 @@ -@import '../../../../../public/style/vars'; - -.ocs-yaml-editor-shortcut__text { - color: var(--pf-t--global--text--color--subtle); - margin-left: var(--pf-t--global--spacer--sm); -} - -.ocs-yaml-editor-toolbar { - align-items: center; -} - -@media (max-width: $screen-sm-max) { - .ocs-yaml-editor-toolbar__link { - display: none; - } -} diff --git a/frontend/packages/console-shared/src/components/editor/CodeEditorToolbar.tsx b/frontend/packages/console-shared/src/components/editor/CodeEditorToolbar.tsx index cdb8c79a2f0..be439ea830e 100644 --- a/frontend/packages/console-shared/src/components/editor/CodeEditorToolbar.tsx +++ b/frontend/packages/console-shared/src/components/editor/CodeEditorToolbar.tsx @@ -1,5 +1,5 @@ import * as React from 'react'; -import { Button, Divider } from '@patternfly/react-core'; +import { Flex, FlexItem, Button, Divider } from '@patternfly/react-core'; import { MagicIcon } from '@patternfly/react-icons'; import { useTranslation } from 'react-i18next'; // eslint-disable-next-line @typescript-eslint/ban-ts-comment @@ -9,9 +9,6 @@ import { action } from 'typesafe-actions'; import { ActionType } from '@console/internal/reducers/ols'; import { useOLSConfig } from '../../hooks/ols-hook'; import { isMac, ShortcutCommand } from '../shortcuts/Shortcut'; -import ShortcutsLink from './ShortcutsLink'; - -import './CodeEditorToolbar.scss'; interface CodeEditorToolbarProps { showShortcuts?: boolean; @@ -25,52 +22,28 @@ const CodeEditorToolbar: React.FC = ({ showShortcuts, to const dispatch = useDispatch(); if (!showShortcuts && !toolbarLinks?.length) return null; return ( -
-
+ + {showLightspeedButton && ( -
- - -
- )} -
-
- - {isMac ? '⌥ Opt' : 'Alt'} - F1 - - - {t('console-shared~Accessibility help')} - -
-
- {showShortcuts && ( -
- - -
+ )} {toolbarLinks && toolbarLinks.map((link, index) => ( // eslint-disable-next-line react/no-array-index-key -
+
{(showShortcuts || index > 0) && link ? ( = ({ showShortcuts, to }} /> ) : null} -
{link}
+ {link}
))} -
-
+ + + + + {isMac ? '⌥ Opt' : 'Alt'} + F1 + + + {t('console-shared~Accessibility help')} + + + ); }; export default CodeEditorToolbar; diff --git a/frontend/packages/console-shared/src/components/editor/ShortcutsLink.tsx b/frontend/packages/console-shared/src/components/editor/ShortcutsLink.tsx index 526ead1e407..709364e1c8c 100644 --- a/frontend/packages/console-shared/src/components/editor/ShortcutsLink.tsx +++ b/frontend/packages/console-shared/src/components/editor/ShortcutsLink.tsx @@ -1,58 +1,39 @@ import * as React from 'react'; -import { Popover, Button } from '@patternfly/react-core'; -import { QuestionCircleIcon } from '@patternfly/react-icons/dist/esm/icons/question-circle-icon'; +import { PopoverProps } from '@patternfly/react-core'; import { useTranslation } from 'react-i18next'; import { ShortcutTable, Shortcut } from '../shortcuts'; import { isMac } from '../shortcuts/Shortcut'; -interface ShortcutsLinkProps { - onHideShortcuts?: () => {}; -} - -const ShortcutsLink: React.FC = ({ onHideShortcuts }) => { +export const useShortcutLink = (onHideShortcuts?: () => {}): PopoverProps => { const { t } = useTranslation(); - return ( - - - {t('console-shared~Accessibility help')} - - {t('console-shared~View all editor shortcuts')} - - {t('console-shared~Activate auto complete')} - - - {t( - 'console-shared~Toggle Tab action between insert Tab character and move focus out of editor', - )} - - - {t('console-shared~View document outline')} - - {t('console-shared~View property descriptions')} - - {t('console-shared~Save')} - - - } - maxWidth="25rem" - distance={18} - onHide={onHideShortcuts} - > - - - ); -}; -export default ShortcutsLink; + return { + 'aria-label': t('console-shared~Shortcuts'), + bodyContent: ( + + + {t('console-shared~Accessibility help')} + + {t('console-shared~View all editor shortcuts')} + + {t('console-shared~Activate auto complete')} + + + {t( + 'console-shared~Toggle Tab action between insert Tab character and move focus out of editor', + )} + + + {t('console-shared~View document outline')} + + {t('console-shared~View property descriptions')} + + {t('console-shared~Save')} + + + ), + maxWidth: '25rem', + distance: 18, + onHide: onHideShortcuts, + }; +}; diff --git a/frontend/packages/console-shared/src/components/editor/__tests__/CodeEditorToolbar.spec.tsx b/frontend/packages/console-shared/src/components/editor/__tests__/CodeEditorToolbar.spec.tsx index 9392ebca58d..83aa9ee1b78 100644 --- a/frontend/packages/console-shared/src/components/editor/__tests__/CodeEditorToolbar.spec.tsx +++ b/frontend/packages/console-shared/src/components/editor/__tests__/CodeEditorToolbar.spec.tsx @@ -8,7 +8,6 @@ import { useDispatch } from 'react-redux'; import { ActionType } from '@console/internal/reducers/ols'; import { useOLSConfig } from '../../../hooks/ols-hook'; import CodeEditorToolbar from '../CodeEditorToolbar'; -import ShortcutsLink from '../ShortcutsLink'; jest.mock('react-i18next', () => ({ useTranslation: jest.fn(), @@ -37,11 +36,6 @@ describe('CodeEditorToolbar', () => { expect(wrapper.isEmptyRender()).toBe(true); }); - it('should render toolbar with shortcuts when showShortcuts is true', () => { - wrapper = shallow(); - expect(wrapper.find(ShortcutsLink).exists()).toBe(true); - }); - it('should render toolbar with custom links when toolbarLinks are provided', () => { const toolbarLinks = [
Custom Link
]; wrapper = shallow(); diff --git a/frontend/packages/console-shared/src/components/editor/theme.ts b/frontend/packages/console-shared/src/components/editor/theme.ts index 5dabd23d8c4..9c157017ce7 100644 --- a/frontend/packages/console-shared/src/components/editor/theme.ts +++ b/frontend/packages/console-shared/src/components/editor/theme.ts @@ -54,10 +54,32 @@ const defineThemes = (editor: typeof monacoEditor) => { }); }; +const useSystemTheme = () => { + const [theme, setTheme] = useState('light'); + + useEffect(() => { + const query = window.matchMedia('(prefers-color-scheme: dark)'); + + const updateTheme = () => { + setTheme(query.matches ? 'dark' : 'light'); + }; + + updateTheme(); + + query.addEventListener('change', updateTheme); + return () => { + query.removeEventListener('change', updateTheme); + }; + }, []); + + return theme; +}; + /** * Sets the theme of a provided Monaco editor instance based on the current theme. */ export const useConsoleMonacoTheme = (editor: typeof monacoEditor | null) => { + const systemTheme = useSystemTheme(); const theme = useContext(ThemeContext); const [themeLoaded, setThemeLoaded] = useState(false); @@ -70,9 +92,11 @@ export const useConsoleMonacoTheme = (editor: typeof monacoEditor | null) => { if (theme === 'light') { editor.setTheme('console-light'); - } else { + } else if (theme === 'dark') { editor.setTheme('console-dark'); + } else if (theme === 'systemDefault') { + editor.setTheme(`console-${systemTheme}`); } } - }, [theme, editor, themeLoaded]); + }, [theme, editor, themeLoaded, systemTheme]); }; diff --git a/frontend/packages/console-shared/src/components/formik-fields/CodeEditorField.tsx b/frontend/packages/console-shared/src/components/formik-fields/CodeEditorField.tsx index 7384d711aa7..ea4e39eea2f 100644 --- a/frontend/packages/console-shared/src/components/formik-fields/CodeEditorField.tsx +++ b/frontend/packages/console-shared/src/components/formik-fields/CodeEditorField.tsx @@ -99,7 +99,6 @@ const CodeEditorField: React.FC = ({ icon={ } - isInline variant="link" onClick={() => setSidebarOpen(true)} > diff --git a/frontend/packages/dev-console/src/components/buildconfig/sections/EditorField.tsx b/frontend/packages/dev-console/src/components/buildconfig/sections/EditorField.tsx index 7ecd9414f7b..6c85c2f17c5 100644 --- a/frontend/packages/dev-console/src/components/buildconfig/sections/EditorField.tsx +++ b/frontend/packages/dev-console/src/components/buildconfig/sections/EditorField.tsx @@ -1,7 +1,8 @@ import * as React from 'react'; -import Editor, { OnChange, EditorProps, Monaco } from '@monaco-editor/react'; +import { CodeEditor, ChangeHandler, Language } from '@patternfly/react-code-editor'; import { FormGroup, FormHelperText, HelperText, HelperTextItem } from '@patternfly/react-core'; import { FormikValues, useFormikContext } from 'formik'; +import type * as Monaco from 'monaco-editor/esm/vs/editor/editor.api'; import { RedExclamationCircleIcon, useDebounceCallback } from '@console/shared/src'; import { useConsoleMonacoTheme } from '@console/shared/src/components/editor/theme'; @@ -11,7 +12,10 @@ type EditorFieldProps = { helpText?: React.ReactNode; required?: boolean; isDisabled?: boolean; -} & EditorProps; + onChange?: ChangeHandler; + language?: Language; + options?: Monaco.editor.IEditorOptions; +}; const EditorField: React.FC = ({ name, @@ -24,10 +28,10 @@ const EditorField: React.FC = ({ }) => { const { getFieldMeta, setFieldValue, setFieldTouched } = useFormikContext(); const { error, value } = getFieldMeta(name); - const [monaco, setMonaco] = React.useState(null); + const [monaco, setMonaco] = React.useState(null); useConsoleMonacoTheme(monaco?.editor); - const debouncedOnChange = useDebounceCallback((newValue, event) => { + const debouncedOnChange = useDebounceCallback((newValue, event) => { if (onChange) { onChange(newValue, event); } @@ -37,13 +41,15 @@ const EditorField: React.FC = ({ return ( - { - setMonaco(m); - }} + onEditorDidMount={(_e, m) => setMonaco(m)} + isReadOnly={false} + isMinimapVisible={false} + height="sizeToFit" + language={otherProps?.language} + options={otherProps?.options} /> diff --git a/frontend/packages/dev-console/src/components/buildconfig/sections/HooksSection.tsx b/frontend/packages/dev-console/src/components/buildconfig/sections/HooksSection.tsx index a69737bbf27..193e223d388 100644 --- a/frontend/packages/dev-console/src/components/buildconfig/sections/HooksSection.tsx +++ b/frontend/packages/dev-console/src/components/buildconfig/sections/HooksSection.tsx @@ -55,12 +55,8 @@ const HooksSection: React.FC<{}> = () => { diff --git a/frontend/packages/dev-console/src/components/buildconfig/sections/SourceSection.tsx b/frontend/packages/dev-console/src/components/buildconfig/sections/SourceSection.tsx index cf18e5ef3d8..b382e2fbe2c 100644 --- a/frontend/packages/dev-console/src/components/buildconfig/sections/SourceSection.tsx +++ b/frontend/packages/dev-console/src/components/buildconfig/sections/SourceSection.tsx @@ -1,4 +1,5 @@ import * as React from 'react'; +import { Language } from '@patternfly/react-code-editor'; import { useField } from 'formik'; import { useTranslation } from 'react-i18next'; import { DropdownField } from '@console/shared'; @@ -33,8 +34,6 @@ const SourceSection: React.FC = () => { }; const lineHeight = 18; - const showLines = 10; // Math.max(3, Math.min(15, dockerfile?.split('/n')?.length)); - const height = lineHeight * showLines; return ( @@ -55,13 +54,9 @@ const SourceSection: React.FC = () => { diff --git a/frontend/packages/integration-tests-cypress/views/yaml-editor.ts b/frontend/packages/integration-tests-cypress/views/yaml-editor.ts index 7323760fc80..bec2f7fe900 100644 --- a/frontend/packages/integration-tests-cypress/views/yaml-editor.ts +++ b/frontend/packages/integration-tests-cypress/views/yaml-editor.ts @@ -1,13 +1,23 @@ +import type { editor } from 'monaco-editor/esm/vs/editor/editor.api'; + export const getEditorContent = () => { - return cy.window().then((win: any) => { - return win.monaco.editor.getModels()[0].getValue(); - }); + return cy + .window() + .its('monaco.editor.getModels') + .should('be.a', 'function') + .then((getModels: typeof editor.getModels) => { + return getModels()[0].getValue(); + }); }; export const setEditorContent = (text: string) => { - return cy.window().then((win: any) => { - win.monaco.editor.getModels()[0].setValue(text); - }); + return cy + .window() + .its('monaco.editor.getModels') + .should('be.a', 'function') + .then((getModels: typeof editor.getModels) => { + return getModels()[0].setValue(text); + }); }; // initially yamlEditor loads with all grey text, finished loading when editor is color coded diff --git a/frontend/public/components/_edit-yaml.scss b/frontend/public/components/_edit-yaml.scss index 5ef25402d7c..d85d07cc5d4 100644 --- a/frontend/public/components/_edit-yaml.scss +++ b/frontend/public/components/_edit-yaml.scss @@ -23,22 +23,6 @@ } } -// only used in packages and will be orphaned when packages move out -.yaml-editor__header { - border-bottom: var(--pf-t--global--border--width--divider--default) solid - var(--pf-t--global--border--color--default); - padding: 20px $pf-v6-global-gutter; - @media (min-width: $pf-v6-global--breakpoint--xl) { - padding-left: $pf-v6-global-gutter--md; - padding-right: $pf-v6-global-gutter--md; - } -} - -// only used in packages and will be orphaned when packages move out -.yaml-editor__header-text { - margin: 0; -} - .yaml-editor__link { display: inline-block; } diff --git a/frontend/public/components/edit-yaml.jsx b/frontend/public/components/edit-yaml.jsx index 9503b9660ce..f1930199427 100644 --- a/frontend/public/components/edit-yaml.jsx +++ b/frontend/public/components/edit-yaml.jsx @@ -8,7 +8,7 @@ import { useDispatch, useSelector, connect } from 'react-redux'; import { action } from 'typesafe-actions'; import { ActionType, getOLSCodeBlock } from '@console/internal/reducers/ols'; import { safeLoad, safeLoadAll, safeDump } from 'js-yaml'; -import { ActionGroup, Alert, Button, Checkbox } from '@patternfly/react-core'; +import { ActionGroup, Alert, Button, Switch } from '@patternfly/react-core'; import { DownloadIcon } from '@patternfly/react-icons/dist/esm/icons/download-icon'; import { InfoCircleIcon } from '@patternfly/react-icons/dist/esm/icons/info-circle-icon'; import { Trans, useTranslation } from 'react-i18next'; @@ -747,21 +747,20 @@ const EditYAMLInner = (props) => { !showSidebar && hasSidebarContent ? ( ) : null; const tooltipCheckBox = ( - ); @@ -807,6 +806,7 @@ const EditYAMLInner = (props) => { onChange={onChange} onSave={() => (allowMultiple ? saveAll() : save())} onEditorDidMount={() => setEditorMounted(true)} + isLanguageLabelVisible />
{customAlerts} diff --git a/frontend/public/components/sidebars/resource-sidebar-samples.tsx b/frontend/public/components/sidebars/resource-sidebar-samples.tsx index 8add9c8a1e1..311303fe856 100644 --- a/frontend/public/components/sidebars/resource-sidebar-samples.tsx +++ b/frontend/public/components/sidebars/resource-sidebar-samples.tsx @@ -1,7 +1,8 @@ import * as _ from 'lodash-es'; import * as React from 'react'; import { Button, Level, LevelItem, Title } from '@patternfly/react-core'; -import Editor, { Monaco } from '@monaco-editor/react'; +import type * as Monaco from 'monaco-editor/esm/vs/editor/editor.api'; +import { CodeEditor, Language } from '@patternfly/react-code-editor'; import { useConsoleMonacoTheme } from '@console/shared/src/components/editor/theme'; import { ChevronDownIcon } from '@patternfly/react-icons/dist/esm/icons/chevron-down-icon'; import { ChevronRightIcon } from '@patternfly/react-icons/dist/esm/icons/chevron-right-icon'; @@ -60,16 +61,16 @@ const ResourceSidebarSample: React.FC = ({ const lineHeight = 18; const PreviewYAML = ({ maxPreviewLines = 20, yaml }) => { - const [monaco, setMonaco] = React.useState(null); + const [monaco, setMonaco] = React.useState(null); useConsoleMonacoTheme(monaco?.editor); return (
- setMonaco(m)} + onEditorDidMount={(_e, m) => setMonaco(m)} options={{ lineHeight, readOnly: true, diff --git a/frontend/public/locales/en/public.json b/frontend/public/locales/en/public.json index 58dacc006b4..edabcc4c0c9 100644 --- a/frontend/public/locales/en/public.json +++ b/frontend/public/locales/en/public.json @@ -486,7 +486,7 @@ "Failed to parse YAML sample": "Failed to parse YAML sample", "An error occurred.": "An error occurred.", "View sidebar": "View sidebar", - "Show tooltips": "Show tooltips", + "Tooltips": "Tooltips", "Drop file here": "Drop file here", "Drag and drop YAML or JSON files into the editor, or manually enter files and use <2>--- to separate each definition.": "Drag and drop YAML or JSON files into the editor, or manually enter files and use <2>--- to separate each definition.", "Create by manually entering YAML or JSON definitions, or by dragging and dropping a file into the editor.": "Create by manually entering YAML or JSON definitions, or by dragging and dropping a file into the editor.", diff --git a/frontend/scripts/check-patternfly-modules.sh b/frontend/scripts/check-patternfly-modules.sh index 07da7e420cf..ba6bf1b8090 100755 --- a/frontend/scripts/check-patternfly-modules.sh +++ b/frontend/scripts/check-patternfly-modules.sh @@ -50,6 +50,7 @@ PKGS_TO_CHECK=( '@patternfly/quickstarts:6' '@patternfly/react-catalog-view-extension:6' '@patternfly/react-charts:8' + '@patternfly/react-code-editor:6' '@patternfly/react-component-groups:6' '@patternfly/react-console:6' '@patternfly/react-core:6' diff --git a/frontend/yarn.lock b/frontend/yarn.lock index eb73225d73f..40ebc73d8aa 100644 --- a/frontend/yarn.lock +++ b/frontend/yarn.lock @@ -1880,6 +1880,18 @@ lodash "^4.17.21" tslib "^2.8.1" +"@patternfly/react-code-editor@^6.1.0": + version "6.1.0" + resolved "https://registry.yarnpkg.com/@patternfly/react-code-editor/-/react-code-editor-6.1.0.tgz#550ddf2cbd3f9f41595d4834731f34a704628936" + integrity sha512-ae04+DdkgXFn3wEzvNCncNa78ZK3Swh5ng8p7yqFrD6lhW69NoJf+DdQlHi8gM8Qy05DNnIemSbQWpWLpInyzw== + dependencies: + "@monaco-editor/react" "^4.6.0" + "@patternfly/react-core" "^6.1.0" + "@patternfly/react-icons" "^6.1.0" + "@patternfly/react-styles" "^6.1.0" + react-dropzone "14.3.5" + tslib "^2.8.1" + "@patternfly/react-component-groups@^6.2.0-prerelease.4": version "6.2.0-prerelease.4" resolved "https://registry.yarnpkg.com/@patternfly/react-component-groups/-/react-component-groups-6.2.0-prerelease.4.tgz#235fc34a40a05aba9b13007e18e523de5f0847ca" @@ -1905,7 +1917,7 @@ xterm "^5.1.0" xterm-addon-fit "^0.8.0" -"@patternfly/react-core@^6.0.0", "@patternfly/react-core@^6.0.0-prerelease.21", "@patternfly/react-core@^6.2.0-prerelease.15": +"@patternfly/react-core@^6.0.0", "@patternfly/react-core@^6.0.0-prerelease.21", "@patternfly/react-core@^6.1.0", "@patternfly/react-core@^6.2.0-prerelease.15": version "6.2.0-prerelease.15" resolved "https://registry.yarnpkg.com/@patternfly/react-core/-/react-core-6.2.0-prerelease.15.tgz#8f576605763e3410a411ca2681ddb7d66c47970d" integrity sha512-h8TdGxcX5Bkxb6c5cdP8Z0FEAeVcu4PO+dPncq2SXdMmFPvwUJfeklVGC8v81fclPPVtlijStFSM5aC5n1XjHQ== @@ -1917,7 +1929,7 @@ react-dropzone "^14.3.5" tslib "^2.8.1" -"@patternfly/react-icons@^6.0.0", "@patternfly/react-icons@^6.0.0-prerelease.7", "@patternfly/react-icons@^6.2.0-prerelease.2": +"@patternfly/react-icons@^6.0.0", "@patternfly/react-icons@^6.0.0-prerelease.7", "@patternfly/react-icons@^6.1.0", "@patternfly/react-icons@^6.2.0-prerelease.2": version "6.2.0-prerelease.2" resolved "https://registry.yarnpkg.com/@patternfly/react-icons/-/react-icons-6.2.0-prerelease.2.tgz#b695a12733d90004d243b37458bfe52947317d8e" integrity sha512-3ymG24ICMAvMqrDPzU8ycPcsHht4RfHjQil3ox43wwHI+nLrpywAgE9z6GtAzwtdm+DVZHBT1CWLWuAgdofX8w== @@ -1932,7 +1944,7 @@ "@patternfly/react-styles" "^6.0.0" memoize-one "^5.1.0" -"@patternfly/react-styles@^6.0.0", "@patternfly/react-styles@^6.0.0-prerelease.6", "@patternfly/react-styles@^6.2.0-prerelease.2": +"@patternfly/react-styles@^6.0.0", "@patternfly/react-styles@^6.0.0-prerelease.6", "@patternfly/react-styles@^6.1.0", "@patternfly/react-styles@^6.2.0-prerelease.2": version "6.2.0-prerelease.2" resolved "https://registry.yarnpkg.com/@patternfly/react-styles/-/react-styles-6.2.0-prerelease.2.tgz#32935b2e8d3bfb8aa4287e06a9aa3d4fbd550eea" integrity sha512-1YJH8Ozu05AHCBNGRZyNA5X1HxNGXuYTQvcR8FbVaNGuuWtvZxzb2EovN9xYVBu2joh+/LCLhiTAXI4eNKw3Ag== @@ -14056,7 +14068,7 @@ react-draggable@4.x: classnames "^2.2.5" prop-types "^15.6.0" -react-dropzone@^14.3.5: +react-dropzone@14.3.5, react-dropzone@^14.3.5: version "14.3.5" resolved "https://registry.yarnpkg.com/react-dropzone/-/react-dropzone-14.3.5.tgz#1a8bd312c8a353ec78ef402842ccb3589c225add" integrity sha512-9nDUaEEpqZLOz5v5SUcFA0CjM4vq8YbqO0WRls+EYT7+DvxUdzDPKNCPLqGfj3YL9MsniCLCD4RFA6M95V6KMQ== From 1201ee57d242b5477cf54836722ed3cbf0e0498d Mon Sep 17 00:00:00 2001 From: logonoff Date: Tue, 11 Feb 2025 10:03:21 -0500 Subject: [PATCH 031/618] CONSOLE-4409: Address PR feedback and refactor refactor: - update monaco-editor to latest 0.52.2 - update monaco-yaml to latest 5.3.1 - align console CodeEditor prop names with PatternFly - extract all monaco imports to BasicCodeEditor, a new component which only includes theming and i18n, to prevent loading from CDN addressing PR feedback: - remove the language label - hide code editor toolbar on mobile - replace "View sidebar" with a switch - rename "View shortcuts" to "Shortcuts" - add gutter to top of yaml editor --- frontend/package.json | 4 +- .../console-dynamic-plugin-sdk/README.md | 2 +- .../src/extensions/console-types.ts | 22 ++--- .../locales/en/console-shared.json | 3 +- .../src/components/editor/BasicCodeEditor.tsx | 42 ++++++++++ .../src/components/editor/CodeEditor.scss | 42 ++++++---- .../src/components/editor/CodeEditor.tsx | 81 +++++++------------ .../components/editor/CodeEditorToolbar.tsx | 9 +-- .../components/editor/yaml-editor-utils.ts | 21 ++--- .../formik-fields/CodeEditorField.tsx | 4 +- .../components/formik-fields/field-types.ts | 2 +- .../buildconfig/sections/EditorField.tsx | 19 +++-- .../components/test-function/RequestPane.tsx | 2 +- .../components/test-function/ResponsePane.tsx | 2 +- frontend/public/components/_edit-yaml.scss | 2 + frontend/public/components/edit-yaml.jsx | 27 +++---- .../sidebars/resource-sidebar-samples.tsx | 13 +-- frontend/public/locales/en/public.json | 2 +- frontend/webpack.config.ts | 6 +- frontend/yarn.lock | 25 +++--- 20 files changed, 171 insertions(+), 159 deletions(-) create mode 100644 frontend/packages/console-shared/src/components/editor/BasicCodeEditor.tsx diff --git a/frontend/package.json b/frontend/package.json index a843e96f863..3e21032e2fc 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -181,7 +181,7 @@ "js-yaml": "^3.13.1", "json-schema": "^0.3.0", "lodash-es": "^4.17.21", - "monaco-yaml": "^5.2.3", + "monaco-yaml": "^5.3.1", "murmurhash-js": "1.0.x", "node-polyfill-webpack-plugin": "^4.0.0", "pluralize": "^8.0.0", @@ -303,7 +303,7 @@ "mochawesome-merge": "^4.1.0", "mochawesome-report-generator": "^5.1.0", "mock-socket": "^9.0.3", - "monaco-editor": "^0.36.1", + "monaco-editor": "^0.52.2", "monaco-editor-webpack-plugin": "^7.1.0", "prettier": "2.0.5", "react-refresh": "^0.10.0", diff --git a/frontend/packages/console-dynamic-plugin-sdk/README.md b/frontend/packages/console-dynamic-plugin-sdk/README.md index 4c5136d595a..7134f31159d 100644 --- a/frontend/packages/console-dynamic-plugin-sdk/README.md +++ b/frontend/packages/console-dynamic-plugin-sdk/README.md @@ -159,7 +159,7 @@ This section documents notable changes in the Console provided shared modules ac - Removed `@fortawesome/font-awesome` and `openshift-logos-icon`. Plugins should use PatternFly icons from `@patternfly/react-icons` instead. The `fa-spin` class remains but is deprecated and will be removed in the future. Plugins should provide their own CSS to spin icons if needed. -- Upgraded `monaco-editor` to version `0.36.1`. +- Upgraded `monaco-editor` to version `0.52.2`. - Removed PatternFly 4.x shared modules. - Upgraded PatternFly to v6. - Removed styling for generic HTML heading elements (e.g., `

`). Use PatternFly components to achieve correct styling. diff --git a/frontend/packages/console-dynamic-plugin-sdk/src/extensions/console-types.ts b/frontend/packages/console-dynamic-plugin-sdk/src/extensions/console-types.ts index 430198a4077..e33d04c9a50 100644 --- a/frontend/packages/console-dynamic-plugin-sdk/src/extensions/console-types.ts +++ b/frontend/packages/console-dynamic-plugin-sdk/src/extensions/console-types.ts @@ -1,6 +1,6 @@ import * as React from 'react'; import { QuickStartContextValues } from '@patternfly/quickstarts'; -import { Language } from '@patternfly/react-code-editor'; +import { CodeEditorProps as PfCodeEditorProps } from '@patternfly/react-code-editor'; import { ButtonProps } from '@patternfly/react-core'; import { ICell, OnSelect, SortByDirection, TableGridBreakpoint } from '@patternfly/react-table'; import { LocationDescriptor } from 'history'; @@ -635,20 +635,20 @@ export type UserInfo = { extra?: object; }; -export type CodeEditorProps = { +// Omit the ref as we have our own ref type, which is completely different +export type BaseCodeEditorProps = Partial>; + +export type CodeEditorProps = Omit & { + /** Code displayed in code editor. */ value?: string; - language?: Language; - options?: object; - minHeight?: string | number; + /** Minimum editor height in valid CSS height values. */ + minHeight?: CSSStyleDeclaration['minHeight']; + /** Whether to show a toolbar with shortcuts on top of the editor. */ showShortcuts?: boolean; - showMiniMap?: boolean; + /** Toolbar links section on top of the editor */ toolbarLinks?: React.ReactNodeArray; - onChange?: (newValue, event) => void; + /** Callback that is run when CTRL / CMD + S is pressed */ onSave?: () => void; - onEditorDidMount?: (editor: monaco.editor.IStandaloneCodeEditor) => void; - isDownloadEnabled?: boolean; - isCopyEnabled?: boolean; - isLanguageLabelVisible?: boolean; }; export type CodeEditorRef = { diff --git a/frontend/packages/console-shared/locales/en/console-shared.json b/frontend/packages/console-shared/locales/en/console-shared.json index 6c8ff950c35..b2f61dbdd5a 100644 --- a/frontend/packages/console-shared/locales/en/console-shared.json +++ b/frontend/packages/console-shared/locales/en/console-shared.json @@ -108,13 +108,12 @@ "true": "true", "Select {{label}}": "Select {{label}}", "Cluster does not have resource {{groupVersionKind}}": "Cluster does not have resource {{groupVersionKind}}", - "View shortcuts": "View shortcuts", + "Shortcuts": "Shortcuts", "Copy code to clipboard": "Copy code to clipboard", "Content copied to clipboard": "Content copied to clipboard", "Download code": "Download code", "Ask OpenShift Lightspeed": "Ask OpenShift Lightspeed", "Accessibility help": "Accessibility help", - "Shortcuts": "Shortcuts", "View all editor shortcuts": "View all editor shortcuts", "Activate auto complete": "Activate auto complete", "Toggle Tab action between insert Tab character and move focus out of editor": "Toggle Tab action between insert Tab character and move focus out of editor", diff --git a/frontend/packages/console-shared/src/components/editor/BasicCodeEditor.tsx b/frontend/packages/console-shared/src/components/editor/BasicCodeEditor.tsx new file mode 100644 index 00000000000..022605e6b33 --- /dev/null +++ b/frontend/packages/console-shared/src/components/editor/BasicCodeEditor.tsx @@ -0,0 +1,42 @@ +import * as React from 'react'; +import { loader, Monaco } from '@monaco-editor/react'; +import { CodeEditor as PfEditor } from '@patternfly/react-code-editor'; +import * as monaco from 'monaco-editor'; +import { useTranslation } from 'react-i18next'; +import { BaseCodeEditorProps } from '@console/dynamic-plugin-sdk'; +import { useConsoleMonacoTheme } from './theme'; + +// Avoid using monaco from CDN +loader.config({ monaco }); + +/** + * PatternFly CodeEditor with i18n and theme applied. Does not include + * YAML language integration or console-specific CSS. + * + * Note that it is important that this is the only component that imports + * monaco-editor, to avoid fetching files from a 3rd-party CDN. + */ +export const BasicCodeEditor: React.FC = (props) => { + const { t } = useTranslation('console-shared'); + const [monacoRef, setMonacoRef] = React.useState(null); + useConsoleMonacoTheme(monacoRef?.editor); + + return ( + { + setMonacoRef(monacoInstance); + props?.editorProps?.beforeMount?.(monacoInstance); + }, + }} + shortcutsPopoverButtonText={t('Shortcuts')} + copyButtonAriaLabel={t('Copy code to clipboard')} + copyButtonSuccessTooltipText={t('Content copied to clipboard')} + copyButtonToolTipText={t('Copy code to clipboard')} + downloadButtonAriaLabel={t('Download code')} + downloadButtonToolTipText={t('Download code')} + /> + ); +}; diff --git a/frontend/packages/console-shared/src/components/editor/CodeEditor.scss b/frontend/packages/console-shared/src/components/editor/CodeEditor.scss index 37d5eaabb78..ad68e269a60 100644 --- a/frontend/packages/console-shared/src/components/editor/CodeEditor.scss +++ b/frontend/packages/console-shared/src/components/editor/CodeEditor.scss @@ -12,33 +12,43 @@ } .ocs-yaml-editor { - // makes automaticLayout work properly + /* Makes automaticLayout work properly. note that this breaks height="sizeToFit" */ .monaco-editor { position: absolute !important; } + /* Hide CodeEditor toolbar on mobile */ + @media (max-width: $screen-sm-max) { + .pf-v6-c-code-editor__header { + display: none; + + + .pf-v6-c-code-editor__main { + border-block-start-width: var(--pf-t--global--border--width--box--default); + } + } + } + + /* Needed to get the CodeEditorToolbar to place buttons on the right side */ .pf-v6-c-code-editor__controls, .ocs-yaml-editor-toolbar { - // needed to get the CodeEditorToolbar to place buttons on the right side width: 100%; height: 100% !important; - } - - .ocs-yaml-editor-toolbar { align-items: center; + } - .ocs-yaml-editor-toolbar__link { - @media (max-width: $screen-sm-max) { - display: none; - } - } + /* Dividers in toolbar */ + .ocs-yaml-editor-toolbar__link:not(:last-child) { + border-inline-end: var(--pf-t--global--border--width--divider--default) solid var(--pf-t--global--border--color--default); + margin-inline-end: var(--pf-t--global--spacer--sm); + padding-inline-end: var(--pf-t--global--spacer--sm); + } - .ocs-yaml-editor-toolbar__shortcuts { - padding: var(--pf-t--global--spacer--md) var(--pf-v6-global-gutter); + /* Accessibility shortcuts hint styling */ + .ocs-yaml-editor-toolbar__shortcuts { + padding: var(--pf-t--global--spacer--md) var(--pf-v6-global-gutter); - .ocs-yaml-editor-shortcut__text { - color: var(--pf-t--global--text--color--subtle); - margin-left: var(--pf-t--global--spacer--sm); - } + .ocs-yaml-editor-shortcut__text { + color: var(--pf-t--global--text--color--subtle); + margin-inline-start: var(--pf-t--global--spacer--sm); } } } diff --git a/frontend/packages/console-shared/src/components/editor/CodeEditor.tsx b/frontend/packages/console-shared/src/components/editor/CodeEditor.tsx index 21c7fc38f21..b950bac3388 100644 --- a/frontend/packages/console-shared/src/components/editor/CodeEditor.tsx +++ b/frontend/packages/console-shared/src/components/editor/CodeEditor.tsx @@ -1,73 +1,59 @@ import * as React from 'react'; -import { CodeEditor as PfEditor, Language } from '@patternfly/react-code-editor'; -import type * as Monaco from 'monaco-editor/esm/vs/editor/editor.api'; -import { useTranslation } from 'react-i18next'; +import { EditorDidMount, Language } from '@patternfly/react-code-editor'; +import type * as monaco from 'monaco-editor'; import { CodeEditorRef, CodeEditorProps } from '@console/dynamic-plugin-sdk'; +import { BasicCodeEditor } from './BasicCodeEditor'; import CodeEditorToolbar from './CodeEditorToolbar'; import { useShortcutLink } from './ShortcutsLink'; import { useConsoleMonacoTheme } from './theme'; -import { registerYAMLinMonaco, defaultEditorOptions } from './yaml-editor-utils'; +import { registerYAMLinMonaco, registerAutoFold, defaultEditorOptions } from './yaml-editor-utils'; import './CodeEditor.scss'; const CodeEditor = React.forwardRef((props, ref) => { const { value, - options = defaultEditorOptions, + minHeight, showShortcuts, - showMiniMap, toolbarLinks, - minHeight, - onChange, onSave, + options, language, onEditorDidMount, - isDownloadEnabled, - isCopyEnabled, - isLanguageLabelVisible, } = props; - const { t } = useTranslation('console-shared'); const shortcutPopover = useShortcutLink(); - const [editorRef, setEditorRef] = React.useState( + const [editorRef, setEditorRef] = React.useState( null, ); - const [monacoRef, setMonacoRef] = React.useState(null); + const [monacoRef, setMonacoRef] = React.useState(null); + const [usesValue] = React.useState(value !== undefined); useConsoleMonacoTheme(monacoRef?.editor); - const [usesValue] = React.useState(value !== undefined); - const editorDidMount = React.useCallback( - (editor, monaco) => { - setEditorRef(editor); - setMonacoRef(monaco); - const currentLanguage = editor.getModel()?.getLanguageId(); - editor.layout(); - editor.focus(); + const editorDidMount: EditorDidMount = React.useCallback( + (mountedEditor, mountedMonaco) => { + setEditorRef(mountedEditor); + setMonacoRef(mountedMonaco); + mountedEditor.getModel()?.updateOptions({ tabSize: 2 }); + const currentLanguage = mountedEditor.getModel()?.getLanguageId(); + mountedEditor.layout(); + mountedEditor.focus(); switch (currentLanguage) { case 'yaml': - registerYAMLinMonaco(editor, monaco, usesValue); + registerYAMLinMonaco(mountedMonaco); + registerAutoFold(mountedEditor, usesValue); break; case 'json': - editor.getAction('editor.action.formatDocument').run(); + mountedEditor.getAction('editor.action.formatDocument').run(); break; default: break; } - monaco.editor.getModels()[0]?.updateOptions({ tabSize: 2 }); - onSave && editor.addCommand(monaco.KeyMod.CtrlCmd | monaco.KeyCode.KeyS, onSave); // eslint-disable-line no-bitwise - onEditorDidMount && onEditorDidMount(editor); + onSave && + mountedEditor.addCommand(mountedMonaco.KeyMod.CtrlCmd | mountedMonaco.KeyCode.KeyS, onSave); // eslint-disable-line no-bitwise + onEditorDidMount && onEditorDidMount(mountedEditor, mountedMonaco); }, [onSave, usesValue, onEditorDidMount], ); - const editorOptions = React.useMemo(() => { - return { - ...options, - minimap: { - enabled: showMiniMap, - }, - automaticLayout: true, - }; - }, [options, showMiniMap]); - // expose the editor instance to the parent component via ref React.useImperativeHandle( ref, @@ -86,26 +72,17 @@ const CodeEditor = React.forwardRef((props, ref) }, [toolbarLinks, showShortcuts]); return ( - ); }); diff --git a/frontend/packages/console-shared/src/components/editor/CodeEditorToolbar.tsx b/frontend/packages/console-shared/src/components/editor/CodeEditorToolbar.tsx index be439ea830e..3fcc98ee799 100644 --- a/frontend/packages/console-shared/src/components/editor/CodeEditorToolbar.tsx +++ b/frontend/packages/console-shared/src/components/editor/CodeEditorToolbar.tsx @@ -1,5 +1,5 @@ import * as React from 'react'; -import { Flex, FlexItem, Button, Divider } from '@patternfly/react-core'; +import { Flex, FlexItem, Button } from '@patternfly/react-core'; import { MagicIcon } from '@patternfly/react-icons'; import { useTranslation } from 'react-i18next'; // eslint-disable-next-line @typescript-eslint/ban-ts-comment @@ -44,13 +44,6 @@ const CodeEditorToolbar: React.FC = ({ showShortcuts, to toolbarLinks.map((link, index) => ( // eslint-disable-next-line react/no-array-index-key
- {(showShortcuts || index > 0) && link ? ( - - ) : null} {link}
))} diff --git a/frontend/packages/console-shared/src/components/editor/yaml-editor-utils.ts b/frontend/packages/console-shared/src/components/editor/yaml-editor-utils.ts index e147edd76a5..43b972b3b01 100644 --- a/frontend/packages/console-shared/src/components/editor/yaml-editor-utils.ts +++ b/frontend/packages/console-shared/src/components/editor/yaml-editor-utils.ts @@ -5,7 +5,11 @@ import * as yaml from 'yaml-ast-parser'; import { openAPItoJSONSchema } from '@console/internal/module/k8s/openapi-to-json-schema'; import { getSwaggerDefinitions } from '@console/internal/module/k8s/swagger'; -export const defaultEditorOptions = { readOnly: false, scrollBeyondLastLine: false }; +export const defaultEditorOptions: monaco.editor.IEditorOptions = { + readOnly: false, + scrollBeyondLastLine: false, + automaticLayout: true, +}; const findManagedMetadata = (model: monaco.editor.ITextModel) => { const modelValue = model.getValue(); @@ -66,7 +70,6 @@ export const fold = ( /** * Register auto fold for the editor - * This should probably be a React hook */ export const registerAutoFold = ( editor: monaco.editor.IStandaloneCodeEditor, @@ -90,21 +93,17 @@ export const registerAutoFold = ( }); }; -export const registerYAMLinMonaco = ( - editor: monaco.editor.IStandaloneCodeEditor, - monacoInstance: typeof monaco, - alreadyInUse: boolean = false, -) => { +export const registerYAMLinMonaco = (monacoInstance: typeof monaco) => { /** * This exists because we enabled globalAPI in the webpack config. This means that the * the monaco instance may have already been setup with the YAML language features. - * Otherwise, you would register all the features again, getting duplicate results. + * Otherwise, we might register all the features again, getting duplicate results. * * Monaco does not provide any APIs for unregistering or checking if the features have already * been registered for a language. * * We check that > 1 YAML language exists because one is the default and - * the other is the initial register that setups our features. + * the other is the language server that we register. */ if (monacoInstance.languages.getLanguages().filter((x) => x.id === 'yaml').length <= 1) { // Prepare the schema @@ -129,8 +128,4 @@ export const registerYAMLinMonaco = ( completion: true, }); } - - if (!alreadyInUse) { - registerAutoFold(editor); - } }; diff --git a/frontend/packages/console-shared/src/components/formik-fields/CodeEditorField.tsx b/frontend/packages/console-shared/src/components/formik-fields/CodeEditorField.tsx index ea4e39eea2f..c2754e7f9d7 100644 --- a/frontend/packages/console-shared/src/components/formik-fields/CodeEditorField.tsx +++ b/frontend/packages/console-shared/src/components/formik-fields/CodeEditorField.tsx @@ -32,7 +32,7 @@ const CodeEditorField: React.FC = ({ schema, showSamples, showShortcuts, - showMiniMap, + isMinimapVisible, minHeight, onSave, language, @@ -90,7 +90,7 @@ const CodeEditorField: React.FC = ({ onChange={(yaml: string) => setFieldValue(name, yaml)} onSave={onSave} showShortcuts={showShortcuts} - showMiniMap={showMiniMap} + isMinimapVisible={isMinimapVisible} language={language} toolbarLinks={ !sidebarOpen && diff --git a/frontend/packages/console-shared/src/components/formik-fields/field-types.ts b/frontend/packages/console-shared/src/components/formik-fields/field-types.ts index 0d88f62b0fa..533109ac305 100644 --- a/frontend/packages/console-shared/src/components/formik-fields/field-types.ts +++ b/frontend/packages/console-shared/src/components/formik-fields/field-types.ts @@ -115,7 +115,7 @@ export interface CodeEditorFieldProps extends FieldProps { schema?: JSONSchema7; showSamples: boolean; showShortcuts?: boolean; - showMiniMap?: boolean; + isMinimapVisible?: boolean; onSave?: () => void; } diff --git a/frontend/packages/dev-console/src/components/buildconfig/sections/EditorField.tsx b/frontend/packages/dev-console/src/components/buildconfig/sections/EditorField.tsx index 6c85c2f17c5..7d175b596a5 100644 --- a/frontend/packages/dev-console/src/components/buildconfig/sections/EditorField.tsx +++ b/frontend/packages/dev-console/src/components/buildconfig/sections/EditorField.tsx @@ -1,10 +1,10 @@ import * as React from 'react'; -import { CodeEditor, ChangeHandler, Language } from '@patternfly/react-code-editor'; +import { ChangeHandler, Language } from '@patternfly/react-code-editor'; import { FormGroup, FormHelperText, HelperText, HelperTextItem } from '@patternfly/react-core'; import { FormikValues, useFormikContext } from 'formik'; import type * as Monaco from 'monaco-editor/esm/vs/editor/editor.api'; import { RedExclamationCircleIcon, useDebounceCallback } from '@console/shared/src'; -import { useConsoleMonacoTheme } from '@console/shared/src/components/editor/theme'; +import { BasicCodeEditor } from '@console/shared/src/components/editor/BasicCodeEditor'; type EditorFieldProps = { name: string; @@ -28,8 +28,6 @@ const EditorField: React.FC = ({ }) => { const { getFieldMeta, setFieldValue, setFieldTouched } = useFormikContext(); const { error, value } = getFieldMeta(name); - const [monaco, setMonaco] = React.useState(null); - useConsoleMonacoTheme(monaco?.editor); const debouncedOnChange = useDebounceCallback((newValue, event) => { if (onChange) { @@ -41,15 +39,16 @@ const EditorField: React.FC = ({ return ( - setMonaco(m)} - isReadOnly={false} - isMinimapVisible={false} + isFullHeight={false} height="sizeToFit" language={otherProps?.language} - options={otherProps?.options} + options={{ + minimap: { enabled: false }, + ...otherProps?.options, + }} /> diff --git a/frontend/packages/knative-plugin/src/components/test-function/RequestPane.tsx b/frontend/packages/knative-plugin/src/components/test-function/RequestPane.tsx index 99bfafcac06..38cf30b8dfe 100644 --- a/frontend/packages/knative-plugin/src/components/test-function/RequestPane.tsx +++ b/frontend/packages/knative-plugin/src/components/test-function/RequestPane.tsx @@ -144,7 +144,7 @@ const RequestPane: React.FC> = ({ setFieldValue, value minHeight="34vh" showSamples={false} showShortcuts={false} - showMiniMap={false} + isMinimapVisible={false} language={getcurrentLanguage(contentType)} />

diff --git a/frontend/packages/knative-plugin/src/components/test-function/ResponsePane.tsx b/frontend/packages/knative-plugin/src/components/test-function/ResponsePane.tsx index 688a7bd1e1d..0a0bc9c7169 100644 --- a/frontend/packages/knative-plugin/src/components/test-function/ResponsePane.tsx +++ b/frontend/packages/knative-plugin/src/components/test-function/ResponsePane.tsx @@ -92,7 +92,7 @@ const ResponsePane: React.FC ) : ( diff --git a/frontend/public/components/_edit-yaml.scss b/frontend/public/components/_edit-yaml.scss index d85d07cc5d4..ee8f98a2cb9 100644 --- a/frontend/public/components/_edit-yaml.scss +++ b/frontend/public/components/_edit-yaml.scss @@ -4,6 +4,8 @@ height: 100%; flex: 1; flex-direction: column; + padding-top: $pf-v6-global-gutter; + @media (min-width: $pf-v6-global--breakpoint--md) { margin-left: $pf-v6-global-gutter; margin-right: $pf-v6-global-gutter; diff --git a/frontend/public/components/edit-yaml.jsx b/frontend/public/components/edit-yaml.jsx index f1930199427..1b0420dae58 100644 --- a/frontend/public/components/edit-yaml.jsx +++ b/frontend/public/components/edit-yaml.jsx @@ -10,7 +10,6 @@ import { ActionType, getOLSCodeBlock } from '@console/internal/reducers/ols'; import { safeLoad, safeLoadAll, safeDump } from 'js-yaml'; import { ActionGroup, Alert, Button, Switch } from '@patternfly/react-core'; import { DownloadIcon } from '@patternfly/react-icons/dist/esm/icons/download-icon'; -import { InfoCircleIcon } from '@patternfly/react-icons/dist/esm/icons/info-circle-icon'; import { Trans, useTranslation } from 'react-i18next'; import { useNavigate } from 'react-router-dom-v5-compat'; import { @@ -743,17 +742,18 @@ const EditYAMLInner = (props) => { const definition = model ? definitionFor(model) : { properties: [] }; const showSchema = definition && !_.isEmpty(definition.properties); const hasSidebarContent = showSchema || (create && !_.isEmpty(samples)) || !_.isEmpty(snippets); - const sidebarLink = - !showSidebar && hasSidebarContent ? ( - - ) : null; - const tooltipCheckBox = ( + const sidebarSwitch = hasSidebarContent && ( + + ); + + const tooltipSwitch = ( { options={options} showShortcuts={!genericYAML} minHeight="100px" - toolbarLinks={sidebarLink ? [tooltipCheckBox, sidebarLink] : [tooltipCheckBox]} + toolbarLinks={sidebarSwitch ? [tooltipSwitch, sidebarSwitch] : [tooltipSwitch]} onChange={onChange} onSave={() => (allowMultiple ? saveAll() : save())} onEditorDidMount={() => setEditorMounted(true)} - isLanguageLabelVisible />
{customAlerts} diff --git a/frontend/public/components/sidebars/resource-sidebar-samples.tsx b/frontend/public/components/sidebars/resource-sidebar-samples.tsx index 311303fe856..4acaa2b00de 100644 --- a/frontend/public/components/sidebars/resource-sidebar-samples.tsx +++ b/frontend/public/components/sidebars/resource-sidebar-samples.tsx @@ -1,9 +1,8 @@ import * as _ from 'lodash-es'; import * as React from 'react'; import { Button, Level, LevelItem, Title } from '@patternfly/react-core'; -import type * as Monaco from 'monaco-editor/esm/vs/editor/editor.api'; -import { CodeEditor, Language } from '@patternfly/react-code-editor'; -import { useConsoleMonacoTheme } from '@console/shared/src/components/editor/theme'; +import { Language } from '@patternfly/react-code-editor'; +import { BasicCodeEditor } from '@console/shared/src/components/editor/BasicCodeEditor'; import { ChevronDownIcon } from '@patternfly/react-icons/dist/esm/icons/chevron-down-icon'; import { ChevronRightIcon } from '@patternfly/react-icons/dist/esm/icons/chevron-right-icon'; import { DownloadIcon } from '@patternfly/react-icons/dist/esm/icons/download-icon'; @@ -61,16 +60,12 @@ const ResourceSidebarSample: React.FC = ({ const lineHeight = 18; const PreviewYAML = ({ maxPreviewLines = 20, yaml }) => { - const [monaco, setMonaco] = React.useState(null); - useConsoleMonacoTheme(monaco?.editor); - return (
- setMonaco(m)} + code={yaml} options={{ lineHeight, readOnly: true, diff --git a/frontend/public/locales/en/public.json b/frontend/public/locales/en/public.json index edabcc4c0c9..812555e2feb 100644 --- a/frontend/public/locales/en/public.json +++ b/frontend/public/locales/en/public.json @@ -485,7 +485,7 @@ "Resources in the same namespace and API group must have unique names": "Resources in the same namespace and API group must have unique names", "Failed to parse YAML sample": "Failed to parse YAML sample", "An error occurred.": "An error occurred.", - "View sidebar": "View sidebar", + "Sidebar": "Sidebar", "Tooltips": "Tooltips", "Drop file here": "Drop file here", "Drag and drop YAML or JSON files into the editor, or manually enter files and use <2>--- to separate each definition.": "Drag and drop YAML or JSON files into the editor, or manually enter files and use <2>--- to separate each definition.", diff --git a/frontend/webpack.config.ts b/frontend/webpack.config.ts index c6dfe06764d..840018a2de9 100644 --- a/frontend/webpack.config.ts +++ b/frontend/webpack.config.ts @@ -61,11 +61,7 @@ const sharedPluginModulesTest = getVendorModuleRegExp( const config: Configuration = { entry: { - main: [ - './public/components/app.jsx', - 'monaco-editor/esm/vs/editor/editor.worker.js', - '/node_modules/@patternfly-5/patternfly/patternfly.scss', - ], + main: ['./public/components/app.jsx', '/node_modules/@patternfly-5/patternfly/patternfly.scss'], }, cache: { type: 'filesystem', diff --git a/frontend/yarn.lock b/frontend/yarn.lock index 40ebc73d8aa..7fe79dc2f9f 100644 --- a/frontend/yarn.lock +++ b/frontend/yarn.lock @@ -12394,10 +12394,10 @@ monaco-editor-webpack-plugin@^7.1.0: dependencies: loader-utils "^2.0.2" -monaco-editor@^0.36.1: - version "0.36.1" - resolved "https://registry.yarnpkg.com/monaco-editor/-/monaco-editor-0.36.1.tgz#aad528c815605307473a1634612946921d8079b5" - integrity sha512-/CaclMHKQ3A6rnzBzOADfwdSJ25BFoFT0Emxsc4zYVyav5SkK9iA6lEtIeuN/oRYbwPgviJT+t3l+sjFa28jYg== +monaco-editor@^0.52.2: + version "0.52.2" + resolved "https://registry.yarnpkg.com/monaco-editor/-/monaco-editor-0.52.2.tgz#53c75a6fcc6802684e99fd1b2700299857002205" + integrity sha512-GEQWEZmfkOGLdd3XK8ryrfWz3AIP8YymVXiPHEdewrUq7mh0qrKrfHLNCXcbB6sTnMLnOZ3ztSiKcciFUkIJwQ== monaco-languageserver-types@^0.4.0: version "0.4.0" @@ -12425,10 +12425,10 @@ monaco-worker-manager@^2.0.0: resolved "https://registry.yarnpkg.com/monaco-worker-manager/-/monaco-worker-manager-2.0.1.tgz#f67c54dfca34ed4b225d5de84e77b24b4e36de8a" integrity sha512-kdPL0yvg5qjhKPNVjJoym331PY/5JC11aPJXtCZNwWRvBr6jhkIamvYAyiY5P1AWFmNOy0aRDRoMdZfa71h8kg== -monaco-yaml@^5.2.3: - version "5.2.3" - resolved "https://registry.yarnpkg.com/monaco-yaml/-/monaco-yaml-5.2.3.tgz#990fcf697bfa54e684a2d2f7df6d81a889843db4" - integrity sha512-GEplKC+YYmS0TOlJdv0FzbqkDN/IG2FSwEw+95myECVxTlhty2amwERYjzvorvJXmIagP1grd3Lleq7aQEJpPg== +monaco-yaml@^5.3.1: + version "5.3.1" + resolved "https://registry.yarnpkg.com/monaco-yaml/-/monaco-yaml-5.3.1.tgz#e37749ff8491924a7cabf9f597e746036cc6a9ea" + integrity sha512-1MN8i1Tnc8d8RugQGqv5jp+Ce2xtNhrnbm0ZZbe5ceExj9C2PkKZfHJhY9kbdUS4G7xSVwKlVdMTmLlStepOtw== dependencies: jsonc-parser "^3.0.0" monaco-languageserver-types "^0.4.0" @@ -12436,7 +12436,7 @@ monaco-yaml@^5.2.3: monaco-types "^0.1.0" monaco-worker-manager "^2.0.0" path-browserify "^1.0.0" - prettier "^2.0.0" + prettier "^3.0.0" vscode-languageserver-textdocument "^1.0.0" vscode-languageserver-types "^3.0.0" vscode-uri "^3.0.0" @@ -13640,11 +13640,16 @@ prettier@2.0.5: resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.0.5.tgz#d6d56282455243f2f92cc1716692c08aa31522d4" integrity sha512-7PtVymN48hGcO4fGjybyBSIWDsLU4H4XlvOHfq91pz9kkGlonzwTfYkaIEwiRg/dAJF9YlbsduBAgtYLi+8cFg== -prettier@^2.0.0, prettier@^2.6.2: +prettier@^2.6.2: version "2.8.8" resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.8.8.tgz#e8c5d7e98a4305ffe3de2e1fc4aca1a71c28b1da" integrity sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q== +prettier@^3.0.0: + version "3.5.0" + resolved "https://registry.yarnpkg.com/prettier/-/prettier-3.5.0.tgz#50325a28887c6dfdf2ca3f8eaba02b66a8429ca7" + integrity sha512-quyMrVt6svPS7CjQ9gKb3GLEX/rl3BCL2oa/QkNcXv4YNVBC9olt3s+H7ukto06q7B1Qz46PbrKLO34PR6vXcA== + pretty-bytes@^5.6.0: version "5.6.0" resolved "https://registry.yarnpkg.com/pretty-bytes/-/pretty-bytes-5.6.0.tgz#356256f643804773c82f64723fe78c92c62beaeb" From 35951fd95a2e34d1c7efaa099f1fb34e10110449 Mon Sep 17 00:00:00 2001 From: Mylanos Date: Thu, 13 Feb 2025 16:44:14 +0100 Subject: [PATCH 032/618] OCPBUGS-50670: fix bold text issues on safari and firefox browsers --- .../src/components/dashboard/status-card/status-card.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/packages/console-shared/src/components/dashboard/status-card/status-card.scss b/frontend/packages/console-shared/src/components/dashboard/status-card/status-card.scss index 175644bcadb..1c29022d627 100644 --- a/frontend/packages/console-shared/src/components/dashboard/status-card/status-card.scss +++ b/frontend/packages/console-shared/src/components/dashboard/status-card/status-card.scss @@ -66,7 +66,7 @@ .co-status-card__alert-item-header { margin-left: 1rem; - font-weight: bold; + font-weight: var(--pf-t--global--font--weight--heading--default); } .co-status-card__alert-item-doc-link { From 8b97270da61bb3f90705f51573eb1386fac80f6b Mon Sep 17 00:00:00 2001 From: logonoff Date: Tue, 11 Feb 2025 17:29:29 -0500 Subject: [PATCH 033/618] CONSOLE-4409: Update CodeEditor styling and i18n --- frontend/@types/console/index.d.ts | 1 + .../src/extensions/console-types.ts | 26 ++--- .../locales/en/console-shared.json | 7 +- .../components/editor/BasicCodeEditor.scss | 7 ++ .../src/components/editor/BasicCodeEditor.tsx | 30 ++++-- .../src/components/editor/CodeEditor.scss | 23 ++--- .../src/components/editor/CodeEditor.tsx | 5 +- .../components/editor/CodeEditorToolbar.tsx | 98 ++++++++++--------- .../__tests__/CodeEditorToolbar.spec.tsx | 8 +- .../components/editor/yaml-editor-utils.ts | 1 - .../buildconfig/sections/EditorField.tsx | 19 ++-- 11 files changed, 125 insertions(+), 100 deletions(-) create mode 100644 frontend/packages/console-shared/src/components/editor/BasicCodeEditor.scss diff --git a/frontend/@types/console/index.d.ts b/frontend/@types/console/index.d.ts index 927f6147a93..023a486eae9 100644 --- a/frontend/@types/console/index.d.ts +++ b/frontend/@types/console/index.d.ts @@ -66,6 +66,7 @@ declare interface Window { webpackSharedScope?: {}; // webpack shared scope object, contains modules shared across plugins ResizeObserver: ResizeObserver.prototype; // polyfill used by react-measure Cypress?: {}; + monaco?: {}; } // From https://www.typescriptlang.org/docs/handbook/release-notes/typescript-2-8.html diff --git a/frontend/packages/console-dynamic-plugin-sdk/src/extensions/console-types.ts b/frontend/packages/console-dynamic-plugin-sdk/src/extensions/console-types.ts index e33d04c9a50..019a160fe30 100644 --- a/frontend/packages/console-dynamic-plugin-sdk/src/extensions/console-types.ts +++ b/frontend/packages/console-dynamic-plugin-sdk/src/extensions/console-types.ts @@ -635,22 +635,26 @@ export type UserInfo = { extra?: object; }; -// Omit the ref as we have our own ref type, which is completely different -export type BaseCodeEditorProps = Partial>; - -export type CodeEditorProps = Omit & { - /** Code displayed in code editor. */ - value?: string; - /** Minimum editor height in valid CSS height values. */ - minHeight?: CSSStyleDeclaration['minHeight']; +export type CodeEditorToolbarProps = { /** Whether to show a toolbar with shortcuts on top of the editor. */ showShortcuts?: boolean; - /** Toolbar links section on top of the editor */ + /** Toolbar links section on the left side of the editor */ toolbarLinks?: React.ReactNodeArray; - /** Callback that is run when CTRL / CMD + S is pressed */ - onSave?: () => void; }; +// Omit the ref as we have our own ref type, which is completely different +export type BasicCodeEditorProps = Partial>; + +export type CodeEditorProps = Omit & + CodeEditorToolbarProps & { + /** Code displayed in code editor. */ + value?: string; + /** Minimum editor height in valid CSS height values. */ + minHeight?: CSSStyleDeclaration['minHeight']; + /** Callback that is run when CTRL / CMD + S is pressed */ + onSave?: () => void; + }; + export type CodeEditorRef = { editor: monaco.editor.IStandaloneCodeEditor; monaco: typeof monaco; diff --git a/frontend/packages/console-shared/locales/en/console-shared.json b/frontend/packages/console-shared/locales/en/console-shared.json index b2f61dbdd5a..58a713eac68 100644 --- a/frontend/packages/console-shared/locales/en/console-shared.json +++ b/frontend/packages/console-shared/locales/en/console-shared.json @@ -108,10 +108,15 @@ "true": "true", "Select {{label}}": "Select {{label}}", "Cluster does not have resource {{groupVersionKind}}": "Cluster does not have resource {{groupVersionKind}}", - "Shortcuts": "Shortcuts", "Copy code to clipboard": "Copy code to clipboard", "Content copied to clipboard": "Content copied to clipboard", "Download code": "Download code", + "Shortcuts": "Shortcuts", + "Upload code": "Upload code", + "Drag and drop a file or upload one.": "Drag and drop a file or upload one.", + "Browse": "Browse", + "Start from scratch": "Start from scratch", + "Start editing": "Start editing", "Ask OpenShift Lightspeed": "Ask OpenShift Lightspeed", "Accessibility help": "Accessibility help", "View all editor shortcuts": "View all editor shortcuts", diff --git a/frontend/packages/console-shared/src/components/editor/BasicCodeEditor.scss b/frontend/packages/console-shared/src/components/editor/BasicCodeEditor.scss new file mode 100644 index 00000000000..c2b76cc9de8 --- /dev/null +++ b/frontend/packages/console-shared/src/components/editor/BasicCodeEditor.scss @@ -0,0 +1,7 @@ +// match the token in theme.ts. it already matches for light theme +.pf-v6-theme-dark { + .co-code-editor:not(.pf-m-read-only) { + --pf-v6-c-code-editor__main--BackgroundColor: var(--pf-t--color--gray--90); + --pf-v6-c-code-editor__tab--BackgroundColor: var(--pf-t--color--gray--90); + } +} diff --git a/frontend/packages/console-shared/src/components/editor/BasicCodeEditor.tsx b/frontend/packages/console-shared/src/components/editor/BasicCodeEditor.tsx index 022605e6b33..e23b0560e58 100644 --- a/frontend/packages/console-shared/src/components/editor/BasicCodeEditor.tsx +++ b/frontend/packages/console-shared/src/components/editor/BasicCodeEditor.tsx @@ -1,10 +1,12 @@ import * as React from 'react'; import { loader, Monaco } from '@monaco-editor/react'; -import { CodeEditor as PfEditor } from '@patternfly/react-code-editor'; +import { CodeEditor } from '@patternfly/react-code-editor'; +import classNames from 'classnames'; import * as monaco from 'monaco-editor'; import { useTranslation } from 'react-i18next'; -import { BaseCodeEditorProps } from '@console/dynamic-plugin-sdk'; +import { BasicCodeEditorProps } from '@console/dynamic-plugin-sdk'; import { useConsoleMonacoTheme } from './theme'; +import './BasicCodeEditor.scss'; // Avoid using monaco from CDN loader.config({ monaco }); @@ -16,27 +18,35 @@ loader.config({ monaco }); * Note that it is important that this is the only component that imports * monaco-editor, to avoid fetching files from a 3rd-party CDN. */ -export const BasicCodeEditor: React.FC = (props) => { +export const BasicCodeEditor: React.FC = (props) => { const { t } = useTranslation('console-shared'); const [monacoRef, setMonacoRef] = React.useState(null); useConsoleMonacoTheme(monacoRef?.editor); return ( - { setMonacoRef(monacoInstance); + window.monaco = monacoInstance; // for e2e tests props?.editorProps?.beforeMount?.(monacoInstance); }, }} - shortcutsPopoverButtonText={t('Shortcuts')} - copyButtonAriaLabel={t('Copy code to clipboard')} - copyButtonSuccessTooltipText={t('Content copied to clipboard')} - copyButtonToolTipText={t('Copy code to clipboard')} - downloadButtonAriaLabel={t('Download code')} - downloadButtonToolTipText={t('Download code')} /> ); }; diff --git a/frontend/packages/console-shared/src/components/editor/CodeEditor.scss b/frontend/packages/console-shared/src/components/editor/CodeEditor.scss index ad68e269a60..ba66cfa8d21 100644 --- a/frontend/packages/console-shared/src/components/editor/CodeEditor.scss +++ b/frontend/packages/console-shared/src/components/editor/CodeEditor.scss @@ -1,16 +1,5 @@ @import '../../../../../public/style/vars'; -// match the token in theme.ts. it already matches for light theme -.pf-v6-theme-dark { - .pf-v6-c-code-editor__main { - --pf-v6-c-code-editor__main--BackgroundColor: var(--pf-t--color--gray--90); - } - - .pf-v6-c-code-editor__tab { - --pf-v6-c-code-editor__tab--BackgroundColor: var(--pf-t--color--gray--90); - } -} - .ocs-yaml-editor { /* Makes automaticLayout work properly. note that this breaks height="sizeToFit" */ .monaco-editor { @@ -36,10 +25,14 @@ } /* Dividers in toolbar */ - .ocs-yaml-editor-toolbar__link:not(:last-child) { - border-inline-end: var(--pf-t--global--border--width--divider--default) solid var(--pf-t--global--border--color--default); - margin-inline-end: var(--pf-t--global--spacer--sm); - padding-inline-end: var(--pf-t--global--spacer--sm); + .ocs-yaml-editor-toolbar__links { + padding-inline-start: var(--pf-t--global--spacer--xs); + + .ocs-yaml-editor-toolbar__link:not(:last-of-type) { + border-inline-end: var(--pf-t--global--border--width--divider--default) solid var(--pf-t--global--border--color--default); + margin-inline-end: var(--pf-t--global--spacer--sm); + padding-inline-end: var(--pf-t--global--spacer--sm); + } } /* Accessibility shortcuts hint styling */ diff --git a/frontend/packages/console-shared/src/components/editor/CodeEditor.tsx b/frontend/packages/console-shared/src/components/editor/CodeEditor.tsx index b950bac3388..01e19bf05a2 100644 --- a/frontend/packages/console-shared/src/components/editor/CodeEditor.tsx +++ b/frontend/packages/console-shared/src/components/editor/CodeEditor.tsx @@ -1,9 +1,10 @@ import * as React from 'react'; import { EditorDidMount, Language } from '@patternfly/react-code-editor'; +import classNames from 'classnames'; import type * as monaco from 'monaco-editor'; import { CodeEditorRef, CodeEditorProps } from '@console/dynamic-plugin-sdk'; import { BasicCodeEditor } from './BasicCodeEditor'; -import CodeEditorToolbar from './CodeEditorToolbar'; +import { CodeEditorToolbar } from './CodeEditorToolbar'; import { useShortcutLink } from './ShortcutsLink'; import { useConsoleMonacoTheme } from './theme'; import { registerYAMLinMonaco, registerAutoFold, defaultEditorOptions } from './yaml-editor-utils'; @@ -74,7 +75,7 @@ const CodeEditor = React.forwardRef((props, ref) return ( = ({ showShortcuts, toolbarLinks }) => { - const { t } = useTranslation(); +export const AskOpenShiftLightspeedButton: React.FC = () => { + const { t } = useTranslation('console-shared'); const openOLS = () => action(ActionType.OpenOLS); const showLightspeedButton = useOLSConfig(); const dispatch = useDispatch(); + + return showLightspeedButton ? ( + + ) : null; +}; + +export const CodeEditorToolbar: React.FC = ({ + showShortcuts, + toolbarLinks, +}) => { + const { t } = useTranslation('console-shared'); if (!showShortcuts && !toolbarLinks?.length) return null; return ( - - - {showLightspeedButton && ( - - )} - {toolbarLinks && - toolbarLinks.map((link, index) => ( - // eslint-disable-next-line react/no-array-index-key -
- {link} -
- ))} -
+ <> + - - - {isMac ? '⌥ Opt' : 'Alt'} - F1 - - - {t('console-shared~Accessibility help')} - - -
+ + + {toolbarLinks && + toolbarLinks.map((link, index) => ( + // eslint-disable-next-line react/no-array-index-key +
+ {link} +
+ ))} +
+ + + + {isMac ? '⌥ Opt' : 'Alt'} + F1 + + {t('Accessibility help')} + +
+ ); }; export default CodeEditorToolbar; diff --git a/frontend/packages/console-shared/src/components/editor/__tests__/CodeEditorToolbar.spec.tsx b/frontend/packages/console-shared/src/components/editor/__tests__/CodeEditorToolbar.spec.tsx index 83aa9ee1b78..a016d6bcd0b 100644 --- a/frontend/packages/console-shared/src/components/editor/__tests__/CodeEditorToolbar.spec.tsx +++ b/frontend/packages/console-shared/src/components/editor/__tests__/CodeEditorToolbar.spec.tsx @@ -7,7 +7,7 @@ import { useTranslation } from 'react-i18next'; import { useDispatch } from 'react-redux'; import { ActionType } from '@console/internal/reducers/ols'; import { useOLSConfig } from '../../../hooks/ols-hook'; -import CodeEditorToolbar from '../CodeEditorToolbar'; +import { AskOpenShiftLightspeedButton, CodeEditorToolbar } from '../CodeEditorToolbar'; jest.mock('react-i18next', () => ({ useTranslation: jest.fn(), @@ -44,19 +44,19 @@ describe('CodeEditorToolbar', () => { it('should render "Ask OpenShift Lightspeed" button when showLightspeedButton is true', () => { (useOLSConfig as jest.Mock).mockReturnValue(true); - wrapper = shallow(); + wrapper = shallow(); expect(wrapper.find(Button).prop('children')).toBe('console-shared~Ask OpenShift Lightspeed'); }); it('should not render "Ask OpenShift Lightspeed" button when showLightspeedButton is false', () => { (useOLSConfig as jest.Mock).mockReturnValue(false); - wrapper = shallow(); + wrapper = shallow(); expect(wrapper.find(Button).exists()).toBe(false); }); it('should dispatch OpenOLS action when "Ask OpenShift Lightspeed" button is clicked', () => { (useOLSConfig as jest.Mock).mockReturnValue(true); - wrapper = shallow(); + wrapper = shallow(); wrapper.find(Button).simulate('click'); expect(mockDispatch).toHaveBeenCalledWith({ type: ActionType.OpenOLS }); }); diff --git a/frontend/packages/console-shared/src/components/editor/yaml-editor-utils.ts b/frontend/packages/console-shared/src/components/editor/yaml-editor-utils.ts index 43b972b3b01..c183be5dc43 100644 --- a/frontend/packages/console-shared/src/components/editor/yaml-editor-utils.ts +++ b/frontend/packages/console-shared/src/components/editor/yaml-editor-utils.ts @@ -6,7 +6,6 @@ import { openAPItoJSONSchema } from '@console/internal/module/k8s/openapi-to-jso import { getSwaggerDefinitions } from '@console/internal/module/k8s/swagger'; export const defaultEditorOptions: monaco.editor.IEditorOptions = { - readOnly: false, scrollBeyondLastLine: false, automaticLayout: true, }; diff --git a/frontend/packages/dev-console/src/components/buildconfig/sections/EditorField.tsx b/frontend/packages/dev-console/src/components/buildconfig/sections/EditorField.tsx index 7d175b596a5..f53f6d810f8 100644 --- a/frontend/packages/dev-console/src/components/buildconfig/sections/EditorField.tsx +++ b/frontend/packages/dev-console/src/components/buildconfig/sections/EditorField.tsx @@ -1,20 +1,18 @@ import * as React from 'react'; -import { ChangeHandler, Language } from '@patternfly/react-code-editor'; +import { ChangeHandler } from '@patternfly/react-code-editor'; import { FormGroup, FormHelperText, HelperText, HelperTextItem } from '@patternfly/react-core'; import { FormikValues, useFormikContext } from 'formik'; -import type * as Monaco from 'monaco-editor/esm/vs/editor/editor.api'; +import { BasicCodeEditorProps } from '@console/dynamic-plugin-sdk'; import { RedExclamationCircleIcon, useDebounceCallback } from '@console/shared/src'; import { BasicCodeEditor } from '@console/shared/src/components/editor/BasicCodeEditor'; -type EditorFieldProps = { +type EditorFieldProps = Partial & { name: string; label?: React.ReactNode; helpText?: React.ReactNode; required?: boolean; isDisabled?: boolean; onChange?: ChangeHandler; - language?: Language; - options?: Monaco.editor.IEditorOptions; }; const EditorField: React.FC = ({ @@ -40,15 +38,12 @@ const EditorField: React.FC = ({ return ( From 5019fff3b3f33293d198057658d5c879a6738201 Mon Sep 17 00:00:00 2001 From: Sanket Pathak Date: Thu, 16 Jan 2025 02:46:42 +0530 Subject: [PATCH 034/618] Edit ci tests to have step to enable developer perspective --- .../support/commands/hooks.ts | 4 +- .../functions/checkDeveloperPerspective.ts | 28 +++++++++ .../pages/search-resources/search-page.ts | 3 +- .../features/helm-release.feature | 37 ++++++----- .../features/helm/helm-navigation.feature | 4 +- .../features/helm/helm-page-tabs.feature | 2 +- .../support/commands/hooks.ts | 7 ++- .../support/step-definitions/common/common.ts | 18 +++++- .../step-definitions/helm/helm-navigation.ts | 12 +++- .../step-definitions/helm/helm-release.ts | 24 ++++--- .../tests/app/auth-multiuser-login.cy.ts | 10 +-- .../tests/crud/other-routes.cy.ts | 2 + .../integration-tests-cypress/views/nav.ts | 62 ++++++++++++++++--- .../support/commands/hooks.ts | 4 ++ .../features/e2e/pipeline-ci.feature | 30 ++++----- .../integration-tests/support/commands/app.ts | 10 --- .../support/commands/hooks.ts | 5 +- .../support/commands/index.ts | 1 - .../support/step-definitions/common/common.ts | 37 +++++++++++ .../step-definitions/common/pipelines.ts | 7 +++ .../pipelines/create-from-builder-page.ts | 11 ++++ .../pipelines/pipelines-runs.ts | 15 +++++ .../pipelines/pipelines-secrets.ts | 14 +++++ .../support/commands/hooks.ts | 7 ++- .../features/e2e/topology-ci.feature | 7 ++- .../support/commands/hooks.ts | 3 +- .../step-definitions/common/topology.ts | 20 ++++++ .../web-terminal-adminuser.feature | 2 +- .../support/commands/hooks.ts | 5 +- .../step-definitions/common/webTerminal.ts | 9 ++- .../pages/web-terminal/initTerminal-page.ts | 7 +-- 31 files changed, 313 insertions(+), 94 deletions(-) create mode 100644 frontend/packages/dev-console/integration-tests/support/pages/functions/checkDeveloperPerspective.ts delete mode 100644 frontend/packages/pipelines-plugin/integration-tests/support/commands/app.ts diff --git a/frontend/packages/dev-console/integration-tests/support/commands/hooks.ts b/frontend/packages/dev-console/integration-tests/support/commands/hooks.ts index 59afd2f84fd..51c7aa11c69 100644 --- a/frontend/packages/dev-console/integration-tests/support/commands/hooks.ts +++ b/frontend/packages/dev-console/integration-tests/support/commands/hooks.ts @@ -1,5 +1,5 @@ -import { guidedTour } from '@console/cypress-integration-tests/views/guided-tour'; import { quickStartSidebarPO } from '../pageObjects/quickStarts-po'; +import { checkDeveloperPerspective } from '../pages/functions/checkDeveloperPerspective'; // To ignore the resizeObserverLoopErrors on CI, adding below code const resizeObserverLoopErrRe = /^[^(ResizeObserver loop limit exceeded)]/; @@ -19,7 +19,7 @@ before(() => { const bridgePasswordPassword: string = Cypress.env('BRIDGE_HTPASSWD_PASSWORD') || 'test'; cy.login(bridgePasswordIDP, bridgePasswordUsername, bridgePasswordPassword); cy.document().its('readyState').should('eq', 'complete'); - guidedTour.close(); + checkDeveloperPerspective(); }); after(() => { diff --git a/frontend/packages/dev-console/integration-tests/support/pages/functions/checkDeveloperPerspective.ts b/frontend/packages/dev-console/integration-tests/support/pages/functions/checkDeveloperPerspective.ts new file mode 100644 index 00000000000..3a156c2057a --- /dev/null +++ b/frontend/packages/dev-console/integration-tests/support/pages/functions/checkDeveloperPerspective.ts @@ -0,0 +1,28 @@ +import { guidedTour } from '@console/cypress-integration-tests/views/guided-tour'; + +export const checkDeveloperPerspective = () => { + cy.byLegacyTestID('perspective-switcher-toggle').click(); + cy.get('body').then(($body) => { + if ($body.find('[data-test-id="perspective-switcher-menu-option"]').length !== 0) { + cy.log('perspective switcher menu enabled'); + cy.byLegacyTestID('perspective-switcher-menu-option').contains('Developer'); + cy.byLegacyTestID('perspective-switcher-toggle').click(); + } else { + cy.exec( + `oc patch console.operator.openshift.io/cluster --type='merge' -p '{"spec":{"customization":{"perspectives":[{"id":"dev","visibility":{"state":"Enabled"}}]}}}'`, + { failOnNonZeroExit: true }, + ).then((result) => { + cy.log(result.stderr); + }); + cy.reload(true); + cy.document().its('readyState').should('eq', 'complete'); + cy.exec(` oc rollout status -w deploy/console -n openshift-console`, { + failOnNonZeroExit: true, + }).then((result) => { + cy.log(result.stderr); + }); + cy.log('perspective switcher menu refreshed'); + } + guidedTour.close(); + }); +}; diff --git a/frontend/packages/dev-console/integration-tests/support/pages/search-resources/search-page.ts b/frontend/packages/dev-console/integration-tests/support/pages/search-resources/search-page.ts index e4873d7c197..b8246c65082 100644 --- a/frontend/packages/dev-console/integration-tests/support/pages/search-resources/search-page.ts +++ b/frontend/packages/dev-console/integration-tests/support/pages/search-resources/search-page.ts @@ -8,7 +8,7 @@ const dataTestIdPref: string = 'data-test-id'; export function performResourceSearching(resourceName: string) { cy.get('[aria-label="Type to filter"]').click(); - cy.get('input[placeholder="Resources"]').clear().type(resourceName); + cy.get('input[placeholder="Resources"]').clear().focus().type(resourceName); cy.get(`label[id$="${resourceName}"]`).click(); } @@ -27,6 +27,7 @@ export const searchResource = { }); cy.get(adminNavigationMenuPO.home.search).click(); performResourceSearching(resourceName); + cy.get('[id="resource-dropdown-listbox"]').scrollTo('top', { ensureScrollable: false }); cy.byLegacyTestID('close-icon').should('be.visible').click({ force: true }); }, diff --git a/frontend/packages/helm-plugin/integration-tests/features/helm-release.feature b/frontend/packages/helm-plugin/integration-tests/features/helm-release.feature index 47a3fc905ce..68b1d3043ea 100644 --- a/frontend/packages/helm-plugin/integration-tests/features/helm-release.feature +++ b/frontend/packages/helm-plugin/integration-tests/features/helm-release.feature @@ -9,16 +9,16 @@ Feature: Helm Release Scenario: Open the Helm tab on the navigation bar when helm charts are absent: HR-05-TC01 - Given user is at developer perspective - When user clicks on the Helm tab - Then user will be redirected to Helm releases page + Given user is at administrator perspective + When user clicks on the Helm Release tab in admin perspective + Then user will be redirected to Helm releases page under Helm tab And user is able to see the message "No Helm Releases found" And user will get the link to install helm charts from developer catalog Scenario: Create Helm Release page details: HR-05-TC02 - Given user is at Add page - When user selects "Helm Chart" card from add page + Given user is at Software Catalog page + When user selects Helm Charts type from Software Catalog page And user searches and selects "Nodejs" card from catalog page And user clicks on the Create button on side bar Then Create Helm Release page is displayed @@ -29,8 +29,8 @@ Feature: Helm Release Scenario: Install Helm Chart from +Add Page using Form View: HR-06-TC04 - Given user is at Add page - When user selects "Helm Chart" card from add page + Given user is at Software Catalog page + When user selects Helm Charts type from Software Catalog page And user searches and selects "Nodejs" card from catalog page And user clicks on the Create button on side bar And user enters Release Name as "nodejs-release" @@ -39,8 +39,8 @@ Feature: Helm Release And Topology page have the helm chart workload "nodejs-release" Scenario: Helm release status verification: HR-01-TC04 - Given user is at the Helm page - And user is able to see "nodejs-release" in helm page + Given user is at the Helm Release tab in admin perspective + And user is able to see "nodejs-release" in helm page in admin view And user is able to see the status and status icon of "nodejs-release" under helm releases tab And user is able to see the "PendingInstall", "PendingUpgrade" and "PendingRollback" options under filter bar When user clicks on the helm release name "nodejs-release" @@ -50,20 +50,19 @@ Feature: Helm Release And user is able to see the status and status icon of Revision history page Scenario: Context menu options of helm release: HR-01-TC01 - Given user is at the Topology page + Given user is at the Topology page in admin view When user right clicks on the helm release "nodejs-release" to open the context menu Then user is able to see the context menu with actions Upgrade and Delete Helm Release Scenario: Open the Helm tab on the navigation bar when helm charts are present: HR-05-TC05 - Given user is at the Helm page - When user clicks on the Helm tab - Then user will be redirected to Helm releases page + Given user is at the Helm Release tab in admin perspective + Then user will be redirected to Helm releases page under Helm tab And user will see the helm charts listed Scenario: Filter out deployed Helm Charts: HR-05-TC06 - Given user is at the Helm page + Given user is at the Helm Release tab in admin perspective When user clicks on the filter drop down And user selects checkbox for the "Deployed" Helm charts Then the checkbox for the "Deployed" Helm chart is checked @@ -71,7 +70,7 @@ Feature: Helm Release Scenario: Helm release details page: HR-05-TC13 - Given user is at the Helm page + Given user is at the Helm Release tab in admin perspective When user clicks on the helm release name "nodejs-release" Then user will see the Details page opened And user will see the Resources tab @@ -86,7 +85,7 @@ Feature: Helm Release Scenario: Perform Upgrade action on Helm Release through Context Menu: HR-08-TC04 - Given user is at the Topology page + Given user is at the Topology page in admin view When user right clicks on the helm release "nodejs-release" to open the context menu And user clicks on the "Upgrade" action And user upgrades the chart Version @@ -100,11 +99,11 @@ Feature: Helm Release And user clicks on the "Upgrade" action And user upgrades the chart Version And user clicks on the upgrade button - Then user will be redirected to Helm Releases page + Then user will be redirected to Helm Releases page under Helm tab Scenario: Perform Rollback action on Helm Release through Context Menu: HR-08-TC03 - Given user is at the Topology page + Given user is at the Topology page in admin view And user is on the topology sidebar of the helm release "nodejs-release" When user clicks on the Actions drop down menu And user clicks on the "Rollback" action @@ -114,7 +113,7 @@ Feature: Helm Release Scenario: Delete Helm Release through Context Menu: HR-01-TC03 - Given user is at the Topology page + Given user is at the Topology page in admin view When user right clicks on the helm release "nodejs-release" to open the context menu And user clicks on the "Delete Helm Release" action And user enters the release name "nodejs-release" diff --git a/frontend/packages/helm-plugin/integration-tests/features/helm/helm-navigation.feature b/frontend/packages/helm-plugin/integration-tests/features/helm/helm-navigation.feature index 55455e45e0b..3332899ff3d 100644 --- a/frontend/packages/helm-plugin/integration-tests/features/helm/helm-navigation.feature +++ b/frontend/packages/helm-plugin/integration-tests/features/helm/helm-navigation.feature @@ -9,7 +9,7 @@ Feature: Navigations on Helm Chart # The test expects that there are no helm releases but there is from the previous feature run. @broken-test Scenario: Open the Helm tab on the navigation bar when helm charts are absent: HR-05-TC01 - When user clicks on the Helm tab + When user clicks on the Helm tab in dev perspective Then user will be redirected to Helm releases page And user is able to see the message "No Helm Releases found" And user will get the link to install helm charts from developer catalog @@ -49,7 +49,7 @@ Feature: Navigations on Helm Chart @smoke Scenario: Open the Helm tab on the navigation bar when helm charts are present: HR-05-TC05 Given user is at the Helm page - When user clicks on the Helm tab + When user clicks on the Helm tab in dev perspective Then user will be redirected to Helm releases page And user will see the helm charts listed diff --git a/frontend/packages/helm-plugin/integration-tests/features/helm/helm-page-tabs.feature b/frontend/packages/helm-plugin/integration-tests/features/helm/helm-page-tabs.feature index 6eeb6cef539..32df53a9d5a 100644 --- a/frontend/packages/helm-plugin/integration-tests/features/helm/helm-page-tabs.feature +++ b/frontend/packages/helm-plugin/integration-tests/features/helm/helm-page-tabs.feature @@ -11,7 +11,7 @@ Feature: Add repositories tab in Helm navigation item @regression Scenario: Helm Page on developer perspective: HR-09-TC01 Given user is at developer perspective - When user clicks on the Helm tab + When user clicks on the Helm tab in dev perspective Then user is able to see Helm Releases and Repositories Tabs And user is able to see the message "No Helm Releases found" And user is able to see the link "Browse the catalog to discover available Helm Charts" diff --git a/frontend/packages/helm-plugin/integration-tests/support/commands/hooks.ts b/frontend/packages/helm-plugin/integration-tests/support/commands/hooks.ts index c1b2c4560e6..07233f4fc8b 100644 --- a/frontend/packages/helm-plugin/integration-tests/support/commands/hooks.ts +++ b/frontend/packages/helm-plugin/integration-tests/support/commands/hooks.ts @@ -1,3 +1,5 @@ +import { guidedTour } from '@console/cypress-integration-tests/views/guided-tour'; + // To ignore the resizeObserverLoopErrors on CI, adding below code const resizeObserverLoopErrRe = /^[^(ResizeObserver loop limit exceeded)]/; /* eslint-disable consistent-return */ @@ -15,16 +17,17 @@ before(() => { const bridgePasswordPassword: string = Cypress.env('BRIDGE_HTPASSWD_PASSWORD') || 'test'; cy.login(bridgePasswordIDP, bridgePasswordUsername, bridgePasswordPassword); cy.document().its('readyState').should('eq', 'complete'); - // set the user settings location to local storage, so that no need of deleting config map from openshift-console-user-settings namespace cy.window().then((win: any) => { win.SERVER_FLAGS.userSettingsLocation = 'localstorage'; }); // Default helm repo has been changed to a new repo, so executing below line to fix that issue cy.exec('oc apply -f test-data/red-hat-helm-charts.yaml'); + guidedTour.close(); }); beforeEach(() => { - cy.initDeveloper(); + cy.initAdmin(); + cy.byLegacyTestID('topology-header').should('exist').click({ force: true }); }); after(() => { diff --git a/frontend/packages/helm-plugin/integration-tests/support/step-definitions/common/common.ts b/frontend/packages/helm-plugin/integration-tests/support/step-definitions/common/common.ts index 74476af4abe..9f8ea0fd840 100644 --- a/frontend/packages/helm-plugin/integration-tests/support/step-definitions/common/common.ts +++ b/frontend/packages/helm-plugin/integration-tests/support/step-definitions/common/common.ts @@ -4,7 +4,6 @@ import { nav } from '@console/cypress-integration-tests/views/nav'; import { devNavigationMenu, switchPerspective, - addOptions, catalogCards, catalogTypes, } from '@console/dev-console/integration-tests/support/constants'; @@ -24,6 +23,10 @@ Given('user is at developer perspective', () => { // cy.testA11y('Developer perspective with guider tour modal'); }); +Given('user is at administrator perspective', () => { + perspective.switchTo(switchPerspective.Administrator); +}); + Given('user has created or selected namespace {string}', (projectName: string) => { Cypress.env('NAMESPACE', projectName); projectNameSpace.selectOrCreateProject(`${projectName}`); @@ -34,6 +37,11 @@ Given('user is at the Topology page', () => { topologyPage.verifyTopologyPage(); }); +Given('user is at the Topology page in admin view', () => { + cy.byLegacyTestID('topology-header').should('exist').click({ force: true }); + topologyPage.verifyTopologyPage(); +}); + When('user enters Git Repo url as {string}', (gitUrl: string) => { gitPage.enterGitUrl(gitUrl); gitPage.verifyValidatedMessage(gitUrl); @@ -85,8 +93,12 @@ When('user selects {string} card from add page', (cardName: string) => { addPage.selectCardFromOptions(cardName); }); -Given('user is at Developer Catalog page', () => { - addPage.selectCardFromOptions(addOptions.DeveloperCatalog); +Given('user is at Software Catalog page', () => { + cy.byLegacyTestID('developer-catalog-header').should('exist').click({ force: true }); +}); + +When('user selects Helm Charts type from Software Catalog page', () => { + catalogPage.selectCatalogType(catalogTypes.HelmCharts); }); When('user switches to the {string} tab', (tab: string) => { diff --git a/frontend/packages/helm-plugin/integration-tests/support/step-definitions/helm/helm-navigation.ts b/frontend/packages/helm-plugin/integration-tests/support/step-definitions/helm/helm-navigation.ts index 1a99e2fc509..eed359ad2bb 100644 --- a/frontend/packages/helm-plugin/integration-tests/support/step-definitions/helm/helm-navigation.ts +++ b/frontend/packages/helm-plugin/integration-tests/support/step-definitions/helm/helm-navigation.ts @@ -33,7 +33,8 @@ Given('user is at developer perspective', () => { perspective.switchTo(switchPerspective.Developer); }); -When('user clicks on the Helm tab', () => { +When('user clicks on the Helm tab in dev perspective', () => { + cy.get('[data-quickstart-id="qs-admin-nav-helm"]').should('be.visible').click({ force: true }); navigateTo(devNavigationMenu.Helm); }); @@ -41,6 +42,10 @@ Then('user will be redirected to Helm releases page', () => { detailsPage.titleShouldContain('Helm'); }); +Then('user will be redirected to Helm releases page under Helm tab', () => { + detailsPage.titleShouldContain(pageTitle.HelmReleases); +}); + Then('user is able to see the message {string}', (noHelmReleasesFound: string) => { helmPage.verifyMessage(noHelmReleasesFound); }); @@ -92,6 +97,11 @@ Given('user is at the Helm page', () => { navigateTo(devNavigationMenu.Helm); }); +Given('user is at the Helm Release tab in admin perspective', () => { + cy.byLegacyTestID('helm-releases-header').should('exist').click({ force: true }); + detailsPage.titleShouldContain(pageTitle.HelmReleases); +}); + When('user selects checkbox for the Deployed Helm charts', (workloadName: string) => { topologyPage.verifyWorkloadInTopologyPage(workloadName); }); diff --git a/frontend/packages/helm-plugin/integration-tests/support/step-definitions/helm/helm-release.ts b/frontend/packages/helm-plugin/integration-tests/support/step-definitions/helm/helm-release.ts index d5e9aad95d9..6d304a6844b 100644 --- a/frontend/packages/helm-plugin/integration-tests/support/step-definitions/helm/helm-release.ts +++ b/frontend/packages/helm-plugin/integration-tests/support/step-definitions/helm/helm-release.ts @@ -1,15 +1,10 @@ import { Given, When, Then } from 'cypress-cucumber-preprocessor/steps'; -import { - devNavigationMenu, - pageTitle, - helmActions, -} from '@console/dev-console/integration-tests/support/constants'; +import { pageTitle, helmActions } from '@console/dev-console/integration-tests/support/constants'; import { helmPO } from '@console/dev-console/integration-tests/support/pageObjects'; import { topologyPage, topologySidePane, app, - navigateTo, createHelmChartFromAddPage, } from '@console/dev-console/integration-tests/support/pages'; import { detailsPage } from '../../../../../integration-tests-cypress/views/details-page'; @@ -80,12 +75,23 @@ Then( ); Given('user is on the Helm page with helm release {string}', (helmRelease: string) => { - navigateTo(devNavigationMenu.Helm); + cy.get('[data-quickstart-id="qs-admin-nav-helm"]').should('be.visible').click({ force: true }); + cy.byLegacyTestID('helm-releases-header').should('exist').click({ force: true }); + detailsPage.titleShouldContain(pageTitle.HelmReleases); + helmPage.search(helmRelease); +}); + +Given('user is able to see {string} in helm page in admin view', (helmRelease: string) => { + cy.byLegacyTestID('helm-releases-header').should('exist').click({ force: true }); helmPage.search(helmRelease); }); -Then('user will be redirected to Helm Releases page', () => { - detailsPage.titleShouldContain(pageTitle.Helm); +When('user clicks on the Helm Release tab in admin perspective', () => { + cy.byLegacyTestID('helm-releases-header').should('exist').click({ force: true }); +}); + +Then('user will be redirected to Helm Releases page under Helm tab', () => { + detailsPage.titleShouldContain(pageTitle.HelmReleases); }); When('user clicks on the Kebab menu', () => { diff --git a/frontend/packages/integration-tests-cypress/tests/app/auth-multiuser-login.cy.ts b/frontend/packages/integration-tests-cypress/tests/app/auth-multiuser-login.cy.ts index ab9f7a7291d..a177d21a1dc 100644 --- a/frontend/packages/integration-tests-cypress/tests/app/auth-multiuser-login.cy.ts +++ b/frontend/packages/integration-tests-cypress/tests/app/auth-multiuser-login.cy.ts @@ -24,14 +24,16 @@ describe('Auth test', () => { const passwd = Cypress.env('BRIDGE_HTPASSWD_PASSWORD') || 'test'; cy.login(idp, username, passwd); cy.url().should('include', Cypress.config('baseUrl')); + // test Developer perspective is default for test user and guided tour is displayed - nav.sidenav.switcher.shouldHaveText('Developer'); + // Below line to be uncommented after pr https://github.com/openshift/console-operator/pull/954 is merged + // nav.sidenav.switcher.shouldHaveText('Administrator'); guidedTour.isOpen(); guidedTour.close(); masthead.username.shouldHaveText(username); cy.log('switches from dev to admin perspective'); - nav.sidenav.switcher.shouldHaveText('Developer'); + // nav.sidenav.switcher.shouldHaveText('Developer'); nav.sidenav.switcher.changePerspectiveTo('Administrator'); nav.sidenav.switcher.shouldHaveText('Administrator'); @@ -69,8 +71,8 @@ describe('Auth test', () => { // test guided tour is displayed first time switching to 'Developer' perspective // skip if running localhost if (!Cypress.config('baseUrl').includes('localhost')) { - nav.sidenav.switcher.changePerspectiveTo('Developer'); - nav.sidenav.switcher.shouldHaveText('Developer'); + // nav.sidenav.switcher.changePerspectiveTo('Developer'); + // nav.sidenav.switcher.shouldHaveText('Developer'); guidedTour.isOpen(); guidedTour.close(); nav.sidenav.switcher.changePerspectiveTo('Administrator'); diff --git a/frontend/packages/integration-tests-cypress/tests/crud/other-routes.cy.ts b/frontend/packages/integration-tests-cypress/tests/crud/other-routes.cy.ts index a64a8e95364..23a86d61ab5 100644 --- a/frontend/packages/integration-tests-cypress/tests/crud/other-routes.cy.ts +++ b/frontend/packages/integration-tests-cypress/tests/crud/other-routes.cy.ts @@ -1,3 +1,4 @@ +import { checkDeveloperPerspective } from '@console/dev-console/integration-tests/support/pages/functions/checkDeveloperPerspective'; import { checkErrors } from '../../support'; import { detailsPage } from '../../views/details-page'; import { guidedTour } from '../../views/guided-tour'; @@ -135,6 +136,7 @@ describe('Test perspective query parameters', () => { cy.visit('/k8s/cluster/projects'); listPage.rows.shouldBeLoaded(); guidedTour.close(); + checkDeveloperPerspective(); }); afterEach(() => { diff --git a/frontend/packages/integration-tests-cypress/views/nav.ts b/frontend/packages/integration-tests-cypress/views/nav.ts index 05264c27a74..afecf67cb3f 100644 --- a/frontend/packages/integration-tests-cypress/views/nav.ts +++ b/frontend/packages/integration-tests-cypress/views/nav.ts @@ -1,15 +1,63 @@ +import { app } from '@console/dev-console/integration-tests/support/pages'; +import { checkDeveloperPerspective } from '@console/dev-console/integration-tests/support/pages/functions/checkDeveloperPerspective'; + export const nav = { sidenav: { switcher: { shouldHaveText: (text: string) => cy.byLegacyTestID('perspective-switcher-toggle').scrollIntoView().contains(text), - changePerspectiveTo: (newPerspective: string) => - cy - .byLegacyTestID('perspective-switcher-toggle') - .click() - .byLegacyTestID('perspective-switcher-menu-option') - .contains(newPerspective) - .click({ force: true }), + changePerspectiveTo: (newPerspective: string) => { + app.waitForDocumentLoad(); + switch (newPerspective) { + case 'Administrator': + case 'administrator': + case 'Admin': + case 'admin': + cy.byLegacyTestID('perspective-switcher-toggle').then(($body) => { + if ($body.text().includes('Administrator')) { + cy.log('Already on admin perspective'); + cy.byLegacyTestID('perspective-switcher-toggle') + .scrollIntoView() + .contains(newPerspective); + } else { + cy.byLegacyTestID('perspective-switcher-toggle') + .click() + .byLegacyTestID('perspective-switcher-menu-option') + .contains(newPerspective) + .click({ force: true }); + } + }); + break; + case 'Developer': + case 'developer': + case 'Dev': + case 'dev': + cy.byLegacyTestID('perspective-switcher-toggle') + .should('be.visible') + .then(($body) => { + if ($body.text().includes('Developer')) { + cy.log('Already on dev perspective'); + cy.byLegacyTestID('perspective-switcher-toggle') + .scrollIntoView() + .contains(newPerspective); + } else { + checkDeveloperPerspective(); + cy.byLegacyTestID('perspective-switcher-toggle') + .click() + .byLegacyTestID('perspective-switcher-menu-option') + .contains(newPerspective) + .click({ force: true }); + } + }); + break; + default: + cy.byLegacyTestID('perspective-switcher-toggle') + .click() + .byLegacyTestID('perspective-switcher-menu-option') + .contains(newPerspective) + .click({ force: true }); + } + }, }, clusters: { shouldHaveText: (text: string) => cy.byLegacyTestID('cluster-dropdown-toggle').contains(text), diff --git a/frontend/packages/knative-plugin/integration-tests/support/commands/hooks.ts b/frontend/packages/knative-plugin/integration-tests/support/commands/hooks.ts index 04ae712c3f7..0ffd8c11f04 100644 --- a/frontend/packages/knative-plugin/integration-tests/support/commands/hooks.ts +++ b/frontend/packages/knative-plugin/integration-tests/support/commands/hooks.ts @@ -1,6 +1,7 @@ import { checkErrors } from '@console/cypress-integration-tests/support'; import { guidedTour } from '@console/cypress-integration-tests/views/guided-tour'; import { installKnativeOperatorUsingCLI } from '@console/dev-console/integration-tests/support/pages'; +import { checkDeveloperPerspective } from '@console/dev-console/integration-tests/support/pages/functions/checkDeveloperPerspective'; before(() => { cy.exec('../../../../contrib/create-user.sh'); @@ -9,6 +10,7 @@ before(() => { const bridgePasswordPassword: string = Cypress.env('BRIDGE_HTPASSWD_PASSWORD') || 'test'; cy.login(bridgePasswordIDP, bridgePasswordUsername, bridgePasswordPassword); cy.document().its('readyState').should('eq', 'complete'); + checkDeveloperPerspective(); installKnativeOperatorUsingCLI(); // To ignore the resizeObserverLoopErrors on CI, adding below code const resizeObserverLoopErrRe = /^[^(ResizeObserver loop limit exceeded)]/; @@ -25,6 +27,8 @@ before(() => { beforeEach(() => { cy.initDeveloper(); + // cy.initAdmin(); + // cy.byLegacyTestID('topology-header').should('exist').click({force: true}); }); after(() => { diff --git a/frontend/packages/pipelines-plugin/integration-tests/features/e2e/pipeline-ci.feature b/frontend/packages/pipelines-plugin/integration-tests/features/e2e/pipeline-ci.feature index 4c05f78017d..e2b0020be30 100644 --- a/frontend/packages/pipelines-plugin/integration-tests/features/e2e/pipeline-ci.feature +++ b/frontend/packages/pipelines-plugin/integration-tests/features/e2e/pipeline-ci.feature @@ -5,13 +5,13 @@ Feature: Entire pipeline flow from Builder page @pre-condition Scenario: Background Steps - Given user is at developer perspective + Given user is at administrator perspective And user has created or selected namespace "pipeline-flow" @smoke Scenario: Create a pipeline from pipeline builder page - Given user is at Pipeline Builder page + Given user is at Pipeline Builder page in admin view When user enters pipeline name as "flow" And user clicks Add task button under Tasks section And user searches "kn" in quick search bar @@ -25,7 +25,7 @@ Feature: Entire pipeline flow from Builder page @smoke Scenario: Pipelines page details - Given user is at pipelines page + Given user is at pipelines page in admin view When user searches pipeline "flow" in pipelines page Then pipelines table displayed with column names Name, Last Run, Task Status, Last Run Status and Last Run Time And pipelines column Name display with value "flow" @@ -36,7 +36,7 @@ Feature: Entire pipeline flow from Builder page @smoke Scenario: Pipelines Details page - Given user is at pipelines page + Given user is at pipelines page in admin view When user clicks pipeline name "flow" on Pipelines page Then user will be redirected to Pipeline Details page with header name "flow" And user is able to see Details, Metrics, YAML, Pipeline Runs and Parameters tabs @@ -46,7 +46,7 @@ Feature: Entire pipeline flow from Builder page @smoke Scenario: Add the task by editing the pipeline - Given user is at pipelines page + Given user is at pipelines page in admin view When user selects "Edit Pipeline" option from kebab menu of "flow" When user adds parallel task "openshift-client" And user clicks save on edit pipeline page @@ -54,7 +54,7 @@ Feature: Entire pipeline flow from Builder page @smoke Scenario: Start the pipeline with workspace - Given user is at pipelines page + Given user is at pipelines page in admin view When user selects "Start" option from kebab menu for pipeline "flow" And user navigates to Workspaces section # And user selects "VolumeClaimTemplate" option from workspace dropdown @@ -67,7 +67,7 @@ Feature: Entire pipeline flow from Builder page @smoke Scenario: Pipeline Run Details page - Given user is at pipelines page + Given user is at pipelines page in admin view When user clicks Last Run value of the pipeline "flow" Then user will be redirected to Pipeline Run Details page And user is able to see Details, YAML, TaskRuns, Parameters, Logs, Events and Output tabs @@ -77,7 +77,7 @@ Feature: Entire pipeline flow from Builder page @smoke Scenario: Rerun the Pipeline Run from pipeline runs page: P-07-TC07 - Given user is at pipelines page + Given user is at pipelines page in admin view When user selects the Pipeline Run for "flow" And user navigates to Pipeline runs page And user selects Rerun option from kebab menu of "flow" @@ -85,8 +85,8 @@ Feature: Entire pipeline flow from Builder page @smoke Scenario: Add secret to pipeline with authentication type as Basic Authentication: P-08-TC02 - Given user is at pipelines page - And user is at Start Pipeline modal for pipeline "flow" + Given user is at pipelines page in admin view + And user is at Start Pipeline modal in admin view for pipeline "flow" When user clicks on Show Credentials link present in Start Pipeline modal And user clicks on Add Secret link And user enters Secret Name as "basic-secret" @@ -99,7 +99,7 @@ Feature: Entire pipeline flow from Builder page @smoke Scenario: Add trigger to the pipeline: P-09-TC02 - Given user is at pipelines page + Given user is at pipelines page in admin view When user selects "Add Trigger" from the kebab menu for "flow" And user selects the "github-pullreq" from Git Provider Type field And user clicks on Add button present in Add Trigger modal @@ -108,7 +108,7 @@ Feature: Entire pipeline flow from Builder page @smoke Scenario: Remove the trigger from pipelines page: P-09-TC08 - Given user is at pipelines page + Given user is at pipelines page in admin view When user selects "Remove Trigger" from the kebab menu for "flow" And user selects the first option from the Trigger Template drop down field And user clicks on Remove button @@ -116,17 +116,17 @@ Feature: Entire pipeline flow from Builder page @smoke Scenario: Delete the Pipeline Run - Given user is at pipelines page + Given user is at pipelines page in admin view When user selects the Pipeline Run for "flow" And user navigates to Pipeline runs page And user selects Delete PipelineRun option from kebab menu of "flow" And user clicks Delete button present in Delete PipelineRun modal Then page will be redirected to pipeline runs page - And pipeline run is deleted from pipeline runs page + And pipeline run is deleted from pipeline runs page in admin view @smoke Scenario: Delete the Pipeline from pipelines page: P-06-TC10 - Given user is at pipelines page + Given user is at pipelines page in admin view When user selects "Delete Pipeline" from the kebab menu for "flow" And user clicks Delete button on Delete Pipeline modal Then user will be redirected to Pipelines page diff --git a/frontend/packages/pipelines-plugin/integration-tests/support/commands/app.ts b/frontend/packages/pipelines-plugin/integration-tests/support/commands/app.ts deleted file mode 100644 index 22238ca950e..00000000000 --- a/frontend/packages/pipelines-plugin/integration-tests/support/commands/app.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { - devNavigationMenu, - switchPerspective, -} from '@console/dev-console/integration-tests/support/constants/global'; -import { navigateTo, perspective } from '@console/dev-console/integration-tests/support/pages'; - -beforeEach(() => { - perspective.switchTo(switchPerspective.Developer); - navigateTo(devNavigationMenu.Add); -}); diff --git a/frontend/packages/pipelines-plugin/integration-tests/support/commands/hooks.ts b/frontend/packages/pipelines-plugin/integration-tests/support/commands/hooks.ts index 53487b6d403..10ebe0e3ca0 100644 --- a/frontend/packages/pipelines-plugin/integration-tests/support/commands/hooks.ts +++ b/frontend/packages/pipelines-plugin/integration-tests/support/commands/hooks.ts @@ -1,4 +1,5 @@ import { checkErrors } from '@console/cypress-integration-tests/support'; +import { guidedTour } from '@console/cypress-integration-tests/views/guided-tour'; import { installPipelinesOperatorUsingCLI } from '@console/dev-console/integration-tests/support/pages'; before(() => { @@ -8,6 +9,7 @@ before(() => { cy.login(bridgePasswordIDP, bridgePasswordUsername, bridgePasswordPassword); cy.document().its('readyState').should('eq', 'complete'); installPipelinesOperatorUsingCLI(); + guidedTour.close(); }); after(() => { @@ -20,7 +22,8 @@ after(() => { }); beforeEach(() => { - cy.initDeveloper(); + cy.initAdmin(); + cy.byLegacyTestID('topology-header').should('exist').click({ force: true }); }); afterEach(() => { diff --git a/frontend/packages/pipelines-plugin/integration-tests/support/commands/index.ts b/frontend/packages/pipelines-plugin/integration-tests/support/commands/index.ts index 0a8770fd268..b9d5f4e2192 100644 --- a/frontend/packages/pipelines-plugin/integration-tests/support/commands/index.ts +++ b/frontend/packages/pipelines-plugin/integration-tests/support/commands/index.ts @@ -4,6 +4,5 @@ import '../../../../integration-tests-cypress/support/a11y'; import '../../../../integration-tests-cypress/support/login'; import '../../../../integration-tests-cypress/support/project'; import '../../../../integration-tests-cypress/support/index'; -import './app'; import '../../../../dev-console/integration-tests/support/commands/app'; import './hooks'; diff --git a/frontend/packages/pipelines-plugin/integration-tests/support/step-definitions/common/common.ts b/frontend/packages/pipelines-plugin/integration-tests/support/step-definitions/common/common.ts index 1081f6de672..ad61ff9cda9 100644 --- a/frontend/packages/pipelines-plugin/integration-tests/support/step-definitions/common/common.ts +++ b/frontend/packages/pipelines-plugin/integration-tests/support/step-definitions/common/common.ts @@ -38,9 +38,37 @@ Given('user is at developer perspective', () => { cy.testA11y('Developer perspective'); }); +Given('user is at administrator perspective', () => { + perspective.switchTo(switchPerspective.Administrator); +}); + Given('user has created or selected namespace {string}', (projectName: string) => { Cypress.env('NAMESPACE', projectName); projectNameSpace.selectOrCreateProject(projectName); + // eslint-disable-next-line cypress/no-unnecessary-waiting + cy.wait(20000); + cy.exec( + `kubectl -n ${Cypress.env( + 'NAMESPACE', + )} wait --for condition=established --timeout=80s crd/pipelineruns.tekton.dev`, + { + failOnNonZeroExit: false, + }, + ).then(function (result) { + cy.log(`STDOUT: ${result.stdout}`); + cy.log(`STDERR: ${result.stderr}`); + }); + cy.exec( + `kubectl -n ${Cypress.env( + 'NAMESPACE', + )} wait --for condition=established --timeout=80s crd/tasks.tekton.dev`, + { + failOnNonZeroExit: false, + }, + ).then(function (result) { + cy.log(`STDOUT: ${result.stdout}`); + cy.log(`STDERR: ${result.stderr}`); + }); cy.exec( `oc apply -f testData/installTasksInsteadOfClusterTask.yaml -n ${Cypress.env('NAMESPACE')}`, { @@ -50,6 +78,15 @@ Given('user has created or selected namespace {string}', (projectName: string) = cy.log(`STDOUT: ${result.stdout}`); cy.log(`STDERR: ${result.stderr}`); }); + cy.exec( + `oc wait --for=condition=ready pod -l app=tekton-operator -n openshift-operators --timeout=800s`, + { + failOnNonZeroExit: false, + }, + ).then(function (result) { + cy.log(`STDOUT: ${result.stdout}`); + cy.log(`STDERR: ${result.stderr}`); + }); }); Given('user is at the Topology page', () => { diff --git a/frontend/packages/pipelines-plugin/integration-tests/support/step-definitions/common/pipelines.ts b/frontend/packages/pipelines-plugin/integration-tests/support/step-definitions/common/pipelines.ts index d7a5666582b..ebf0bcf2ddf 100644 --- a/frontend/packages/pipelines-plugin/integration-tests/support/step-definitions/common/pipelines.ts +++ b/frontend/packages/pipelines-plugin/integration-tests/support/step-definitions/common/pipelines.ts @@ -87,6 +87,13 @@ Given('user is at pipelines page', () => { cy.get(pipelinesPO.pipelinesTab).click(); }); +Given('user is at pipelines page in admin view', () => { + cy.get('[data-test="nav"][data-quickstart-id="qs-nav-pipelines"]') + .should('exist') + .click({ force: true }); + cy.get(pipelinesPO.pipelinesTab).click(); +}); + Given('user has installed OpenShift Pipelines operator using cli', () => { cy.exec(`oc apply -f testData/installPipelinesOperator.yaml`); cy.exec( diff --git a/frontend/packages/pipelines-plugin/integration-tests/support/step-definitions/pipelines/create-from-builder-page.ts b/frontend/packages/pipelines-plugin/integration-tests/support/step-definitions/pipelines/create-from-builder-page.ts index 8b84663c553..ab8354c73c3 100644 --- a/frontend/packages/pipelines-plugin/integration-tests/support/step-definitions/pipelines/create-from-builder-page.ts +++ b/frontend/packages/pipelines-plugin/integration-tests/support/step-definitions/pipelines/create-from-builder-page.ts @@ -59,6 +59,17 @@ Given('user is at Pipeline Builder page', () => { .should('be.checked'); }); +Given('user is at Pipeline Builder page in admin view', () => { + cy.get('[data-test="nav"][data-quickstart-id="qs-nav-pipelines"]') + .should('exist') + .click({ force: true }); + pipelinesPage.clickOnCreatePipeline(); + pipelineBuilderPage.verifyTitle(); + cy.get(pipelineBuilderPO.configureVia.pipelineBuilder) + .check({ force: true }) + .should('be.checked'); +}); + When('user enters pipeline name as {string}', (pipelineName: string) => { Cypress.env('PIPELINE_NAME', pipelineName); pipelineBuilderPage.enterPipelineName(pipelineName); diff --git a/frontend/packages/pipelines-plugin/integration-tests/support/step-definitions/pipelines/pipelines-runs.ts b/frontend/packages/pipelines-plugin/integration-tests/support/step-definitions/pipelines/pipelines-runs.ts index c4763a78587..a6bd887314c 100644 --- a/frontend/packages/pipelines-plugin/integration-tests/support/step-definitions/pipelines/pipelines-runs.ts +++ b/frontend/packages/pipelines-plugin/integration-tests/support/step-definitions/pipelines/pipelines-runs.ts @@ -218,6 +218,21 @@ Then('pipeline run is deleted from pipeline runs page', () => { }); }); +Then('pipeline run is deleted from pipeline runs page in admin view', () => { + cy.get('[data-test="nav"][data-quickstart-id="qs-nav-pipelines"]') + .should('exist') + .click({ force: true }); + cy.get(pipelinesPO.pipelinesTab).click(); + cy.byLegacyTestID('flow').click(); + cy.get(pipelineDetailsPO.pipelineRunsTab).click(); + cy.get(pipelineRunsPO.pipelineRunsTable.table) + .find('tr') + .then(($ele) => { + numOfPipelineRunsAfterDeletion = $ele.length; + expect(numOfPipelineRunsAfterDeletion).toBeLessThan(numOfPipelineRunsBeforeDeletion); + }); +}); + When('user clicks Delete button present in Delete PipelineRun modal', () => { modal.modalTitleShouldContain('Delete PipelineRun?'); modal.submit(true); diff --git a/frontend/packages/pipelines-plugin/integration-tests/support/step-definitions/pipelines/pipelines-secrets.ts b/frontend/packages/pipelines-plugin/integration-tests/support/step-definitions/pipelines/pipelines-secrets.ts index 7189e43cd88..1a855c8b7f3 100644 --- a/frontend/packages/pipelines-plugin/integration-tests/support/step-definitions/pipelines/pipelines-secrets.ts +++ b/frontend/packages/pipelines-plugin/integration-tests/support/step-definitions/pipelines/pipelines-secrets.ts @@ -42,6 +42,20 @@ Given('user is at Start Pipeline modal for pipeline {string}', (pipelineName: st app.waitForLoad(); }); +Given( + 'user is at Start Pipeline modal in admin view for pipeline {string}', + (pipelineName: string) => { + cy.get('[data-test="nav"][data-quickstart-id="qs-nav-pipelines"]') + .should('exist') + .click({ force: true }); + cy.get(pipelinesPO.pipelinesTab).click(); + pipelinesPage.search(pipelineName); + pipelinesPage.selectActionForPipeline(pipelineName, pipelineActions.Start); + modal.modalTitleShouldContain('Start Pipeline'); + app.waitForLoad(); + }, +); + When('user enters URL, Revision as {string} and {string}', (gitUrl: string, revision: string) => { startPipelineInPipelinesPage.addGitResource(gitUrl, revision); }); diff --git a/frontend/packages/shipwright-plugin/integration-tests/support/commands/hooks.ts b/frontend/packages/shipwright-plugin/integration-tests/support/commands/hooks.ts index bf5ba316193..ae8ae4b19f5 100644 --- a/frontend/packages/shipwright-plugin/integration-tests/support/commands/hooks.ts +++ b/frontend/packages/shipwright-plugin/integration-tests/support/commands/hooks.ts @@ -1,4 +1,4 @@ -import { guidedTour } from '@console/cypress-integration-tests/views/guided-tour'; +import { checkDeveloperPerspective } from '@console/dev-console/integration-tests/support/pages/functions/checkDeveloperPerspective'; import { installShipwrightOperatorUsingCLI } from '@console/dev-console/integration-tests/support/pages/functions/installOperatorOnClusterUsingCLI'; // To ignore the resizeObserverLoopErrors on CI, adding below code @@ -15,8 +15,7 @@ Cypress.on('uncaught:exception', (err, runnable, promise) => { before(() => { cy.exec('../../../../contrib/create-user.sh'); cy.login(); - cy.document().its('readyState').should('eq', 'complete'); - guidedTour.close(); + checkDeveloperPerspective(); installShipwrightOperatorUsingCLI(); }); @@ -27,6 +26,8 @@ after(() => { beforeEach(() => { cy.initDeveloper(); + // cy.initAdmin() + // cy.byLegacyTestID('topology-header').should('exist').click({force: true}); }); afterEach(() => { diff --git a/frontend/packages/topology/integration-tests/features/e2e/topology-ci.feature b/frontend/packages/topology/integration-tests/features/e2e/topology-ci.feature index e18cc99f89e..ad2bf8def4a 100644 --- a/frontend/packages/topology/integration-tests/features/e2e/topology-ci.feature +++ b/frontend/packages/topology/integration-tests/features/e2e/topology-ci.feature @@ -5,12 +5,13 @@ Feature: Perform actions on topology @pre-condition Scenario: Background steps - Given user is at developer perspective + Given user is at administrator perspective + And user is at Topology page in the admin view And user has created or selected namespace "aut-topology-ci" Scenario: Empty state of topology: T-06-TC01 - When user navigates to Topology page + When user navigates to Topology page in admin view Then user sees Topology page with message "No resources found" And user is able to see Start building your application, Add page links And Display options dropdown, Filter by resource and Find by name fields are disabled @@ -57,4 +58,4 @@ Feature: Perform actions on topology And user clicks on Action menu And user clicks "Delete Deployment" from action menu And user clicks on Delete button from modal - Then user will see workload disappeared from topology \ No newline at end of file + Then user will see workload disappeared from topology in admin view \ No newline at end of file diff --git a/frontend/packages/topology/integration-tests/support/commands/hooks.ts b/frontend/packages/topology/integration-tests/support/commands/hooks.ts index a9e2a8a7f4c..bb56842f261 100644 --- a/frontend/packages/topology/integration-tests/support/commands/hooks.ts +++ b/frontend/packages/topology/integration-tests/support/commands/hooks.ts @@ -21,7 +21,8 @@ after(() => { }); beforeEach(() => { - cy.initDeveloper(); + cy.initAdmin(); + cy.byLegacyTestID('topology-header').should('exist').click({ force: true }); }); afterEach(() => { diff --git a/frontend/packages/topology/integration-tests/support/step-definitions/common/topology.ts b/frontend/packages/topology/integration-tests/support/step-definitions/common/topology.ts index 80dca55d2fd..aefea8d87e7 100644 --- a/frontend/packages/topology/integration-tests/support/step-definitions/common/topology.ts +++ b/frontend/packages/topology/integration-tests/support/step-definitions/common/topology.ts @@ -25,6 +25,10 @@ import { topologyPO } from '@console/topology/integration-tests/support/page-obj import { topologyPage } from '@console/topology/integration-tests/support/pages/topology/topology-page'; import { topologySidePane } from '@console/topology/integration-tests/support/pages/topology/topology-side-pane-page'; +Given('user is at administrator perspective', () => { + perspective.switchTo(switchPerspective.Administrator); +}); + Given('user is at the Topology page', () => { navigateTo(devNavigationMenu.Topology); topologyPage.verifyTopologyPage(); @@ -35,10 +39,20 @@ Given('user is at Topology page', () => { topologyPage.verifyTopologyPage(); }); +Given('user is at Topology page in the admin view', () => { + cy.get('[data-quickstart-id="qs-nav-workloads"]').should('be.visible').click({ force: true }); + cy.byLegacyTestID('topology-header').should('be.visible').click({ force: true }); + topologyPage.verifyTopologyPage(); +}); + When('user navigates to Topology page', () => { navigateTo(devNavigationMenu.Topology); }); +When('user navigates to Topology page in admin view', () => { + cy.byLegacyTestID('topology-header').should('exist').click({ force: true }); +}); + Then('user is able to see workload {string} in topology page list view', (workloadName: string) => { topologyPage.verifyWorkloadInTopologyPage(workloadName); }); @@ -161,6 +175,12 @@ Then('user will see workload disappeared from topology', () => { cy.get(topologyPO.emptyStateIcon).should('be.visible'); }); +Then('user will see workload disappeared from topology in admin view', () => { + cy.byLegacyTestID('developer-catalog-header').should('exist').click({ force: true }); + cy.byLegacyTestID('topology-header').should('exist').click({ force: true }); + cy.get(topologyPO.emptyStateIcon).should('be.visible'); +}); + Given('user has installed OpenShift Serverless Operator', () => { installKnativeOperatorUsingCLI(); }); diff --git a/frontend/packages/webterminal-plugin/integration-tests/features/web-terminal/web-terminal-adminuser.feature b/frontend/packages/webterminal-plugin/integration-tests/features/web-terminal/web-terminal-adminuser.feature index 937602d7d15..678bc4b5251 100644 --- a/frontend/packages/webterminal-plugin/integration-tests/features/web-terminal/web-terminal-adminuser.feature +++ b/frontend/packages/webterminal-plugin/integration-tests/features/web-terminal/web-terminal-adminuser.feature @@ -5,7 +5,7 @@ Feature: Web Terminal for Admin user Background: Given user has logged in as admin user - And user is at developer perspective + And user is at administrator perspective # And user has created or selected namespace "aut-terminal" diff --git a/frontend/packages/webterminal-plugin/integration-tests/support/commands/hooks.ts b/frontend/packages/webterminal-plugin/integration-tests/support/commands/hooks.ts index e7a1ba94367..02510aa1f07 100644 --- a/frontend/packages/webterminal-plugin/integration-tests/support/commands/hooks.ts +++ b/frontend/packages/webterminal-plugin/integration-tests/support/commands/hooks.ts @@ -5,8 +5,8 @@ import { installWebterminalOperatorUsingCLI } from '@console/dev-console/integra before(() => { cy.login(); cy.document().its('readyState').should('eq', 'complete'); - guidedTour.close(); installWebterminalOperatorUsingCLI(); + guidedTour.close(); }); after(() => { @@ -19,7 +19,8 @@ after(() => { }); beforeEach(() => { - cy.initDeveloper(); + cy.initAdmin(); + cy.byLegacyTestID('topology-header').should('exist').click({ force: true }); }); afterEach(() => { diff --git a/frontend/packages/webterminal-plugin/integration-tests/support/step-definitions/common/webTerminal.ts b/frontend/packages/webterminal-plugin/integration-tests/support/step-definitions/common/webTerminal.ts index d85b2a53d94..9ded942df59 100644 --- a/frontend/packages/webterminal-plugin/integration-tests/support/step-definitions/common/webTerminal.ts +++ b/frontend/packages/webterminal-plugin/integration-tests/support/step-definitions/common/webTerminal.ts @@ -47,8 +47,9 @@ When('user clicks on the Web Terminal icon on the Masthead', () => { }); Then('user will see the terminal window', () => { - cy.wait(15000); - webTerminalPage.verifyConnectionRediness(); + cy.get('.co-cloudshell-terminal__container').should('be.visible'); + // cy.wait(15000); + // webTerminalPage.verifyConnectionRediness(); }); // check existing of web terminal in the dedicated project. Create it for the correct checking if a webterminal instance is not existed. @@ -83,6 +84,10 @@ Given('user is at developer perspective', () => { perspective.switchTo(switchPerspective.Developer); }); +Given('user is at administrator perspective', () => { + perspective.switchTo(switchPerspective.Administrator); +}); + Given('user has created or selected namespace {string}', (projectName: string) => { Cypress.env('NAMESPACE', projectName); projectNameSpace.selectOrCreateProject(`${projectName}`); diff --git a/frontend/packages/webterminal-plugin/integration-tests/support/step-definitions/pages/web-terminal/initTerminal-page.ts b/frontend/packages/webterminal-plugin/integration-tests/support/step-definitions/pages/web-terminal/initTerminal-page.ts index 11e6e7b61ff..cd207d60ccb 100644 --- a/frontend/packages/webterminal-plugin/integration-tests/support/step-definitions/pages/web-terminal/initTerminal-page.ts +++ b/frontend/packages/webterminal-plugin/integration-tests/support/step-definitions/pages/web-terminal/initTerminal-page.ts @@ -1,4 +1,3 @@ -import { guidedTour } from '@console/cypress-integration-tests/views/guided-tour'; import { switchPerspective } from '@console/dev-console/integration-tests/support/constants'; import { formPO } from '@console/dev-console/integration-tests/support/pageObjects'; import { webTerminalPO } from '@console/dev-console/integration-tests/support/pageObjects/webterminal-po'; @@ -35,11 +34,11 @@ export const initTerminalPage = { cy.get(webTerminalPO.terminalCloseWindowBtn).click(); cy.reload(); app.waitForDocumentLoad(); - perspective.switchTo(switchPerspective.Developer); + perspective.switchTo(switchPerspective.Administrator); + cy.byLegacyTestID('topology-header').should('exist').click({ force: true }); projectNameSpace.selectProject('openshift-terminal'); - guidedTour.close(); webTerminalPage.clickOpenCloudShellBtn(); - searchResource.searchResourceByNameAsDev('DevWorkspace'); + searchResource.searchResourceByNameAsAdmin('DevWorkspace'); searchResource.selectSearchedItem('terminal'); // cy.get('[data-test="loading-indicator"]').should('not.exist', { timeout: 210000 }); } else { From 166e3921ba359f1a880bed80899a3bdc189a328a Mon Sep 17 00:00:00 2001 From: logonoff Date: Thu, 13 Feb 2025 17:13:29 -0500 Subject: [PATCH 035/618] CONSOLE-4407: Downgrade `monaco-editor` to `0.51.0` Unfortunately `monaco-editor` 0.52.2 causes e2e tests to flake: see https://github.com/microsoft/monaco-editor/issues/4702 --- frontend/package.json | 2 +- frontend/yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/frontend/package.json b/frontend/package.json index 3e21032e2fc..101c7ec4591 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -303,7 +303,7 @@ "mochawesome-merge": "^4.1.0", "mochawesome-report-generator": "^5.1.0", "mock-socket": "^9.0.3", - "monaco-editor": "^0.52.2", + "monaco-editor": "^0.51.0", "monaco-editor-webpack-plugin": "^7.1.0", "prettier": "2.0.5", "react-refresh": "^0.10.0", diff --git a/frontend/yarn.lock b/frontend/yarn.lock index 7fe79dc2f9f..38e9f319496 100644 --- a/frontend/yarn.lock +++ b/frontend/yarn.lock @@ -12394,10 +12394,10 @@ monaco-editor-webpack-plugin@^7.1.0: dependencies: loader-utils "^2.0.2" -monaco-editor@^0.52.2: - version "0.52.2" - resolved "https://registry.yarnpkg.com/monaco-editor/-/monaco-editor-0.52.2.tgz#53c75a6fcc6802684e99fd1b2700299857002205" - integrity sha512-GEQWEZmfkOGLdd3XK8ryrfWz3AIP8YymVXiPHEdewrUq7mh0qrKrfHLNCXcbB6sTnMLnOZ3ztSiKcciFUkIJwQ== +monaco-editor@^0.51.0: + version "0.51.0" + resolved "https://registry.yarnpkg.com/monaco-editor/-/monaco-editor-0.51.0.tgz#922a6103f6742b5a62fbb097276c5a6619d879db" + integrity sha512-xaGwVV1fq343cM7aOYB6lVE4Ugf0UyimdD/x5PWcWBMKENwectaEu77FAN7c5sFiyumqeJdX1RPTh1ocioyDjw== monaco-languageserver-types@^0.4.0: version "0.4.0" From b515d0e0fa46624bc9b822cc2b71861b69c61f44 Mon Sep 17 00:00:00 2001 From: logonoff Date: Thu, 13 Feb 2025 17:14:33 -0500 Subject: [PATCH 036/618] CONSOLE-4407: Style YAML hover tooltips to match OCP 4.18 --- .../src/components/editor/CodeEditor.scss | 13 ++++++++- .../src/components/editor/CodeEditor.tsx | 28 ++++++++++--------- .../components/editor/yaml-editor-utils.ts | 2 +- 3 files changed, 28 insertions(+), 15 deletions(-) diff --git a/frontend/packages/console-shared/src/components/editor/CodeEditor.scss b/frontend/packages/console-shared/src/components/editor/CodeEditor.scss index ba66cfa8d21..d52a6e810d9 100644 --- a/frontend/packages/console-shared/src/components/editor/CodeEditor.scss +++ b/frontend/packages/console-shared/src/components/editor/CodeEditor.scss @@ -1,9 +1,20 @@ @import '../../../../../public/style/vars'; .ocs-yaml-editor { - /* Makes automaticLayout work properly. note that this breaks height="sizeToFit" */ .monaco-editor { + // Makes automaticLayout work properly. note that this breaks height="sizeToFit" position: absolute !important; + + .monaco-hover-content .markdown-hover { + // matches tooltip styling seen back in OpenShift 4.18 + max-width: 500px; + word-wrap: break-word; + + // hide "Source: yaml" in hover tooltip + p:last-of-type { + display: none; + } + } } /* Hide CodeEditor toolbar on mobile */ diff --git a/frontend/packages/console-shared/src/components/editor/CodeEditor.tsx b/frontend/packages/console-shared/src/components/editor/CodeEditor.tsx index 01e19bf05a2..c383300dd54 100644 --- a/frontend/packages/console-shared/src/components/editor/CodeEditor.tsx +++ b/frontend/packages/console-shared/src/components/editor/CodeEditor.tsx @@ -3,6 +3,7 @@ import { EditorDidMount, Language } from '@patternfly/react-code-editor'; import classNames from 'classnames'; import type * as monaco from 'monaco-editor'; import { CodeEditorRef, CodeEditorProps } from '@console/dynamic-plugin-sdk'; +import { ErrorBoundaryInline } from '@console/shared/src/components/error'; import { BasicCodeEditor } from './BasicCodeEditor'; import { CodeEditorToolbar } from './CodeEditorToolbar'; import { useShortcutLink } from './ShortcutsLink'; @@ -17,7 +18,6 @@ const CodeEditor = React.forwardRef((props, ref) showShortcuts, toolbarLinks, onSave, - options, language, onEditorDidMount, } = props; @@ -73,18 +73,20 @@ const CodeEditor = React.forwardRef((props, ref) }, [toolbarLinks, showShortcuts]); return ( - + + + ); }); diff --git a/frontend/packages/console-shared/src/components/editor/yaml-editor-utils.ts b/frontend/packages/console-shared/src/components/editor/yaml-editor-utils.ts index c183be5dc43..de4f61ca8bd 100644 --- a/frontend/packages/console-shared/src/components/editor/yaml-editor-utils.ts +++ b/frontend/packages/console-shared/src/components/editor/yaml-editor-utils.ts @@ -7,7 +7,7 @@ import { getSwaggerDefinitions } from '@console/internal/module/k8s/swagger'; export const defaultEditorOptions: monaco.editor.IEditorOptions = { scrollBeyondLastLine: false, - automaticLayout: true, + automaticLayout: true, // paired with position: absolute for auto-resizing }; const findManagedMetadata = (model: monaco.editor.ITextModel) => { From c14790b67abb7e9a307587c08f752cdf1a642e1e Mon Sep 17 00:00:00 2001 From: logonoff Date: Thu, 13 Feb 2025 17:44:20 -0500 Subject: [PATCH 037/618] CONSOLE-4407: Use `ResizeObserver` over automaticLayout `automaticLayout` has a few problems in this older version, see https://github.com/microsoft/monaco-editor/issues/4277 --- .../src/components/editor/BasicCodeEditor.tsx | 17 +++++++++++++++++ .../src/components/editor/CodeEditor.scss | 3 --- .../src/components/editor/yaml-editor-utils.ts | 1 - 3 files changed, 17 insertions(+), 4 deletions(-) diff --git a/frontend/packages/console-shared/src/components/editor/BasicCodeEditor.tsx b/frontend/packages/console-shared/src/components/editor/BasicCodeEditor.tsx index e23b0560e58..515f87a9c13 100644 --- a/frontend/packages/console-shared/src/components/editor/BasicCodeEditor.tsx +++ b/frontend/packages/console-shared/src/components/editor/BasicCodeEditor.tsx @@ -1,6 +1,7 @@ import * as React from 'react'; import { loader, Monaco } from '@monaco-editor/react'; import { CodeEditor } from '@patternfly/react-code-editor'; +import { getResizeObserver } from '@patternfly/react-core'; import classNames from 'classnames'; import * as monaco from 'monaco-editor'; import { useTranslation } from 'react-i18next'; @@ -23,6 +24,22 @@ export const BasicCodeEditor: React.FC = (props) => { const [monacoRef, setMonacoRef] = React.useState(null); useConsoleMonacoTheme(monacoRef?.editor); + // TODO(PF6): remove this when https://github.com/patternfly/patternfly-react/issues/11531 is fixed + const handleResize = React.useCallback(() => { + monacoRef?.editor?.getEditors()?.forEach((editor) => { + editor.layout({ width: 0, height: 0 }); + editor.layout(); + }); + }, [monacoRef]); + + React.useEffect(() => { + const observer = getResizeObserver(undefined, handleResize, true); + + return () => { + observer(); + }; + }, [handleResize]); + return ( { From 315038c689e22cfd010d3350164f97e13eb49904 Mon Sep 17 00:00:00 2001 From: Lokananda Prabhu Date: Thu, 13 Feb 2025 17:49:57 +0530 Subject: [PATCH 038/618] Added favorites navigation menu --- .../nav/FavoriteNavItemResource.tsx | 25 +++ .../nav/FavoriteNavItemResources.scss | 38 ++++ .../nav/FavoriteNavItemResources.tsx | 95 +++++++++ .../src/components/nav/PluginNavItem.tsx | 18 +- .../console-shared/src/constants/common.ts | 1 + .../components/utils/_favorite-button.scss | 11 ++ .../components/utils/favorite-button.tsx | 183 ++++++++++++++++++ frontend/public/components/utils/headings.tsx | 50 +++-- frontend/public/components/utils/index.tsx | 1 + frontend/public/locales/en/public.json | 7 + frontend/public/style.scss | 2 + frontend/public/style/_common.scss | 4 + 12 files changed, 410 insertions(+), 25 deletions(-) create mode 100644 frontend/packages/console-app/src/components/nav/FavoriteNavItemResource.tsx create mode 100644 frontend/packages/console-app/src/components/nav/FavoriteNavItemResources.scss create mode 100644 frontend/packages/console-app/src/components/nav/FavoriteNavItemResources.tsx create mode 100644 frontend/public/components/utils/_favorite-button.scss create mode 100644 frontend/public/components/utils/favorite-button.tsx diff --git a/frontend/packages/console-app/src/components/nav/FavoriteNavItemResource.tsx b/frontend/packages/console-app/src/components/nav/FavoriteNavItemResource.tsx new file mode 100644 index 00000000000..48cccd5adf4 --- /dev/null +++ b/frontend/packages/console-app/src/components/nav/FavoriteNavItemResource.tsx @@ -0,0 +1,25 @@ +import * as React from 'react'; +import { NavItem } from '@patternfly/react-core'; +import { NavLink } from 'react-router-dom'; +import { ResourceNSNavItem } from '@console/dynamic-plugin-sdk'; + +export const FavoriteNavItemResource: React.FC = ({ + className, + dataAttributes, + isActive, + to, + ...navLinkProps +}) => { + return ( + + + + ); +}; + +export type FavoriteNavItemResourceProps = { + to: string; + dataAttributes?: ResourceNSNavItem['properties']['dataAttributes']; + isActive: boolean; + className: string; +}; diff --git a/frontend/packages/console-app/src/components/nav/FavoriteNavItemResources.scss b/frontend/packages/console-app/src/components/nav/FavoriteNavItemResources.scss new file mode 100644 index 00000000000..eef834bbad3 --- /dev/null +++ b/frontend/packages/console-app/src/components/nav/FavoriteNavItemResources.scss @@ -0,0 +1,38 @@ +.oc-favorite-resource.pf-v6-c-nav__item { + .pf-v6-c-nav__link { + display: flex; + align-items: center; + justify-content: space-between; + flex-grow: 1; + overflow: hidden; + position: relative; + text-overflow: ellipsis; + white-space: nowrap; + padding-top: 0; + padding-bottom: 0; + padding-right: 0; + &:hover { + --pf-v6-c-nav__section-title--PaddingRight: 30px; + .oc-favorite-delete-button { + .pf-v6-c-button__text { + opacity: 1; + } + } + } + } +} + +.oc-favorite-delete-button { + background-color: transparent !important; + margin-left: auto; + margin-right: var(--pf-t--global--spacer--lg); +} + +.oc-favorite-menu { + padding-top: 0 !important; + padding-bottom: 0 !important; +} + +.oc-no-favorites-message { + padding-left: var(--pf-t--global--spacer--md); +} diff --git a/frontend/packages/console-app/src/components/nav/FavoriteNavItemResources.tsx b/frontend/packages/console-app/src/components/nav/FavoriteNavItemResources.tsx new file mode 100644 index 00000000000..2df29dc02a4 --- /dev/null +++ b/frontend/packages/console-app/src/components/nav/FavoriteNavItemResources.tsx @@ -0,0 +1,95 @@ +import * as React from 'react'; +import { Nav, NavExpandable, NavList, Button } from '@patternfly/react-core'; +import { StarIcon } from '@patternfly/react-icons'; +import * as classNames from 'classnames'; +import { useTranslation } from 'react-i18next'; +import { FAVORITES_CONFIG_MAP_KEY, FavoritesType } from '@console/internal/components/utils'; +import { FAVORITES_LOCAL_STORAGE_KEY, useUserSettingsCompatibility } from '@console/shared'; +import { FavoriteNavItemResource } from './FavoriteNavItemResource'; + +import './FavoriteNavItemResources.scss'; + +export const FavoriteNavItemResources: React.FC = () => { + const { t } = useTranslation(); + const [activeGroup, setActiveGroup] = React.useState(''); + const [activeItem, setActiveItem] = React.useState(''); + const currentUrlPath = window.location.pathname; + + const onSelect = ( + _event: React.FormEvent, + result: { itemId: number | string; groupId: number | string | null }, + ) => { + setActiveGroup(result.groupId as string); + setActiveItem(result.itemId as string); + }; + + const [favorites, setFavorites, loaded] = useUserSettingsCompatibility( + FAVORITES_CONFIG_MAP_KEY, + FAVORITES_LOCAL_STORAGE_KEY, + null, + true, + ); + + React.useEffect(() => { + if (loaded && favorites) { + const currentFavorite = favorites.find((favorite) => favorite.url === currentUrlPath); + if (currentFavorite) { + setActiveGroup('favorites-group'); + setActiveItem(`favorites-item-${currentFavorite.url}`); + } + } + }, [loaded, favorites, currentUrlPath]); + + const navList = React.useMemo(() => { + const handleUnfavorite = (favoriteUrl: string) => { + const updatedFavorites = favorites?.filter((favorite) => favorite.url !== favoriteUrl); + setFavorites(updatedFavorites); + if (activeItem === `favorites-item-${favoriteUrl}`) { + setActiveItem(''); + } + }; + if (!loaded) return null; + if (!favorites || favorites.length === 0) { + return
{t('public~No favorites added')}
; + } + + return favorites.map((favorite) => ( + + {favorite.name} +
+ + ) : ( + , + , + ]} + variant={ModalVariant.small} + > +
+ + handleNameChange(v)} + value={name || ''} + autoFocus + required + maxLength={20} + /> + {error && ( + + + }> + {error} + + + + )} + +
+ + )} + + ); +}); diff --git a/frontend/public/components/utils/headings.tsx b/frontend/public/components/utils/headings.tsx index 537377aa4be..250eecef8e6 100644 --- a/frontend/public/components/utils/headings.tsx +++ b/frontend/public/components/utils/headings.tsx @@ -16,7 +16,7 @@ import { ContentVariants, Title, } from '@patternfly/react-core'; -import { ResourceStatus } from '@console/dynamic-plugin-sdk'; +import { ResourceStatus, useActivePerspective } from '@console/dynamic-plugin-sdk'; import { RootState } from '@console/internal/redux'; import { OverviewItem, @@ -45,6 +45,7 @@ import { referenceForModel, } from '../../module/k8s'; import { ManagedByOperatorLink } from './managed-by'; +import { FavoriteButton } from './favorite-button'; export const ResourceItemDeleting = () => { const { t } = useTranslation(); @@ -123,6 +124,7 @@ export const PageHeading = connectToModel((props: PageHeadingProps) => { helpText, 'data-test': dataTestId, } = props; + const [perspective] = useActivePerspective(); const extraResources = _.reduce( props.resourceKeys, (extraObjs, key) => ({ ...extraObjs, [key]: _.get(props[key], 'data') }), @@ -170,6 +172,7 @@ export const PageHeading = connectToModel((props: PageHeadingProps) => { className={classNames({ 'co-m-pane__heading--logo': props.icon, 'co-m-pane__heading--with-help-text': helpText, + 'co-m-pane__heading--full-width': !showActions, })} alignItemsBaseline={!!link} centerText={centerText} @@ -196,25 +199,32 @@ export const PageHeading = connectToModel((props: PageHeadingProps) => { {badge} )} {link &&
{link}
} - {showActions && ( -
- {hasButtonActions && ( - a(kindObj, data))} /> - )} - {hasMenuActions && ( - a(kindObj, data, extraResources, customData)) - } - /> - )} - {_.isFunction(customActionMenu) - ? customActionMenu(kindObj, data) - : customActionMenu} -
- )} +
+ {perspective === 'admin' && ( +
+ +
+ )} + {showActions && ( + <> + {hasButtonActions && ( + a(kindObj, data))} /> + )} + {hasMenuActions && ( + a(kindObj, data, extraResources, customData)) + } + /> + )} + {_.isFunction(customActionMenu) + ? customActionMenu(kindObj, data) + : customActionMenu} + + )} +
)} {helpText && ( diff --git a/frontend/public/components/utils/index.tsx b/frontend/public/components/utils/index.tsx index 67a13f9feb9..96bfc24cd14 100644 --- a/frontend/public/components/utils/index.tsx +++ b/frontend/public/components/utils/index.tsx @@ -59,3 +59,4 @@ export * from './types'; export * from './release-notes-link'; export * from './service-level'; export * from './container-select'; +export * from './favorite-button'; diff --git a/frontend/public/locales/en/public.json b/frontend/public/locales/en/public.json index 58dacc006b4..7366c0e8c6b 100644 --- a/frontend/public/locales/en/public.json +++ b/frontend/public/locales/en/public.json @@ -1553,6 +1553,11 @@ "Add bookmark {{content}}": "Add bookmark {{content}}", "Remove favorite {{content}}": "Remove favorite {{content}}", "Add favorite {{content}}": "Add favorite {{content}}", + "Name is required.": "Name is required.", + "The name {{favoriteName}} already exists in your favorites. Choose a unique name to save to your favorites.": "The name {{favoriteName}} already exists in your favorites. Choose a unique name to save to your favorites.", + "Name can only contain letters, numbers, spaces, and hyphens.": "Name can only contain letters, numbers, spaces, and hyphens.", + "Maximum number of favorites ({{maxCount}}) reached. To add another favorite, remove an existing page from your favorites.": "Maximum number of favorites ({{maxCount}}) reached. To add another favorite, remove an existing page from your favorites.", + "Add to favorites": "Add to favorites", "Help": "Help", "Maximum file size exceeded. File limit is 4MB.": "Maximum file size exceeded. File limit is 4MB.", "Filename": "Filename", @@ -1808,6 +1813,8 @@ "Admission Webhook Warning": "Admission Webhook Warning", "{{kind}} {{name}} violates policy {{warning}}": "{{kind}} {{name}} violates policy {{warning}}", "Are you sure you want to remove <1>{{label}} from navigation?": "Are you sure you want to remove <1>{{label}} from navigation?", + "No favorites added": "No favorites added", + "Favorites": "Favorites", "Refer to your cluster administrator to know which network provider is used.": "Refer to your cluster administrator to know which network provider is used.", "Input error: selectors must start and end by a letter or number, and can only contain -, _, / or . Offending value: {{offendingSelector}}": "Input error: selectors must start and end by a letter or number, and can only contain -, _, / or . Offending value: {{offendingSelector}}", "Can't preview pods": "Can't preview pods", diff --git a/frontend/public/style.scss b/frontend/public/style.scss index c093bd6b5f9..93fd3bbaf91 100644 --- a/frontend/public/style.scss +++ b/frontend/public/style.scss @@ -113,5 +113,7 @@ @import 'components/dashboard/project-dashboard/activity-card'; @import 'components/dashboard/project-dashboard/details-card'; +@import 'components/utils/favorite-button'; + // Themes @import 'style/theme-dark'; diff --git a/frontend/public/style/_common.scss b/frontend/public/style/_common.scss index 6ac87de22d0..1c9ccd7a47e 100644 --- a/frontend/public/style/_common.scss +++ b/frontend/public/style/_common.scss @@ -164,6 +164,10 @@ dl.co-inline { font-size: $font-size-base; } +.co-m-pane__heading--full-width { + width: 100%; +} + .co-no-bold { font-weight: var(--pf-t--global--font--weight--body--default); } From 45476d82988500e68812b9dc61393dbc03b34ddb Mon Sep 17 00:00:00 2001 From: rawagner Date: Fri, 14 Feb 2025 12:04:43 +0100 Subject: [PATCH 039/618] OCPBUGS-50546: Do not load CSRs if user does not have permissions --- .../src/components/nodes/NodesPage.tsx | 37 ++++++++++++++----- .../console-app/src/components/nodes/csr.ts | 2 +- 2 files changed, 29 insertions(+), 10 deletions(-) diff --git a/frontend/packages/console-app/src/components/nodes/NodesPage.tsx b/frontend/packages/console-app/src/components/nodes/NodesPage.tsx index bcee209f966..d66d7868c03 100644 --- a/frontend/packages/console-app/src/components/nodes/NodesPage.tsx +++ b/frontend/packages/console-app/src/components/nodes/NodesPage.tsx @@ -6,7 +6,10 @@ import { useTranslation } from 'react-i18next'; // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore: FIXME out-of-sync @types/react-redux version as new types cause many build errors import { useSelector, useDispatch } from 'react-redux'; -import { ListPageBody } from '@console/dynamic-plugin-sdk/src/api/dynamic-core-api'; +import { + ListPageBody, + useAccessReview, +} from '@console/dynamic-plugin-sdk/src/api/dynamic-core-api'; import { NodeCertificateSigningRequestKind, RowFilter, @@ -678,6 +681,29 @@ const getFilters = (t: TFunction): RowFilter[] => [ }, ]; +const useWatchCSRs = (): [CertificateSigningRequestKind[], boolean, unknown] => { + const [isAllowed, checkIsLoading] = useAccessReview({ + group: 'certificates.k8s.io', + resource: 'CertificateSigningRequest', + verb: 'list', + }); + + const [csrs, loaded, error] = useK8sWatchResource( + isAllowed + ? { + groupVersionKind: { + group: 'certificates.k8s.io', + kind: 'CertificateSigningRequest', + version: 'v1', + }, + isList: true, + } + : undefined, + ); + + return [csrs, !checkIsLoading && loaded, error]; +}; + const NodesPage: React.FC = ({ selector }) => { const dispatch = useDispatch(); @@ -697,14 +723,7 @@ const NodesPage: React.FC = ({ selector }) => { selector, }); - const [csrs, csrsLoaded, csrsLoadError] = useK8sWatchResource({ - groupVersionKind: { - group: 'certificates.k8s.io', - kind: 'CertificateSigningRequest', - version: 'v1', - }, - isList: true, - }); + const [csrs, csrsLoaded, csrsLoadError] = useWatchCSRs(); React.useEffect(() => { const updateMetrics = async () => { diff --git a/frontend/packages/console-app/src/components/nodes/csr.ts b/frontend/packages/console-app/src/components/nodes/csr.ts index e724e7d2723..29a9d450e05 100644 --- a/frontend/packages/console-app/src/components/nodes/csr.ts +++ b/frontend/packages/console-app/src/components/nodes/csr.ts @@ -18,7 +18,7 @@ const getNodeCSRs = ( username: string, client: boolean, ): CertificateSigningRequestKind[] => - csrs + (csrs || []) .filter( (csr) => csr.spec.username === username && From c46dbda51d7b7cabcfdc2979b6af91e8b15fbf6e Mon Sep 17 00:00:00 2001 From: Vojtech Szocs Date: Thu, 13 Feb 2025 18:18:35 +0000 Subject: [PATCH 040/618] Update Console dynamic plugin SDK doc and changelogs --- .../CHANGELOG-core.md | 26 ++++++- .../CHANGELOG-webpack.md | 20 +++++- .../console-dynamic-plugin-sdk/README.md | 72 ++++++++++++++----- 3 files changed, 96 insertions(+), 22 deletions(-) diff --git a/frontend/packages/console-dynamic-plugin-sdk/CHANGELOG-core.md b/frontend/packages/console-dynamic-plugin-sdk/CHANGELOG-core.md index e1e1024318f..100757e6d60 100644 --- a/frontend/packages/console-dynamic-plugin-sdk/CHANGELOG-core.md +++ b/frontend/packages/console-dynamic-plugin-sdk/CHANGELOG-core.md @@ -1,7 +1,21 @@ # Changelog for `@openshift-console/dynamic-plugin-sdk` -Refer to [Console dynamic plugins README](./README.md) for OpenShift Console version vs SDK package -version and PatternFly version compatibility. +Console plugin SDK packages follow a semver scheme where the major and minor version number indicates +the earliest supported OCP Console version, and the patch version number indicates the release of that +particular package. + +For released (GA) versions of Console, use `4.x.z` packages. +For current development version of Console, use `4.x.0-prerelease.n` packages. + +For 1.x plugin SDK packages, refer to "OpenShift Console Versions vs SDK Versions" compatibility table +in [Console dynamic plugins README](./README.md). + +## 4.19.0-prerelease.1 - 2025-02-14 + +- Remove Console provided PatternFly 4 shared modules ([CONSOLE-4379], [#14615]) +- Add `customData` prop to `HorizontalNav` component ([OCPBUGS-45319], [#14575]) +- Allow custom popover description in extension type `console.resource/details-item` ([CONSOLE-4269], [#14487]) +- Change generated JS build target from `es2016` to `es2021` ([CONSOLE-4400], [#14620]) ## 1.8.0 - 2024-11-04 @@ -63,6 +77,9 @@ version and PatternFly version compatibility. [CONSOLE-4097]: https://issues.redhat.com/browse/CONSOLE-4097 [CONSOLE-4185]: https://issues.redhat.com/browse/CONSOLE-4185 [CONSOLE-4263]: https://issues.redhat.com/browse/CONSOLE-4263 +[CONSOLE-4269]: https://issues.redhat.com/browse/CONSOLE-4269 +[CONSOLE-4379]: https://issues.redhat.com/browse/CONSOLE-4379 +[CONSOLE-4400]: https://issues.redhat.com/browse/CONSOLE-4400 [OCPBUGS-19048]: https://issues.redhat.com/browse/OCPBUGS-19048 [OCPBUGS-30077]: https://issues.redhat.com/browse/OCPBUGS-30077 [OCPBUGS-31355]: https://issues.redhat.com/browse/OCPBUGS-31355 @@ -74,6 +91,7 @@ version and PatternFly version compatibility. [OCPBUGS-37426]: https://issues.redhat.com/browse/OCPBUGS-37426 [OCPBUGS-43538]: https://issues.redhat.com/browse/OCPBUGS-43538 [OCPBUGS-43998]: https://issues.redhat.com/browse/OCPBUGS-43998 +[OCPBUGS-45319]: https://issues.redhat.com/browse/OCPBUGS-45319 [ODC-7425]: https://issues.redhat.com/browse/ODC-7425 [#12983]: https://github.com/openshift/console/pull/12983 [#13233]: https://github.com/openshift/console/pull/13233 @@ -96,3 +114,7 @@ version and PatternFly version compatibility. [#14156]: https://github.com/openshift/console/pull/14156 [#14421]: https://github.com/openshift/console/pull/14421 [#14447]: https://github.com/openshift/console/pull/14447 +[#14487]: https://github.com/openshift/console/pull/14487 +[#14575]: https://github.com/openshift/console/pull/14575 +[#14615]: https://github.com/openshift/console/pull/14615 +[#14620]: https://github.com/openshift/console/pull/14620 diff --git a/frontend/packages/console-dynamic-plugin-sdk/CHANGELOG-webpack.md b/frontend/packages/console-dynamic-plugin-sdk/CHANGELOG-webpack.md index 660977cb3b7..a755601bf61 100644 --- a/frontend/packages/console-dynamic-plugin-sdk/CHANGELOG-webpack.md +++ b/frontend/packages/console-dynamic-plugin-sdk/CHANGELOG-webpack.md @@ -1,7 +1,19 @@ # Changelog for `@openshift-console/dynamic-plugin-sdk-webpack` -Refer to [Console dynamic plugins README](./README.md) for OpenShift Console version vs SDK package -version and PatternFly version compatibility. +Console plugin SDK packages follow a semver scheme where the major and minor version number indicates +the earliest supported OCP Console version, and the patch version number indicates the release of that +particular package. + +For released (GA) versions of Console, use `4.x.z` packages. +For current development version of Console, use `4.x.0-prerelease.n` packages. + +For 1.x plugin SDK packages, refer to "OpenShift Console Versions vs SDK Versions" compatibility table +in [Console dynamic plugins README](./README.md). + +## 4.19.0-prerelease.1 - 2025-02-14 + +- Remove Console provided PatternFly 4 shared modules ([CONSOLE-4379], [#14615]) +- Change generated JS build target from `es2016` to `es2021` ([CONSOLE-4400], [#14620]) ## 1.3.0 - 2024-10-31 @@ -39,6 +51,8 @@ version and PatternFly version compatibility. [CONSOLE-3705]: https://issues.redhat.com/browse/CONSOLE-3705 [CONSOLE-3853]: https://issues.redhat.com/browse/CONSOLE-3853 +[CONSOLE-4379]: https://issues.redhat.com/browse/CONSOLE-4379 +[CONSOLE-4400]: https://issues.redhat.com/browse/CONSOLE-4400 [OCPBUGS-30762]: https://issues.redhat.com/browse/OCPBUGS-30762 [OCPBUGS-30824]: https://issues.redhat.com/browse/OCPBUGS-30824 [OCPBUGS-31901]: https://issues.redhat.com/browse/OCPBUGS-31901 @@ -58,3 +72,5 @@ version and PatternFly version compatibility. [#13992]: https://github.com/openshift/console/pull/13992 [#14167]: https://github.com/openshift/console/pull/14167 [#14300]: https://github.com/openshift/console/pull/14300 +[#14615]: https://github.com/openshift/console/pull/14615 +[#14620]: https://github.com/openshift/console/pull/14620 diff --git a/frontend/packages/console-dynamic-plugin-sdk/README.md b/frontend/packages/console-dynamic-plugin-sdk/README.md index a4f8c090fd4..72ffd157b8e 100644 --- a/frontend/packages/console-dynamic-plugin-sdk/README.md +++ b/frontend/packages/console-dynamic-plugin-sdk/README.md @@ -76,13 +76,22 @@ used with multiple versions of OpenShift Console but don't provide any backwards ## OpenShift Console Versions vs SDK Versions -Not all NPM packages are fully compatible with all versions of the Console. This table will help align -compatible versions of distributable SDK packages to versions of the OpenShift Console. +Console plugin SDK packages follow a semver scheme where the major and minor version number indicates +the earliest supported OCP Console version, and the patch version number indicates the release of that +particular package. + +During development, we will publish prerelease versions of plugin SDK packages, e.g. `4.19.0-prerelease.1`. +Once the given Console version is released (GA), we will publish corresponding plugin SDK packages without +the prerelease tag, e.g. `4.19.0`. + +For older 1.x plugin SDK packages, refer to the following version compatibility table: | Console Version | SDK Package | Last Package Version | | --------------- | ----------------------------------------------- | -------------------- | -| 4.17.x | `@openshift-console/dynamic-plugin-sdk` | Latest | -| | `@openshift-console/dynamic-plugin-sdk-webpack` | Latest | +| 4.18.x | `@openshift-console/dynamic-plugin-sdk` | 1.8.0 | +| | `@openshift-console/dynamic-plugin-sdk-webpack` | 1.3.0 | +| 4.17.x | `@openshift-console/dynamic-plugin-sdk` | 1.6.0 | +| | `@openshift-console/dynamic-plugin-sdk-webpack` | 1.2.0 | | 4.16.x | `@openshift-console/dynamic-plugin-sdk` | 1.4.0 | | | `@openshift-console/dynamic-plugin-sdk-webpack` | 1.1.1 | | 4.15.x | `@openshift-console/dynamic-plugin-sdk` | 1.0.0 | @@ -156,20 +165,20 @@ This section documents notable changes in the Console provided shared modules ac #### Console 4.19.x +- Removed PatternFly 4.x shared modules. Console now uses PatternFly 6.x and provides PatternFly 5.x + styles for compatibility with existing plugins. - Removed `@fortawesome/font-awesome` and `openshift-logos-icon`. Plugins should use PatternFly icons from `@patternfly/react-icons` instead. The `fa-spin` class remains but is deprecated and will be removed in the future. Plugins should provide their own CSS to spin icons if needed. -- Removed PatternFly 4.x shared modules. -- Upgraded PatternFly to v6. -- Removed styling for generic HTML heading elements (e.g., `

`). Use PatternFly components to achieve correct styling. +- Removed styling for generic HTML heading elements (e.g., `

`). Use PatternFly components to achieve + correct styling. -### PatternFly dynamic modules +### PatternFly 5+ dynamic modules -Newer versions of `@openshift-console/dynamic-plugin-sdk-webpack` package (1.0.0 and higher) include -support for automatic detection and sharing of individual PatternFly 5.x dynamic modules. +Newer versions of `@openshift-console/dynamic-plugin-sdk-webpack` package include support for automatic +detection and sharing of individual PatternFly 5+ dynamic modules. -Plugins using PatternFly 5.x dependencies should generally avoid non-index imports for any PatternFly -packages, for example: +Plugins using PatternFly 5.x and newer should avoid non-index imports, for example: ```ts // Do _not_ do this: @@ -186,20 +195,47 @@ Console application uses [Content Security Policy](https://developer.mozilla.org includes the document origin `'self'` and Console webpack dev server when running off-cluster. All dynamic plugin assets _should_ be loaded using `/api/plugins/` Bridge endpoint which -matches the `'self'` CSP source of Console application. +matches the `'self'` CSP source for all Console assets served via Bridge. -See `cspSources` and `cspDirectives` in -[`pkg/server/server.go`](https://github.com/openshift/console/blob/master/pkg/server/server.go) +Refer to `BuildCSPDirectives` function in +[`pkg/utils/utils.go`](https://github.com/openshift/console/blob/master/pkg/utils/utils.go) for details on the current Console CSP implementation. +Refer to [Dynamic Plugins feature page][console-doc-feature-page] section on Content Security Policy +for more details. + ### Changes in Console CSP -This section documents notable changes in the Console Content Security Policy. +This section documents notable changes in the Console Content Security Policy implementation. #### Console 4.18.x -Console CSP is deployed in report-only mode. CSP violations will be logged in the browser console -but the associated CSP directives will not be enforced. +Console deploys CSP in report-only mode; CSP violations will be logged in the browser console +and CSP violation data may be reported through telemetry service in production deployments. + +In a future release, Console will begin enforcing CSP. To test your plugin with CSP, enable +the `ConsolePluginContentSecurityPolicy` feature gate on a test cluster. This feature gate +should **not** be enabled on production clusters. Enabling this feature gate also allows you +to set `spec.contentSecurityPolicy` in your `ConsolePlugin` resource to extend existing Console +CSP directives, for example: + +```yaml +apiVersion: console.openshift.io/v1 +kind: ConsolePlugin +metadata: + name: cron-tab +spec: + displayName: 'Cron Tab' + contentSecurityPolicy: + - directive: 'ScriptSrc' + values: + - 'https://example1.com/' + - 'https://example2.com/' +``` + +#### Console 4.19.x + +The CSP feature is enabled by default. CSP implementation remains in report-only mode. ## Plugin metadata From f230f2a37e0a51aae663d3037fd910e9da7c6af9 Mon Sep 17 00:00:00 2001 From: Vojtech Szocs Date: Fri, 14 Feb 2025 16:01:32 +0000 Subject: [PATCH 041/618] Update Console 4.18 section on CSP enablement --- .../console-dynamic-plugin-sdk/README.md | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/frontend/packages/console-dynamic-plugin-sdk/README.md b/frontend/packages/console-dynamic-plugin-sdk/README.md index 72ffd157b8e..d1a68476000 100644 --- a/frontend/packages/console-dynamic-plugin-sdk/README.md +++ b/frontend/packages/console-dynamic-plugin-sdk/README.md @@ -210,13 +210,10 @@ This section documents notable changes in the Console Content Security Policy im #### Console 4.18.x -Console deploys CSP in report-only mode; CSP violations will be logged in the browser console -and CSP violation data may be reported through telemetry service in production deployments. - -In a future release, Console will begin enforcing CSP. To test your plugin with CSP, enable -the `ConsolePluginContentSecurityPolicy` feature gate on a test cluster. This feature gate -should **not** be enabled on production clusters. Enabling this feature gate also allows you -to set `spec.contentSecurityPolicy` in your `ConsolePlugin` resource to extend existing Console +Console CSP feature is disabled by default. To test your plugins with CSP, enable the +`ConsolePluginContentSecurityPolicy` feature gate on a test cluster. This feature gate +should **not** be enabled on production clusters. Enabling this feature gate allows you +to set `spec.contentSecurityPolicy` in your `ConsolePlugin` resource to extend existing CSP directives, for example: ```yaml @@ -233,6 +230,13 @@ spec: - 'https://example2.com/' ``` +When enabled, Console CSP operates in report-only mode; CSP violations will be logged in +the browser and CSP violation data will be reported through telemetry service in production +deployments. + +In a future release, Console will begin enforcing CSP. Consider testing and preparing your +plugins now to avoid CSP related issues in future. + #### Console 4.19.x The CSP feature is enabled by default. CSP implementation remains in report-only mode. From d7fb0e86192353acb973d0d57c88b619e5059ccb Mon Sep 17 00:00:00 2001 From: kmcfaul <45077788+kmcfaul@users.noreply.github.com> Date: Fri, 14 Feb 2025 14:29:42 -0500 Subject: [PATCH 042/618] CONSOLE-4462: update dark theme specific styles (#14728) * CONSOLE-4462: update dark theme specific styles * add lightspeed background back in * move to right line --- .../QuickStartGettingStartedCard.tsx | 2 +- .../src/components/add/AddCardItem.scss | 6 ------ .../QuickStartGettingStartedCard.tsx | 2 +- .../create-local-volume-set.scss | 4 ---- .../ServiceBindingAlerts.scss | 5 ----- .../ServiceBindingAlerts.tsx | 1 - frontend/public/style/_overrides.scss | 12 ++--------- frontend/public/style/_theme-dark.scss | 21 ++++--------------- 8 files changed, 8 insertions(+), 45 deletions(-) delete mode 100644 frontend/packages/service-binding-plugin/src/components/service-binding-utils/ServiceBindingAlerts.scss diff --git a/frontend/packages/console-shared/src/components/getting-started/QuickStartGettingStartedCard.tsx b/frontend/packages/console-shared/src/components/getting-started/QuickStartGettingStartedCard.tsx index c3f76ac13a2..01be03bf063 100644 --- a/frontend/packages/console-shared/src/components/getting-started/QuickStartGettingStartedCard.tsx +++ b/frontend/packages/console-shared/src/components/getting-started/QuickStartGettingStartedCard.tsx @@ -116,7 +116,7 @@ export const QuickStartGettingStartedCard: React.FC

} bodyContent={ From 14cb836f17fdd87b7765bac3a5763097b1f7a65e Mon Sep 17 00:00:00 2001 From: Robb Hamilton Date: Mon, 17 Feb 2025 16:57:59 -0500 Subject: [PATCH 044/618] OCPBUGS-50847: fix but where textarea can be expanded horizontally --- .../console-shared/src/components/modals/CreateProjectModal.tsx | 2 +- frontend/public/components/utils/file-input.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/packages/console-shared/src/components/modals/CreateProjectModal.tsx b/frontend/packages/console-shared/src/components/modals/CreateProjectModal.tsx index ef5b30a02e6..b944269459d 100644 --- a/frontend/packages/console-shared/src/components/modals/CreateProjectModal.tsx +++ b/frontend/packages/console-shared/src/components/modals/CreateProjectModal.tsx @@ -206,7 +206,7 @@ const DefaultCreateProjectModal: ModalComponent = ({ {t('console-shared~Description')}
- +