= ({ conditions }) => {
+ const successCount = getConditionOKCount(conditions);
+ const failureCount = _.size(conditions) - successCount;
+ return (
+
+
+ {' '}
+ {successCount} | {' '}
+ {failureCount}
+
+
+ );
+};
+
+export default GetConditionsForStatus;
diff --git a/frontend/packages/knative-plugin/src/components/functions/GettingStartedSection.scss b/frontend/packages/knative-plugin/src/components/functions/GettingStartedSection.scss
new file mode 100644
index 00000000000..ea970cd26a4
--- /dev/null
+++ b/frontend/packages/knative-plugin/src/components/functions/GettingStartedSection.scss
@@ -0,0 +1,61 @@
+.odc-functions-getting-started-section {
+ padding-right: var(--pf-global--spacer--lg);
+ padding-left: var(--pf-global--spacer--lg);
+}
+
+.pf-c-expandable-section__toggle-text {
+ color: var(--pf-global--Color--dark-100) !important;
+ &.is-dark {
+ color: var(--pf-global--Color--light-100) !important;
+ }
+ font-size: var(--pf-global--icon--FontSize--md) !important;
+ font-weight: var(--pf-global--FontWeight--bold) !important;
+}
+
+.pf-c-expandable-section__toggle {
+ align-items: center;
+}
+
+.pf-c-expandable-section__content {
+ padding-bottom: 0;
+}
+
+.odc-functions-getting-started-grid {
+ --min-column-width: 220px;
+ box-shadow: none;
+ // Increase css specificity to override a generic [class*="pf-c-"] rule.
+ &__header.pf-c-card__header {
+ // Use padding sm instead of lg to fix alignment of the KebabToggle action button.
+ padding-right: var(--pf-global--spacer--sm);
+ }
+ &__tooltip {
+ white-space: pre-line;
+ }
+ &__tooltip-icon {
+ padding-left: var(--pf-global--spacer--sm);
+ }
+
+ // Increase css specificity to override a generic [class*="pf-c-"] rule.
+ &__content.pf-c-card__body {
+ display: grid;
+ grid-template-columns: repeat(auto-fit, minmax(var(--min-column-width), 1fr));
+
+ // Keep only additional spacing at the bottom. Horizontal spacing is added to the child elements.
+ padding: 0 0 calc(var(--pf-c-card--child--PaddingBottom) / 2) 0;
+ // Hide the border on the right side of the content. Works together wie negative margin below.
+ overflow: hidden;
+
+ // Increase css specificity to override a generic [class*="pf-c-"] rule.
+ > .pf-l-flex.pf-m-column {
+ // Show a divider on the right side and hide them in the latest column.
+ border-right: var(--pf-global--BorderWidth--sm) solid var(--pf-global--BorderColor--100);
+ margin-right: calc(-1 * var(--pf-global--BorderWidth--sm));
+ // Padding around the card. Vertical spacing is splitted on the card and the grid.
+ padding-top: calc(var(--pf-c-card--first-child--PaddingTop) / 2);
+ padding-bottom: calc(var(--pf-c-card--child--PaddingBottom) / 2);
+ padding-left: var(--pf-global--spacer--md);
+ padding-right: var(--pf-c-card--child--PaddingRight);
+ }
+ }
+}
+
diff --git a/frontend/packages/knative-plugin/src/components/functions/GettingStartedSection.tsx b/frontend/packages/knative-plugin/src/components/functions/GettingStartedSection.tsx
new file mode 100644
index 00000000000..168b21a106d
--- /dev/null
+++ b/frontend/packages/knative-plugin/src/components/functions/GettingStartedSection.tsx
@@ -0,0 +1,47 @@
+import * as React from 'react';
+import { Card, CardBody, ExpandableSection } from '@patternfly/react-core';
+import { useTranslation } from 'react-i18next';
+import { useUserSettings } from '@console/shared';
+import { FUNCTIONS_GETTING_STARTED_SECTION_USER_SETTING_KEY } from '../../const';
+import { FunctionsDocsGettingStartedCard } from './FunctionsDocsGettingStartedCard';
+import { QuickStartGettingStartedCard } from './QuickStartGettingStartedCard';
+import { SampleGettingStartedCard } from './SamplesGettingStartedCard';
+
+import './GettingStartedSection.scss';
+
+export const GettingStartedSection: React.FC = () => {
+ const { t } = useTranslation();
+
+ const [isGettingStartedSectionOpen, setIsGettingStartedSectionOpen] = useUserSettings(
+ FUNCTIONS_GETTING_STARTED_SECTION_USER_SETTING_KEY,
+ true,
+ );
+
+ return (
+
+ setIsGettingStartedSectionOpen(!isGettingStartedSectionOpen)}
+ isExpanded={isGettingStartedSectionOpen}
+ displaySize="large"
+ >
+
+
+ {t('knative-plugin~Choose how to create a function from 3 methods')}
+
+
+
+
+
+
+
+
+
+ );
+};
diff --git a/frontend/packages/knative-plugin/src/components/functions/QuickStartGettingStartedCard.tsx b/frontend/packages/knative-plugin/src/components/functions/QuickStartGettingStartedCard.tsx
new file mode 100644
index 00000000000..caea75b24b9
--- /dev/null
+++ b/frontend/packages/knative-plugin/src/components/functions/QuickStartGettingStartedCard.tsx
@@ -0,0 +1,129 @@
+import * as React from 'react';
+import {
+ AllQuickStartStates,
+ QuickStart,
+ QuickStartStatus,
+ QuickStartContext,
+ QuickStartContextValues,
+ getQuickStartStatus,
+} from '@patternfly/quickstarts';
+import { RouteIcon } from '@patternfly/react-icons';
+import { useTranslation } from 'react-i18next';
+import QuickStartsLoader from '@console/app/src/components/quick-starts/loader/QuickStartsLoader';
+import {
+ GettingStartedCard,
+ GettingStartedLink,
+} from '@console/shared/src/components/getting-started';
+
+interface QuickStartGettingStartedCardProps {
+ featured?: string[];
+ title?: string;
+ description?: string;
+ filter?: (QuickStart) => boolean;
+}
+
+const orderQuickStarts = (
+ allQuickStarts: QuickStart[],
+ allQuickStartStates: AllQuickStartStates,
+ featured: string[],
+ filter?: (QuickStart) => boolean,
+): QuickStart[] => {
+ const orderedQuickStarts: QuickStart[] = [];
+
+ const filteredQuickStarts = filter ? allQuickStarts.filter(filter) : allQuickStarts;
+
+ const getStatus = (quickStart: QuickStart) =>
+ getQuickStartStatus(allQuickStartStates, quickStart.metadata.name);
+
+ // Prioritize featured quick starts and keep specified order
+ if (featured) {
+ const featuredQuickStartsByName = filteredQuickStarts.reduce((acc, q) => {
+ acc[q.metadata.name] = q;
+ return acc;
+ }, {} as Record);
+ featured.forEach((quickStartName) => {
+ if (
+ featuredQuickStartsByName[quickStartName] &&
+ getStatus(featuredQuickStartsByName[quickStartName]) !== QuickStartStatus.COMPLETE
+ ) {
+ orderedQuickStarts.push(featuredQuickStartsByName[quickStartName]);
+ }
+ });
+ }
+
+ return orderedQuickStarts;
+};
+
+export const QuickStartGettingStartedCard: React.FC = ({
+ featured,
+ title,
+ description,
+ filter,
+}) => {
+ const { t } = useTranslation();
+ const { allQuickStartStates, setActiveQuickStart } = React.useContext(
+ QuickStartContext,
+ );
+
+ return (
+
+ {(quickStarts, loaded) => {
+ const orderedQuickStarts = orderQuickStarts(
+ quickStarts,
+ allQuickStartStates,
+ featured,
+ filter,
+ );
+ const slicedQuickStarts = orderedQuickStarts.slice(0, 2);
+
+ let links: GettingStartedLink[] = [];
+ if (loaded && slicedQuickStarts.length === 0) {
+ links.push(
+ {
+ id: 'ide-extensions',
+ href:
+ 'https://marketplace.visualstudio.com/items?itemName=redhat.vscode-openshift-connector',
+ title: t('knative-plugin~Create using IDE extension'),
+ external: true,
+ },
+ {
+ id: 'functions-tekton-pipelines',
+ title: t('knative-plugin~Building Functions on Cluster with Tekton Pipelines'),
+ href:
+ 'https://github.com/knative/func/blob/main/docs/building-functions/on_cluster_build.md',
+ external: true,
+ },
+ );
+ } else {
+ links = loaded
+ ? slicedQuickStarts.map((quickStart: QuickStart) => ({
+ id: quickStart.metadata.name,
+ title: quickStart.spec.displayName,
+ onClick: () => {
+ setActiveQuickStart(quickStart.metadata.name, quickStart.spec.tasks.length);
+ },
+ }))
+ : featured?.map((name) => ({
+ id: name,
+ loading: true,
+ }));
+ }
+ return (
+ }
+ title={title || t('knative-plugin~Build with guided documentation')}
+ titleColor={'var(--co-global--palette--purple-700)'}
+ description={
+ description ||
+ t(
+ 'knative-plugin~Follow guided documentation to build applications and familiarize yourself with key features.',
+ )
+ }
+ links={links}
+ />
+ );
+ }}
+
+ );
+};
diff --git a/frontend/packages/knative-plugin/src/components/functions/SamplesGettingStartedCard.tsx b/frontend/packages/knative-plugin/src/components/functions/SamplesGettingStartedCard.tsx
new file mode 100644
index 00000000000..04a756a35d5
--- /dev/null
+++ b/frontend/packages/knative-plugin/src/components/functions/SamplesGettingStartedCard.tsx
@@ -0,0 +1,115 @@
+import * as React from 'react';
+import { CatalogIcon } from '@patternfly/react-icons';
+import { useTranslation } from 'react-i18next';
+import { SAMPLE_CATALOG_TYPE_ID } from '@console/dev-console/src/const';
+import { getDisabledAddActions } from '@console/dev-console/src/utils/useAddActionExtensions';
+import { CatalogItem } from '@console/dynamic-plugin-sdk';
+import {
+ CatalogServiceProvider,
+ isCatalogTypeEnabled,
+ ALL_NAMESPACES_KEY,
+ useActiveNamespace,
+} from '@console/shared';
+import {
+ GettingStartedLink,
+ GettingStartedCard,
+} from '@console/shared/src/components/getting-started';
+
+interface SampleGettingStartedCardProps {
+ featured?: string[];
+}
+
+const orderCatalogItems = (allCatalogItems: CatalogItem[], featured: string[]): CatalogItem[] => {
+ const orderedCatalogItems: CatalogItem[] = [];
+
+ const isNotFeatured = (catalogItem: CatalogItem) => !featured?.includes(catalogItem.uid);
+
+ // Prioritze featured catalog items
+ if (featured) {
+ const featuredQuickStartsByName = allCatalogItems.reduce((acc, ci) => {
+ acc[ci.uid] = ci;
+ return acc;
+ }, {} as Record);
+ featured.forEach((uid) => {
+ if (featuredQuickStartsByName[uid]) {
+ orderedCatalogItems.push(featuredQuickStartsByName[uid]);
+ }
+ });
+ }
+
+ // All all other catalog items
+ orderedCatalogItems.push(...allCatalogItems.filter(isNotFeatured));
+
+ return orderedCatalogItems;
+};
+
+export const SampleGettingStartedCard: React.FC = ({
+ featured = [],
+}) => {
+ const { t } = useTranslation();
+ const [activeNamespace] = useActiveNamespace();
+ const isSampleTypeEnabled = isCatalogTypeEnabled(SAMPLE_CATALOG_TYPE_ID);
+
+ const disabledAddActions = getDisabledAddActions();
+ if (disabledAddActions?.includes('import-from-samples') || !isSampleTypeEnabled) {
+ return null;
+ }
+
+ const moreLink: GettingStartedLink = {
+ id: 'all-samples',
+ title: t('knative-plugin~View all samples'),
+ href:
+ activeNamespace && activeNamespace !== ALL_NAMESPACES_KEY
+ ? `/samples/ns/${activeNamespace}?sampleType=Serverless function`
+ : '/samples/all-namespaces?sampleType=Serverless function',
+ };
+
+ return (
+
+ {(service) => {
+ const orderedCatalogItems = orderCatalogItems(service.items || [], featured);
+
+ const orderedCatalogItemsTemp = orderedCatalogItems.filter((item) => {
+ return (
+ item?.typeLabel === 'Serverless function' ||
+ item?.data?.metadata?.labels['sample-type'] === 'Serverless function'
+ );
+ });
+
+ const slicedCatalogItems = orderedCatalogItemsTemp.slice(0, 2);
+
+ if (slicedCatalogItems.length === 0) {
+ return null;
+ }
+
+ const links: GettingStartedLink[] = service.loaded
+ ? slicedCatalogItems.map((item) => {
+ return {
+ id: item.uid,
+ title: item.name,
+ href: item.cta?.href,
+ onClick: item.cta?.callback,
+ };
+ })
+ : featured.map((uid) => {
+ return {
+ id: uid,
+ loading: true,
+ };
+ });
+
+ return (
+ }
+ title={t('knative-plugin~Create functions using samples')}
+ titleColor={'var(--co-global--palette--blue-400)'}
+ description={t('knative-plugin~Choose a code sample to create a function.')}
+ links={links}
+ moreLink={moreLink}
+ />
+ );
+ }}
+
+ );
+};
diff --git a/frontend/packages/knative-plugin/src/components/services/ServiceDetailsPage.tsx b/frontend/packages/knative-plugin/src/components/services/ServiceDetailsPage.tsx
index b2b35f48181..eea36dcb99a 100644
--- a/frontend/packages/knative-plugin/src/components/services/ServiceDetailsPage.tsx
+++ b/frontend/packages/knative-plugin/src/components/services/ServiceDetailsPage.tsx
@@ -46,7 +46,7 @@ const ServiceDetails: React.FC<{ obj: ServiceKind }> = ({ obj }) => {
return (
<>
-
+
() => {
props: { className: tableColumnClasses[2] },
},
{
- title: t('knative-plugin~Generation'),
- sortField: 'metadata.generation',
- transforms: [sortable],
+ title: t('knative-plugin~Conditions'),
props: { className: tableColumnClasses[3] },
},
{
- title: t('knative-plugin~Created'),
- sortField: 'metadata.creationTimestamp',
- transforms: [sortable],
+ title: t('knative-plugin~Ready'),
props: { className: tableColumnClasses[4] },
},
{
- title: t('knative-plugin~Conditions'),
+ title: t('knative-plugin~Reason'),
props: { className: tableColumnClasses[5] },
},
{
- title: t('knative-plugin~Ready'),
+ title: t('knative-plugin~Revision'),
+ sortField: 'metadata.generation',
+ transforms: [sortable],
props: { className: tableColumnClasses[6] },
},
{
- title: t('knative-plugin~Reason'),
+ title: t('knative-plugin~Created'),
+ sortField: 'metadata.creationTimestamp',
+ transforms: [sortable],
props: { className: tableColumnClasses[7] },
},
{
diff --git a/frontend/packages/knative-plugin/src/components/services/ServiceRow.tsx b/frontend/packages/knative-plugin/src/components/services/ServiceRow.tsx
index 778dd6924df..83a9aa8688e 100644
--- a/frontend/packages/knative-plugin/src/components/services/ServiceRow.tsx
+++ b/frontend/packages/knative-plugin/src/components/services/ServiceRow.tsx
@@ -6,7 +6,8 @@ import { referenceForModel, referenceFor } from '@console/internal/module/k8s';
import { LazyActionMenu, ClampedText } from '@console/shared';
import { ServiceModel } from '../../models';
import { ServiceKind, ConditionTypes } from '../../types';
-import { getConditionString, getCondition } from '../../utils/condition-utils';
+import { getCondition } from '../../utils/condition-utils';
+import GetConditionsForStatus from '../functions/GetConditionsForStatus';
import { tableColumnClasses } from './service-table';
const serviceReference = referenceForModel(ServiceModel);
@@ -40,22 +41,22 @@ const ServiceRow: React.FC> = ({ obj }) => {
)) ||
'-'}
- {obj.metadata.generation || '-'}
-
-
+
+ {obj.status ? : '-'}
-
- {obj.status ? getConditionString(obj.status.conditions) : '-'}
-
-
+
{(readyCondition && readyCondition.status) || '-'}
-
+
{(readyCondition?.message && (
{readyCondition?.message}
)) ||
'-'}
+ {obj.metadata.generation || '-'}
+
+
+
diff --git a/frontend/packages/knative-plugin/src/components/services/__tests__/ServiceRow.spec.tsx b/frontend/packages/knative-plugin/src/components/services/__tests__/ServiceRow.spec.tsx
index ae7ebdf15f3..ae5cfbdce93 100644
--- a/frontend/packages/knative-plugin/src/components/services/__tests__/ServiceRow.spec.tsx
+++ b/frontend/packages/knative-plugin/src/components/services/__tests__/ServiceRow.spec.tsx
@@ -36,22 +36,17 @@ describe('ServiceRow', () => {
});
it('should show generations for associated service', () => {
- const generationColData = wrapper.find(TableData).at(3);
+ const generationColData = wrapper.find(TableData).at(6);
expect(generationColData.props().children).toEqual(1);
});
it('should show "-" in generations for no associated generation', () => {
svcData = _.omit(svcData, 'obj.metadata.generation');
wrapper = shallow();
- const generationColData = wrapper.find(TableData).at(3);
+ const generationColData = wrapper.find(TableData).at(6);
expect(generationColData.props().children).toEqual('-');
});
- it('should show appropriate conditions', () => {
- const conditionColData = wrapper.find(TableData).at(5);
- expect(conditionColData.props().children).toEqual('3 OK / 3');
- });
-
it('should show "-" in conditions for no associated generation', () => {
svcData = _.omit(svcData, 'obj.status');
wrapper = shallow();
@@ -60,8 +55,8 @@ describe('ServiceRow', () => {
});
it('should show appropriate ready status and reason for ready state', () => {
- const readyColData = wrapper.find(TableData).at(6);
- const reasonColData = wrapper.find(TableData).at(7);
+ const readyColData = wrapper.find(TableData).at(4);
+ const reasonColData = wrapper.find(TableData).at(5);
expect(readyColData.props().children).toEqual('True');
expect(reasonColData.props().children).toEqual('-');
});
@@ -82,8 +77,8 @@ describe('ServiceRow', () => {
},
};
wrapper = shallow();
- const readyColData = wrapper.find(TableData).at(6);
- const reasonColData = wrapper.find(TableData).at(7);
+ const readyColData = wrapper.find(TableData).at(4);
+ const reasonColData = wrapper.find(TableData).at(5);
expect(readyColData.props().children).toEqual('False');
expect(reasonColData.dive().find(ClampedText).at(0).props().children).toEqual(
'Something went wrong.',
diff --git a/frontend/packages/knative-plugin/src/components/services/service-table.ts b/frontend/packages/knative-plugin/src/components/services/service-table.ts
index c5d585ff8ca..993657602ac 100644
--- a/frontend/packages/knative-plugin/src/components/services/service-table.ts
+++ b/frontend/packages/knative-plugin/src/components/services/service-table.ts
@@ -5,10 +5,10 @@ export const tableColumnClasses = [
'', // name
'', // namespace
classNames('pf-m-hidden', 'pf-m-visible-on-sm'), // url
- classNames('pf-m-hidden', 'pf-m-visible-on-lg'), // generation
- classNames('pf-m-hidden', 'pf-m-visible-on-lg'), // created
classNames('pf-m-hidden', 'pf-m-visible-on-xl'), // conditions
classNames('pf-m-hidden', 'pf-m-visible-on-xl'), // ready
- classNames('pf-m-hidden', 'pf-m-visible-on-2xl'), // created
+ classNames('pf-m-hidden', 'pf-m-visible-on-2xl'), // reason
+ classNames('pf-m-hidden', 'pf-m-visible-on-lg'), // revision
+ classNames('pf-m-hidden', 'pf-m-visible-on-lg'), // created
Kebab.columnClass,
];
diff --git a/frontend/packages/knative-plugin/src/const.ts b/frontend/packages/knative-plugin/src/const.ts
index f854940b1fd..cc241f89167 100644
--- a/frontend/packages/knative-plugin/src/const.ts
+++ b/frontend/packages/knative-plugin/src/const.ts
@@ -52,3 +52,4 @@ export const EVENT_SOURCE_ACTION_ID = 'knative-event-source';
export const EVENT_SINK_ACTION_ID = 'knative-event-sink';
export const EVENTING_CHANNEL_ACTION_ID = 'knative-eventing-channel';
export const EVENTING_BROKER_ACTION_ID = 'knative-eventing-broker';
+export const FUNCTIONS_GETTING_STARTED_SECTION_USER_SETTING_KEY = 'functions.gettingStartedSection';
diff --git a/frontend/packages/knative-plugin/src/utils/icons.tsx b/frontend/packages/knative-plugin/src/utils/icons.tsx
index c2f631e895b..11479b7c52d 100644
--- a/frontend/packages/knative-plugin/src/utils/icons.tsx
+++ b/frontend/packages/knative-plugin/src/utils/icons.tsx
@@ -1,4 +1,5 @@
import * as React from 'react';
+import { LaptopCodeIcon, GitAltIcon } from '@patternfly/react-icons';
import * as serverlessFunctionIcon from '@console/internal/imgs/logos/serverlessfx.svg';
import * as channelIcon from '../imgs/channel.svg';
import * as eventSinkIcon from '../imgs/event-sink.svg';
@@ -16,6 +17,10 @@ export const channelIconSVG = channelIcon;
export const serverlessFunctionSVG = serverlessFunctionIcon;
+export const gitIconElement = ;
+
+export const samplesIconElement = ;
+
export const EventSinkIcon: React.FC<{ style?: React.CSSProperties }> = ({ style }) => (
);