[Enterprise Search] Upgrade to Kea 2.2-rc.4 (#76206)

* Add Kea 2.20.rc.3

* Remove Kea declarations

* Convert AppLogic files to use new syntax

- Utilizes MakeLogicType to type the logic file and remove bespoke typings
- Use object syntax for actions and reducers, instead of functions
- Add return types to actions in interface
- Remove interfaces from component (Kea does this automagically now)

- Also renamed IAppLogic* to IApp*

* Convert Workplace Search Overview files to use new syntax

- Utilizes MakeLogicType to type the logic file and remove bespoke typings
- Use object syntax for actions and reducers, instead of functions
- Add return types to actions in interface
- Remove interfaces from component (Kea does this automagically now)

- Also renamed mockLogic* to mockApp*

* Convert HttpLogic files to use new syntax

- Utilizes MakeLogicType to type the logic file and remove bespoke typings
- Use object syntax for actions and reducers, instead of functions
- Add return types to actions in interface
- Remove interfaces from component (Kea does this automagically now)

- Also renamed IHttpLogic* to IHttp*

* Convert FlashMessages files to use new syntax

- Utilizes MakeLogicType to type the logic file and remove bespoke typings
- Use object syntax for actions and reducers, instead of functions
- Add return types to actions in interface
- Remove interfaces from component (Kea does this automagically now)

* Remove hand-rolled Kea types

Bye-bye pretty code

* Upgrade to rc4 per author

From Kea author: “I’d suggest upgrading to rc.4 before merging though, as I seem to have committed some optional chaining operators ("?.") into the compiled JS output, which will cause errors if anyone is using an older browser. You can also wait for 2.2.0 final, but that might still be a week or more away…”

Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>
This commit is contained in:
Scotty Bollinger 2020-08-31 09:52:37 -05:00 committed by GitHub
parent 9b5912203f
commit 137aadb830
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
23 changed files with 97 additions and 181 deletions

View file

@ -195,7 +195,7 @@
"jsdom": "13.1.0",
"jsondiffpatch": "0.4.1",
"jsts": "^1.6.2",
"kea": "^2.0.1",
"kea": "2.2.0-rc.4",
"loader-utils": "^1.2.3",
"lz-string": "^1.4.4",
"madge": "3.4.4",

View file

@ -4,28 +4,27 @@
* you may not use this file except in compliance with the Elastic License.
*/
import { kea } from 'kea';
import { kea, MakeLogicType } from 'kea';
import { IInitialAppData } from '../../../common/types';
import { IKeaLogic } from '../shared/types';
export interface IAppLogicValues {
export interface IAppValues {
hasInitialized: boolean;
}
export interface IAppLogicActions {
export interface IAppActions {
initializeAppData(props: IInitialAppData): void;
}
export const AppLogic = kea({
actions: (): IAppLogicActions => ({
export const AppLogic = kea<MakeLogicType<IAppValues, IAppActions>>({
actions: {
initializeAppData: (props) => props,
}),
reducers: () => ({
},
reducers: {
hasInitialized: [
false,
{
initializeAppData: () => true,
},
],
}),
}) as IKeaLogic<IAppLogicValues, IAppLogicActions>;
},
});

View file

@ -11,8 +11,8 @@ import { useActions, useValues } from 'kea';
import { i18n } from '@kbn/i18n';
import { KibanaContext, IKibanaContext } from '../index';
import { HttpLogic, IHttpLogicValues } from '../shared/http';
import { AppLogic, IAppLogicActions, IAppLogicValues } from './app_logic';
import { HttpLogic } from '../shared/http';
import { AppLogic } from './app_logic';
import { IInitialAppData } from '../../../common/types';
import { APP_SEARCH_PLUGIN } from '../../../common/constants';
@ -48,9 +48,9 @@ export const AppSearchUnconfigured: React.FC = () => (
);
export const AppSearchConfigured: React.FC<IInitialAppData> = (props) => {
const { hasInitialized } = useValues(AppLogic) as IAppLogicValues;
const { initializeAppData } = useActions(AppLogic) as IAppLogicActions;
const { errorConnecting } = useValues(HttpLogic) as IHttpLogicValues;
const { hasInitialized } = useValues(AppLogic);
const { initializeAppData } = useActions(AppLogic);
const { errorConnecting } = useValues(HttpLogic);
useEffect(() => {
if (!hasInitialized) initializeAppData(props);

View file

@ -1,13 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
declare module 'kea' {
export function useValues(logic?: object): object;
export function useActions(logic?: object): object;
export function getContext(): { store: object };
export function resetContext(context: object): object;
export function kea(logic: object): object;
}

View file

@ -8,7 +8,7 @@ import React, { Fragment } from 'react';
import { useValues } from 'kea';
import { EuiCallOut, EuiCallOutProps, EuiSpacer } from '@elastic/eui';
import { FlashMessagesLogic, IFlashMessagesValues } from './flash_messages_logic';
import { FlashMessagesLogic } from './flash_messages_logic';
const FLASH_MESSAGE_TYPES = {
success: { color: 'success' as EuiCallOutProps['color'], icon: 'check' },
@ -18,7 +18,7 @@ const FLASH_MESSAGE_TYPES = {
};
export const FlashMessages: React.FC = ({ children }) => {
const { messages } = useValues(FlashMessagesLogic) as IFlashMessagesValues;
const { messages } = useValues(FlashMessagesLogic);
// If we have no messages to display, do not render the element at all
if (!messages.length) return null;

View file

@ -4,12 +4,10 @@
* you may not use this file except in compliance with the Elastic License.
*/
import { kea } from 'kea';
import { kea, MakeLogicType } from 'kea';
import { ReactNode } from 'react';
import { History } from 'history';
import { IKeaLogic, TKeaReducers, IKeaParams } from '../types';
export interface IFlashMessage {
type: 'success' | 'info' | 'warning' | 'error';
message: ReactNode;
@ -22,27 +20,27 @@ export interface IFlashMessagesValues {
historyListener: Function | null;
}
export interface IFlashMessagesActions {
setFlashMessages(messages: IFlashMessage | IFlashMessage[]): void;
setFlashMessages(messages: IFlashMessage | IFlashMessage[]): { messages: IFlashMessage[] };
clearFlashMessages(): void;
setQueuedMessages(messages: IFlashMessage | IFlashMessage[]): void;
setQueuedMessages(messages: IFlashMessage | IFlashMessage[]): { messages: IFlashMessage[] };
clearQueuedMessages(): void;
listenToHistory(history: History): void;
setHistoryListener(historyListener: Function): void;
listenToHistory(history: History): History;
setHistoryListener(historyListener: Function): { historyListener: Function };
}
const convertToArray = (messages: IFlashMessage | IFlashMessage[]) =>
!Array.isArray(messages) ? [messages] : messages;
export const FlashMessagesLogic = kea({
actions: (): IFlashMessagesActions => ({
export const FlashMessagesLogic = kea<MakeLogicType<IFlashMessagesValues, IFlashMessagesActions>>({
actions: {
setFlashMessages: (messages) => ({ messages: convertToArray(messages) }),
clearFlashMessages: () => null,
setQueuedMessages: (messages) => ({ messages: convertToArray(messages) }),
clearQueuedMessages: () => null,
listenToHistory: (history) => history,
setHistoryListener: (historyListener) => ({ historyListener }),
}),
reducers: (): TKeaReducers<IFlashMessagesValues, IFlashMessagesActions> => ({
},
reducers: {
messages: [
[],
{
@ -63,8 +61,8 @@ export const FlashMessagesLogic = kea({
setHistoryListener: (_, { historyListener }) => historyListener,
},
],
}),
listeners: ({ values, actions }): Partial<IFlashMessagesActions> => ({
},
listeners: ({ values, actions }) => ({
listenToHistory: (history) => {
// On React Router navigation, clear previous flash messages and load any queued messages
const unlisten = history.listen(() => {
@ -81,7 +79,4 @@ export const FlashMessagesLogic = kea({
if (removeHistoryListener) removeHistoryListener();
},
}),
} as IKeaParams<IFlashMessagesValues, IFlashMessagesActions>) as IKeaLogic<
IFlashMessagesValues,
IFlashMessagesActions
>;
});

View file

@ -8,19 +8,15 @@ import React, { useEffect } from 'react';
import { useValues, useActions } from 'kea';
import { History } from 'history';
import {
FlashMessagesLogic,
IFlashMessagesValues,
IFlashMessagesActions,
} from './flash_messages_logic';
import { FlashMessagesLogic } from './flash_messages_logic';
interface IFlashMessagesProviderProps {
history: History;
}
export const FlashMessagesProvider: React.FC<IFlashMessagesProviderProps> = ({ history }) => {
const { historyListener } = useValues(FlashMessagesLogic) as IFlashMessagesValues;
const { listenToHistory } = useActions(FlashMessagesLogic) as IFlashMessagesActions;
const { historyListener } = useValues(FlashMessagesLogic);
const { listenToHistory } = useActions(FlashMessagesLogic);
useEffect(() => {
if (!historyListener) listenToHistory(history);

View file

@ -4,32 +4,36 @@
* you may not use this file except in compliance with the Elastic License.
*/
import { kea } from 'kea';
import { kea, MakeLogicType } from 'kea';
import { HttpSetup } from 'src/core/public';
import { IKeaLogic, IKeaParams, TKeaReducers } from '../../shared/types';
export interface IHttpLogicValues {
export interface IHttpValues {
http: HttpSetup;
httpInterceptors: Function[];
errorConnecting: boolean;
}
export interface IHttpLogicActions {
initializeHttp({ http, errorConnecting }: { http: HttpSetup; errorConnecting?: boolean }): void;
export interface IHttpActions {
initializeHttp({
http,
errorConnecting,
}: {
http: HttpSetup;
errorConnecting?: boolean;
}): { http: HttpSetup; errorConnecting?: boolean };
initializeHttpInterceptors(): void;
setHttpInterceptors(httpInterceptors: Function[]): void;
setErrorConnecting(errorConnecting: boolean): void;
setHttpInterceptors(httpInterceptors: Function[]): { httpInterceptors: Function[] };
setErrorConnecting(errorConnecting: boolean): { errorConnecting: boolean };
}
export const HttpLogic = kea({
actions: (): IHttpLogicActions => ({
export const HttpLogic = kea<MakeLogicType<IHttpValues, IHttpActions>>({
actions: {
initializeHttp: ({ http, errorConnecting }) => ({ http, errorConnecting }),
initializeHttpInterceptors: () => null,
setHttpInterceptors: (httpInterceptors) => ({ httpInterceptors }),
setErrorConnecting: (errorConnecting) => ({ errorConnecting }),
}),
reducers: (): TKeaReducers<IHttpLogicValues, IHttpLogicActions> => ({
},
reducers: {
http: [
(null as unknown) as HttpSetup,
{
@ -49,7 +53,7 @@ export const HttpLogic = kea({
setErrorConnecting: (_, { errorConnecting }) => errorConnecting,
},
],
}),
},
listeners: ({ values, actions }) => ({
initializeHttpInterceptors: () => {
const httpInterceptors = [];
@ -80,7 +84,4 @@ export const HttpLogic = kea({
});
},
}),
} as IKeaParams<IHttpLogicValues, IHttpLogicActions>) as IKeaLogic<
IHttpLogicValues,
IHttpLogicActions
>;
});

View file

@ -9,7 +9,7 @@ import { useActions } from 'kea';
import { HttpSetup } from 'src/core/public';
import { HttpLogic, IHttpLogicActions } from './http_logic';
import { HttpLogic } from './http_logic';
interface IHttpProviderProps {
http: HttpSetup;
@ -17,7 +17,7 @@ interface IHttpProviderProps {
}
export const HttpProvider: React.FC<IHttpProviderProps> = (props) => {
const { initializeHttp, initializeHttpInterceptors } = useActions(HttpLogic) as IHttpLogicActions;
const { initializeHttp, initializeHttpInterceptors } = useActions(HttpLogic);
useEffect(() => {
initializeHttp(props);

View file

@ -4,5 +4,5 @@
* you may not use this file except in compliance with the Elastic License.
*/
export { HttpLogic, IHttpLogicValues, IHttpLogicActions } from './http_logic';
export { HttpLogic, IHttpValues, IHttpActions } from './http_logic';
export { HttpProvider } from './http_provider';

View file

@ -5,63 +5,3 @@
*/
export { IFlashMessage } from './flash_messages';
export interface IKeaLogic<IKeaValues, IKeaActions> {
mount(): Function;
values: IKeaValues;
actions: IKeaActions;
}
/**
* This reusable interface mostly saves us a few characters / allows us to skip
* defining params inline. Unfortunately, the return values *do not work* as
* expected (hence the voids). While I can tell selectors to use TKeaSelectors,
* the return value is *not* properly type checked if it's not declared inline. :/
*
* Also note that if you switch to Kea 2.1's plain object notation -
* `selectors: {}` vs. `selectors: () => ({})`
* - type checking also stops working and type errors become significantly less
* helpful - showing less specific error messages and highlighting. 👎
*/
export interface IKeaParams<IKeaValues, IKeaActions> {
selectors?(params: { selectors: IKeaValues }): void;
listeners?(params: { actions: IKeaActions; values: IKeaValues }): void;
events?(params: { actions: IKeaActions; values: IKeaValues }): void;
}
/**
* This reducers() type checks that:
*
* 1. The value object keys are defined within IKeaValues
* 2. The default state (array[0]) matches the type definition within IKeaValues
* 3. The action object keys (array[1]) are defined within IKeaActions
* 3. The new state returned by the action matches the type definition within IKeaValues
*/
export type TKeaReducers<IKeaValues, IKeaActions> = {
[Value in keyof IKeaValues]?: [
IKeaValues[Value],
{
[Action in keyof IKeaActions]?: (
state: IKeaValues[Value],
payload: IKeaValues
) => IKeaValues[Value];
}
];
};
/**
* This selectors() type checks that:
*
* 1. The object keys are defined within IKeaValues
* 2. The selected values are defined within IKeaValues
* 3. The returned value match the type definition within IKeaValues
*
* The unknown[] and any[] are unfortunately because I have no idea how to
* assert for arbitrary type/values as an array
*/
export type TKeaSelectors<IKeaValues> = {
[Value in keyof IKeaValues]?: [
(selectors: IKeaValues) => unknown[],
(...args: any[]) => IKeaValues[Value] // eslint-disable-line @typescript-eslint/no-explicit-any
];
};

View file

@ -4,11 +4,10 @@
* you may not use this file except in compliance with the Elastic License.
*/
import { kea } from 'kea';
import { kea, MakeLogicType } from 'kea';
import { IInitialAppData } from '../../../common/types';
import { IWorkplaceSearchInitialData } from '../../../common/types/workplace_search';
import { IKeaLogic } from '../shared/types';
export interface IAppValues extends IWorkplaceSearchInitialData {
hasInitialized: boolean;
@ -17,16 +16,16 @@ export interface IAppActions {
initializeAppData(props: IInitialAppData): void;
}
export const AppLogic = kea({
actions: (): IAppActions => ({
export const AppLogic = kea<MakeLogicType<IAppValues, IAppActions>>({
actions: {
initializeAppData: ({ workplaceSearch }) => workplaceSearch,
}),
reducers: () => ({
},
reducers: {
hasInitialized: [
false,
{
initializeAppData: () => true,
},
],
}),
}) as IKeaLogic<IAppValues, IAppActions>;
},
});

View file

@ -10,8 +10,8 @@ import { useActions, useValues } from 'kea';
import { IInitialAppData } from '../../../common/types';
import { KibanaContext, IKibanaContext } from '../index';
import { HttpLogic, IHttpLogicValues } from '../shared/http';
import { AppLogic, IAppActions, IAppValues } from './app_logic';
import { HttpLogic } from '../shared/http';
import { AppLogic } from './app_logic';
import { Layout } from '../shared/layout';
import { WorkplaceSearchNav } from './components/layout/nav';
@ -27,9 +27,9 @@ export const WorkplaceSearch: React.FC<IInitialAppData> = (props) => {
};
export const WorkplaceSearchConfigured: React.FC<IInitialAppData> = (props) => {
const { hasInitialized } = useValues(AppLogic) as IAppValues;
const { initializeAppData } = useActions(AppLogic) as IAppActions;
const { errorConnecting } = useValues(HttpLogic) as IHttpLogicValues;
const { hasInitialized } = useValues(AppLogic);
const { initializeAppData } = useActions(AppLogic);
const { errorConnecting } = useValues(HttpLogic);
useEffect(() => {
if (!hasInitialized) initializeAppData(props);

View file

@ -4,4 +4,4 @@
* you may not use this file except in compliance with the Elastic License.
*/
export { setMockValues, mockLogicValues, mockLogicActions } from './overview_logic.mock';
export { setMockValues, mockValues, mockActions } from './overview_logic.mock';

View file

@ -7,7 +7,7 @@
import { IOverviewValues } from '../overview_logic';
import { IAccount, IOrganization } from '../../../types';
export const mockLogicValues = {
export const mockValues = {
accountsCount: 0,
activityFeed: [],
canCreateContentSources: false,
@ -24,21 +24,21 @@ export const mockLogicValues = {
dataLoading: true,
} as IOverviewValues;
export const mockLogicActions = {
export const mockActions = {
initializeOverview: jest.fn(() => ({})),
};
jest.mock('kea', () => ({
...(jest.requireActual('kea') as object),
useActions: jest.fn(() => ({ ...mockLogicActions })),
useValues: jest.fn(() => ({ ...mockLogicValues })),
useActions: jest.fn(() => ({ ...mockActions })),
useValues: jest.fn(() => ({ ...mockValues })),
}));
import { useValues } from 'kea';
export const setMockValues = (values: object) => {
(useValues as jest.Mock).mockImplementationOnce(() => ({
...mockLogicValues,
...mockValues,
...values,
}));
};

View file

@ -28,7 +28,7 @@ import { ORG_SOURCES_PATH, USERS_PATH, ORG_SETTINGS_PATH } from '../../routes';
import { ContentSection } from '../../components/shared/content_section';
import { OverviewLogic, IOverviewValues } from './overview_logic';
import { OverviewLogic } from './overview_logic';
import { OnboardingCard } from './onboarding_card';
@ -68,7 +68,7 @@ export const OnboardingSteps: React.FC = () => {
fpAccount: { isCurated },
organization: { name, defaultOrgName },
isFederatedAuth,
} = useValues(OverviewLogic) as IOverviewValues;
} = useValues(OverviewLogic);
const accountsPath =
!isFederatedAuth && (canCreateInvitations || isCurated) ? USERS_PATH : undefined;

View file

@ -14,7 +14,7 @@ import { i18n } from '@kbn/i18n';
import { ContentSection } from '../../components/shared/content_section';
import { ORG_SOURCES_PATH, USERS_PATH } from '../../routes';
import { OverviewLogic, IOverviewValues } from './overview_logic';
import { OverviewLogic } from './overview_logic';
import { StatisticCard } from './statistic_card';
@ -25,7 +25,7 @@ export const OrganizationStats: React.FC = () => {
accountsCount,
personalSourcesCount,
isFederatedAuth,
} = useValues(OverviewLogic) as IOverviewValues;
} = useValues(OverviewLogic);
return (
<ContentSection

View file

@ -6,7 +6,7 @@
import '../../../__mocks__/react_router_history.mock';
import './__mocks__/overview_logic.mock';
import { mockLogicActions, setMockValues } from './__mocks__';
import { mockActions, setMockValues } from './__mocks__';
import React from 'react';
import { shallow, mount } from 'enzyme';
@ -32,7 +32,7 @@ describe('Overview', () => {
it('calls initialize function', async () => {
mount(<Overview />);
expect(mockLogicActions.initializeOverview).toHaveBeenCalled();
expect(mockActions.initializeOverview).toHaveBeenCalled();
});
it('renders onboarding state', () => {

View file

@ -14,7 +14,7 @@ import { useActions, useValues } from 'kea';
import { SetWorkplaceSearchChrome as SetPageChrome } from '../../../shared/kibana_chrome';
import { SendWorkplaceSearchTelemetry as SendTelemetry } from '../../../shared/telemetry';
import { OverviewLogic, IOverviewActions, IOverviewValues } from './overview_logic';
import { OverviewLogic } from './overview_logic';
import { Loading } from '../../components/shared/loading';
import { ProductButton } from '../../components/shared/product_button';
@ -44,7 +44,7 @@ const HEADER_DESCRIPTION = i18n.translate(
);
export const Overview: React.FC = () => {
const { initializeOverview } = useActions(OverviewLogic) as IOverviewActions;
const { initializeOverview } = useActions(OverviewLogic);
const {
dataLoading,
@ -52,7 +52,7 @@ export const Overview: React.FC = () => {
hasOrgSources,
isOldAccount,
organization: { name: orgName, defaultOrgName },
} = useValues(OverviewLogic) as IOverviewValues;
} = useValues(OverviewLogic);
useEffect(() => {
initializeOverview();

View file

@ -9,7 +9,7 @@ import { resetContext } from 'kea';
jest.mock('../../../shared/http', () => ({ HttpLogic: { values: { http: { get: jest.fn() } } } }));
import { HttpLogic } from '../../../shared/http';
import { mockLogicValues } from './__mocks__';
import { mockValues } from './__mocks__';
import { OverviewLogic } from './overview_logic';
describe('OverviewLogic', () => {
@ -20,7 +20,7 @@ describe('OverviewLogic', () => {
});
it('has expected default values', () => {
expect(OverviewLogic.values).toEqual(mockLogicValues);
expect(OverviewLogic.values).toEqual(mockValues);
});
describe('setServerData', () => {

View file

@ -4,11 +4,10 @@
* you may not use this file except in compliance with the Elastic License.
*/
import { kea } from 'kea';
import { kea, MakeLogicType } from 'kea';
import { HttpLogic } from '../../../shared/http';
import { IAccount, IOrganization } from '../../types';
import { IKeaLogic, TKeaReducers, IKeaParams } from '../../../shared/types';
import { IFeedActivity } from './recent_activity';
@ -29,7 +28,7 @@ export interface IOverviewServerData {
}
export interface IOverviewActions {
setServerData(serverData: IOverviewServerData): void;
setServerData(serverData: IOverviewServerData): IOverviewServerData;
initializeOverview(): void;
}
@ -37,12 +36,12 @@ export interface IOverviewValues extends IOverviewServerData {
dataLoading: boolean;
}
export const OverviewLogic = kea({
actions: (): IOverviewActions => ({
export const OverviewLogic = kea<MakeLogicType<IOverviewValues, IOverviewActions>>({
actions: {
setServerData: (serverData) => serverData,
initializeOverview: () => null,
}),
reducers: (): TKeaReducers<IOverviewValues, IOverviewActions> => ({
},
reducers: {
organization: [
{} as IOrganization,
{
@ -127,11 +126,11 @@ export const OverviewLogic = kea({
setServerData: () => false,
},
],
}),
listeners: ({ actions }): Partial<IOverviewActions> => ({
},
listeners: ({ actions }) => ({
initializeOverview: async () => {
const response = await HttpLogic.values.http.get('/api/workplace_search/overview');
actions.setServerData(response);
},
}),
} as IKeaParams<IOverviewValues, IOverviewActions>) as IKeaLogic<IOverviewValues, IOverviewActions>;
});

View file

@ -17,7 +17,7 @@ import { sendTelemetry } from '../../../shared/telemetry';
import { KibanaContext, IKibanaContext } from '../../../index';
import { getSourcePath } from '../../routes';
import { OverviewLogic, IOverviewValues } from './overview_logic';
import { OverviewLogic } from './overview_logic';
import './recent_activity.scss';
@ -33,7 +33,7 @@ export const RecentActivity: React.FC = () => {
const {
organization: { name, defaultOrgName },
activityFeed,
} = useValues(OverviewLogic) as IOverviewValues;
} = useValues(OverviewLogic);
return (
<ContentSection

View file

@ -18099,10 +18099,10 @@ kdbush@^3.0.0:
resolved "https://registry.yarnpkg.com/kdbush/-/kdbush-3.0.0.tgz#f8484794d47004cc2d85ed3a79353dbe0abc2bf0"
integrity sha512-hRkd6/XW4HTsA9vjVpY9tuXJYLSlelnkTmVFu4M9/7MIYQtFcHpbugAU7UbOfjOiVSVYl2fqgBuJ32JUmRo5Ew==
kea@^2.0.1:
version "2.1.1"
resolved "https://registry.yarnpkg.com/kea/-/kea-2.1.1.tgz#6e3c65c4873b67d270a2ec7bf73b0d178937234c"
integrity sha512-W9o4lHLOcEDIu3ASHPrWJJJzL1bMkGyxaHn9kuaDgI96ztBShVrf52R0QPGlQ2k9ca3XnkB/dnVHio1UB8kGWA==
kea@2.2.0-rc.4:
version "2.2.0-rc.4"
resolved "https://registry.yarnpkg.com/kea/-/kea-2.2.0-rc.4.tgz#cc0376950530a6751f73387c4b25a39efa1faa77"
integrity sha512-pYuwaCiJkBvHZShi8kqhk8dC4DjeELdK51Lw7Pn0tNdJgZJDF6COhsUiF/yrh9d7woNYDxKfuxH+QWZFfo8PkA==
kew@~0.1.7:
version "0.1.7"