Skip to content

Commit

Permalink
avoid pre-fetching tekton hub task versions
Browse files Browse the repository at this point in the history
  • Loading branch information
karthikjeeyar committed Mar 23, 2022
1 parent cadd0da commit a6852ae
Show file tree
Hide file tree
Showing 15 changed files with 360 additions and 143 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
import { setUtilsConfig } from '@console/dynamic-plugin-sdk/src/app/configSetup';
import { appInternalFetch } from '@console/internal/co-fetch';
import { sampleTektonHubCatalogItem } from '../../../../test-data/catalog-item-data';
import {
TektonHubTaskVersion,
getApiResponse,
getHubUIPath,
TEKTON_HUB_ENDPOINT,
} from '../tektonHub';

const emptyHeaders = new Headers();
const apiResults = ({
ok,
status,
versions,
}: {
ok: boolean;
status?: number;
versions?: TektonHubTaskVersion[];
}) =>
Promise.resolve({
headers: emptyHeaders,
ok,
...(status && { status }),
json: () => ({
data: {
versions,
},
}),
});

setUtilsConfig({ appFetch: appInternalFetch });

describe('getApiResponse', () => {
beforeEach(() => {
window.fetch = jest.fn(() =>
apiResults({
ok: true,
status: 200,
versions: sampleTektonHubCatalogItem.attributes.versions,
}),
);
window.console.warn = jest.fn(() => '');
});

afterEach(() => {
(window.fetch as jest.Mock).mockClear();
(window.console.warn as jest.Mock).mockClear();
});

it('should throw error if the response status is not 200', async () => {
window.fetch = jest.fn(() => Promise.resolve({ status: 404, headers: emptyHeaders }));
try {
await getApiResponse('testurl');
fail('Expect an error if the response status is not 200');
} catch (e) {
expect(e?.code).toBe(404);
}
});

it('should throw error if the response ok is false', async () => {
window.fetch = jest.fn(() =>
Promise.resolve({ ok: false, status: 500, headers: emptyHeaders }),
);

try {
await getApiResponse('testurl');
fail('Expect an error if the response ok is false');
} catch (e) {
expect(e?.response?.ok).toBe(false);
}
});

it('should return a valid response', async () => {
const response = await getApiResponse('testurl');
expect(response.data).toBeDefined();
expect(response.data.versions).toBeDefined();
});
});

