Skip to content

Commit

Permalink
Bug 1866114: Don't store OpenAPI definitions in localStorage
Browse files Browse the repository at this point in the history
The document is too large, so we hit browser limits for localStorage.
This can break Safari. Since the API server supports ETags for the
OpenAPI document, rely on browser caching instead.
  • Loading branch information
spadgett authored and yaacov committed Aug 9, 2020
1 parent d9d48f4 commit 285515f
Show file tree
Hide file tree
Showing 5 changed files with 35 additions and 47 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import {
} from 'monaco-languageclient/lib/monaco-converter';
import { getLanguageService, TextDocument } from 'yaml-language-server';
import { openAPItoJSONSchema } from '@console/internal/module/k8s/openapi-to-json-schema';
import { getStoredSwagger } from '@console/internal/module/k8s/swagger';
import { getSwaggerDefinitions } from '@console/internal/module/k8s/swagger';
import { global_BackgroundColor_dark_100 as editorBackground } from '@patternfly/react-tokens';

window.monaco.editor.defineTheme('console', {
Expand Down Expand Up @@ -62,7 +62,7 @@ export const createYAMLService = () => {
const yamlService = getLanguageService(resolveSchema, workspaceContext, []);

// Prepare the schema
const yamlOpenAPI = getStoredSwagger();
const yamlOpenAPI = getSwaggerDefinitions();

// Convert the openAPI schema to something the language server understands
const kubernetesJSONSchema = openAPItoJSONSchema(yamlOpenAPI);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ type K8sResourceSelectProps = {
title?: string;
validation?: ValidationObject;
filter?: (obj: K8sResourceKind) => boolean;
getResourceLabel?: (resource: K8sResourceKind) => string;
};

export const K8sResourceSelectRow: React.FC<K8sResourceSelectProps> = ({
Expand All @@ -36,6 +37,7 @@ export const K8sResourceSelectRow: React.FC<K8sResourceSelectProps> = ({
title,
validation,
filter,
getResourceLabel,
}) => {
const isLoading = !isLoaded(data);
const loadError = getLoadError(data, model);
Expand Down Expand Up @@ -84,7 +86,11 @@ export const K8sResourceSelectRow: React.FC<K8sResourceSelectProps> = ({
)}
{ignoreCaseSort(loadedData, ['metadata', 'name']).map((entity) => {
const selectName = getName(entity);
return <FormSelectOption key={selectName} value={selectName} label={selectName} />;
const label = getResourceLabel && getResourceLabel(entity);

return (
<FormSelectOption key={selectName} value={selectName} label={label || selectName} />
);
})}
</FormSelect>
</FormRow>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import {
PersistentVolumeClaimModel,
StorageClassModel,
} from '@console/internal/models';
import { getName } from '@console/shared/src';
import { getName, getAnnotations } from '@console/shared/src';
import { getLoadedData, isLoaded, prefixedID, resolveDataVolumeName } from '../../../utils';
import { validateDisk } from '../../../utils/validations/vm';
import { isValidationError } from '../../../utils/validations/common';
Expand Down Expand Up @@ -47,6 +47,7 @@ import { DiskWrapper } from '../../../k8s/wrapper/vm/disk-wrapper';
import { DataVolumeWrapper } from '../../../k8s/wrapper/vm/data-volume-wrapper';
import { VolumeWrapper } from '../../../k8s/wrapper/vm/volume-wrapper';
import { AccessMode, DiskBus, DiskType, VolumeMode } from '../../../constants/vm/storage';
import { DEFAULT_SC_ANNOTATION } from '../../../constants/sc';
import { getPvcStorageSize } from '../../../selectors/pvc/selectors';
import { K8sResourceSelectRow } from '../../form/k8s-resource-select-row';
import { SizeUnitFormRow } from '../../form/size-unit-form-row';
Expand Down Expand Up @@ -548,6 +549,11 @@ export const DiskModal = withHandlePromise((props: DiskModalProps) => {
model={StorageClassModel}
hasPlaceholder
onChange={(sc) => onStorageClassNameChanged(sc || '')}
getResourceLabel={(sc) =>
getAnnotations(sc, {})[DEFAULT_SC_ANNOTATION] === 'true'
? `${getName(sc)} (default)`
: getName(sc)
}
/>
)}
{isPlainDataVolume && (
Expand Down
4 changes: 2 additions & 2 deletions frontend/public/components/sidebars/explore-type-sidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { Breadcrumb, BreadcrumbItem, Button } from '@patternfly/react-core';

import {
getDefinitionKey,
getStoredSwagger,
getSwaggerDefinitions,
getSwaggerPath,
K8sKind,
SwaggerDefinition,
Expand All @@ -29,7 +29,7 @@ export const ExploreType: React.FC<ExploreTypeProps> = (props) => {
return null;
}

const allDefinitions: SwaggerDefinitions = getStoredSwagger();
const allDefinitions: SwaggerDefinitions = getSwaggerDefinitions();
if (!allDefinitions) {
return null;
}
Expand Down
58 changes: 17 additions & 41 deletions frontend/public/module/k8s/swagger.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,43 +21,22 @@ export const getDefinitionKey = _.memoize(
referenceForModel,
);

// Cache parsed swagger to avoid reparsing the JSON each call.
let swagger: SwaggerDefinitions;
export const getStoredSwagger = (): SwaggerDefinitions => {
if (swagger) {
return swagger;
}
const json = window.localStorage.getItem(SWAGGER_LOCAL_STORAGE_KEY);
if (!json) {
return null;
}
try {
swagger = JSON.parse(json);
return swagger;
} catch (e) {
// eslint-disable-next-line no-console
console.error('Could not parse swagger JSON.', e);
return null;
}
};

const storeSwagger = (definitions: SwaggerDefinitions) => {
// Only store definitions to reduce the document size.
const json = JSON.stringify(definitions);
window.localStorage.setItem(SWAGGER_LOCAL_STORAGE_KEY, json);
swagger = definitions;
};
let swaggerDefinitions: SwaggerDefinitions;
export const getSwaggerDefinitions = (): SwaggerDefinitions => swaggerDefinitions;

export const fetchSwagger = async (): Promise<SwaggerDefinitions> => {
// Remove any old definitions from `localSotrage`. We rely on the browser cache now.
// TODO: We should be able to remove this in a future release.
localStorage.removeItem(SWAGGER_LOCAL_STORAGE_KEY);
try {
const response: SwaggerAPISpec = await coFetchJSON('api/kubernetes/openapi/v2');
if (!response.definitions) {
// eslint-disable-next-line no-console
console.error('Definitions missing in OpenAPI response.');
return null;
}
storeSwagger(response.definitions);
return response.definitions;
swaggerDefinitions = response.definitions;
return swaggerDefinitions;
} catch (e) {
// eslint-disable-next-line no-console
console.error('Could not get OpenAPI definitions', e);
Expand All @@ -66,12 +45,11 @@ export const fetchSwagger = async (): Promise<SwaggerDefinitions> => {
};

export const definitionFor = _.memoize((model: K8sKind): SwaggerDefinition => {
const allDefinitions: SwaggerDefinitions = getStoredSwagger();
if (!allDefinitions) {
if (!swaggerDefinitions) {
return null;
}
const key = getDefinitionKey(model, allDefinitions);
return _.get(allDefinitions, key);
const key = getDefinitionKey(model, swaggerDefinitions);
return _.get(swaggerDefinitions, key);
}, referenceForModel);

const getRef = (definition: SwaggerDefinition): string => {
Expand Down Expand Up @@ -102,25 +80,24 @@ export const getSwaggerPath = (
};

const findDefinition = (kindObj: K8sKind, propertyPath: string[]): SwaggerDefinition => {
const allDefinitions: SwaggerDefinitions = getStoredSwagger();
if (!allDefinitions) {
if (!swaggerDefinitions) {
return null;
}

const rootPath = getDefinitionKey(kindObj, allDefinitions);
const rootPath = getDefinitionKey(kindObj, swaggerDefinitions);
const path = propertyPath.reduce(
(currentPath: string[], nextProperty: string, i: number): string[] => {
if (!currentPath) {
return null;
}
// Don't follow the last reference since the description is not as good.
const followRef = i !== propertyPath.length - 1;
return getSwaggerPath(allDefinitions, currentPath, nextProperty, followRef);
return getSwaggerPath(swaggerDefinitions, currentPath, nextProperty, followRef);
},
[rootPath],
);

return path ? (_.get(allDefinitions, path) as SwaggerDefinition) : null;
return path ? (_.get(swaggerDefinitions, path) as SwaggerDefinition) : null;
};

export const getPropertyDescription = (
Expand All @@ -133,12 +110,11 @@ export const getPropertyDescription = (
};

export const getResourceDescription = _.memoize((kindObj: K8sKind): string => {
const allDefinitions: SwaggerDefinitions = getStoredSwagger();
if (!allDefinitions) {
if (!swaggerDefinitions) {
return null;
}
const key = getDefinitionKey(kindObj, allDefinitions);
return _.get(allDefinitions, [key, 'description']);
const key = getDefinitionKey(kindObj, swaggerDefinitions);
return _.get(swaggerDefinitions, [key, 'description']);
}, referenceForModel);

export type SwaggerDefinition = {
Expand Down

0 comments on commit 285515f

Please sign in to comment.