describe('getHubUIPath', () => {
it('should return null if the path is not set', () => {
expect(getHubUIPath('')).toBeNull();
expect(getHubUIPath(null)).toBeNull();
expect(getHubUIPath(undefined)).toBeNull();
});

it('should return a value if the path is set', () => {
expect(getHubUIPath('test')).not.toBeNull();
expect(getHubUIPath('test-path')).toBe(`${TEKTON_HUB_ENDPOINT}/test-path`);
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import { coFetch } from '@console/internal/co-fetch';
import useApiResponse, { ApiResult } from '../hooks/useApiResponse';

export type TektonHubItem = {
id: number;
name: string;
};
export type TektonHubCategory = TektonHubItem;

export type TektonHubTag = TektonHubItem;

export type TektonHubPlatform = TektonHubItem;

export type TektonHubCatalog = TektonHubItem & {
type: string;
};

export type TektonHubTaskVersion = {
id: number;
version: string;
hubURLPath: string;
rawURL: string;
webURL: string;
platforms: TektonHubPlatform[];
};

export type TektonHubLatestVersion = TektonHubTaskVersion & {
displayName: string;
description: string;
minPipelinesVersion: string;
updatedAt: string;
};

export type TektonHubTask = {
id: number;
name: string;
categories: TektonHubCategory[];
catalog: TektonHubCatalog;
platforms: TektonHubPlatform[];
kind: string;
latestVersion: TektonHubLatestVersion;
tags: TektonHubTag[];
rating: number;
};

export const TEKTON_HUB_API_ENDPOINT = 'https://api.hub.tekton.dev/v1';
export const TEKTON_HUB_ENDPOINT = `https://hub.tekton.dev`;
export const TEKTON_HUB_INTEGRATION_KEY = 'enable-devconsole-integration';

export const getHubUIPath = (path: string = ''): string => {
if (!path) {
return null;
}
return path ? `${TEKTON_HUB_ENDPOINT}/${path}` : TEKTON_HUB_ENDPOINT;
};

export const getApiResponse = async (url: string) => (await coFetch(url)).json();

export const useTektonHubResources = (hasPermission: boolean): ApiResult<TektonHubTask[]> => {
return useApiResponse<TektonHubTask>(`${TEKTON_HUB_API_ENDPOINT}/resources`, hasPermission);
};

export const getTektonHubTaskVersions = async (
resourceId: string,
): Promise<TektonHubTaskVersion[]> => {
const response = await getApiResponse(
`${TEKTON_HUB_API_ENDPOINT}/resource/${resourceId}/versions`,
);
return response?.data?.versions ?? [];
};
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import { useK8sGet } from '@console/internal/components/utils/k8s-get-hook';
import { K8sResourceKind } from '@console/internal/module/k8s';
import { TektonConfigModel } from '../../models';
import { TektonHubTask } from '../../types/tektonHub';
import { TEKTON_HUB_INTEGRATION_KEY } from './const';
import { TektonHubTask, TEKTON_HUB_INTEGRATION_KEY } from './apis/tektonHub';

export const getClusterPlatform = (): string =>
`${window.SERVER_FLAGS.GOOS}/${window.SERVER_FLAGS.GOARCH}`;
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -2,62 +2,49 @@ import * as React from 'react';
import { Label } from '@patternfly/react-core';
import i18next from 'i18next';
import { CatalogItem, ExtensionHook } from '@console/dynamic-plugin-sdk';
import { coFetch } from '@console/internal/co-fetch';
import { ResourceIcon, useAccessReview } from '@console/internal/components/utils';
import { referenceForModel } from '@console/internal/module/k8s';
import { TaskModel } from '../../../models/pipelines';
import { TektonHubTask } from '../../../types/tektonHub';
import { TektonTaskProviders } from '../../pipelines/const';
import { TektonHubTask, useTektonHubResources } from '../apis/tektonHub';
import { filterBySupportedPlatforms, useTektonHubIntegration } from '../catalog-utils';
import { TEKTON_HUB_API_ENDPOINT } from '../const';
import useApiResponse from '../hooks/useApiResponse';

const normalizeTektonHubTasks = async (
tektonHubTasks: TektonHubTask[],
): Promise<CatalogItem<TektonHubTask>[]> => {
const tasks = tektonHubTasks.filter(filterBySupportedPlatforms).reduce((acc, task) => {
if (task.kind !== TaskModel.kind) {
const normalizeTektonHubTasks = (tektonHubTasks: TektonHubTask[]): CatalogItem<TektonHubTask>[] => {
const normalizedTektonHubTasks: CatalogItem<TektonHubTask>[] = tektonHubTasks
.filter(filterBySupportedPlatforms)
.reduce((acc, task) => {
if (task.kind !== TaskModel.kind) {
return acc;
}
const { id, name } = task;
const { description } = task.latestVersion;
const provider = TektonTaskProviders.community;
const tags = task.tags?.map((t) => t.name) ?? [];
const categories = task.categories?.map((ct) => ct.name) ?? [];
const [secondaryLabelName] = categories;
const versions = [];
const normalizedTektonTask: CatalogItem<TektonHubTask> = {
uid: id.toString(),
type: TektonTaskProviders.community,
name,
description,
provider,
tags,
secondaryLabel: secondaryLabelName && <Label color="blue">{secondaryLabelName}</Label>,
icon: {
node: <ResourceIcon kind={referenceForModel(TaskModel)} />,
},
attributes: { installed: '', versions, categories },
cta: {
label: i18next.t('pipelines-plugin~Add'),
},
data: task,
};
acc.push(normalizedTektonTask);

return acc;
}
const { id, name } = task;
const { description } = task.latestVersion;
const provider = TektonTaskProviders.community;
const tags = task.tags?.map((t) => t.name) ?? [];
const categories = task.categories?.map((ct) => ct.name) ?? [];
const [secondaryLabelName] = categories;
acc.push(
coFetch(`${TEKTON_HUB_API_ENDPOINT}/resource/${id}/versions`)
.then(async (res) => {
const json = await res.json();
const versions = json.data?.versions ?? [];
const normalizedTektonTask: CatalogItem<TektonHubTask> = {
uid: id.toString(),
type: TektonTaskProviders.community,
name,
description,
provider,
tags,
secondaryLabel: secondaryLabelName && <Label color="blue">{secondaryLabelName}</Label>,
icon: {
node: <ResourceIcon kind={referenceForModel(TaskModel)} />,
},
attributes: { installed: '', versions, categories },
cta: {
label: i18next.t('pipelines-plugin~Add'),
},
data: task,
};
return normalizedTektonTask;
})
.catch((err) => {
// eslint-disable-next-line no-console
console.log('Failed to fetch tekton hub resource', err);
}),
);
return acc;
}, []);
}, []);

const normalizedTektonHubTasks: CatalogItem<any>[] = await Promise.all(tasks);
return normalizedTektonHubTasks;
};

Expand All @@ -84,15 +71,13 @@ const useTektonHubTasksProvider: ExtensionHook<CatalogItem[]> = ({

const integrationEnabled = useTektonHubIntegration();

const [tektonHubTasks, tasksLoaded, tasksError] = useApiResponse<TektonHubTask>(
`${TEKTON_HUB_API_ENDPOINT}/resources`,
const [tektonHubTasks, tasksLoaded, tasksError] = useTektonHubResources(
canCreateTask && canUpdateTask && integrationEnabled,
);

React.useMemo(
async () => setNormalizedTektonHubTasks(await normalizeTektonHubTasks(tektonHubTasks)),
[tektonHubTasks],
);
React.useMemo(() => setNormalizedTektonHubTasks(normalizeTektonHubTasks(tektonHubTasks)), [
tektonHubTasks,
]);
return [normalizedTektonHubTasks, tasksLoaded, tasksError];
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,12 +59,7 @@ const Contents: React.FC<{
if (item.provider === TektonTaskProviders.community) {
item.attributes.installed = '';
if (installedTask) {
const installedVersion = item.attributes?.versions?.find(
(v) => v.version === installedTask.attributes?.versions[0]?.version,
);
if (installedVersion) {
item.attributes.installed = installedVersion.id.toString();
}
item.attributes.installed = installedTask.attributes?.versions[0]?.version.toString();
}
}

Expand Down
Loading

0 comments on commit a6852ae

Please sign in to comment.