Skip to content

Commit

Permalink
#538 - wip - remove modals and display new env. values in UI
Browse files Browse the repository at this point in the history
  • Loading branch information
quentinovega committed Aug 23, 2023
1 parent 4e06905 commit 9c16e90
Show file tree
Hide file tree
Showing 5 changed files with 110 additions and 91 deletions.
33 changes: 20 additions & 13 deletions daikoku/javascript/src/components/frontend/api/ApiDocumentation.tsx
Original file line number Diff line number Diff line change
@@ -1,20 +1,17 @@
/* eslint-disable react/display-name */
import { useQuery, useQueryClient } from '@tanstack/react-query';
import asciidoctor from 'asciidoctor';
import classNames from 'classnames';
import hljs from 'highlight.js';
import { useContext, useEffect, useState } from 'react';
import { Link, useMatch, useMatches, useParams } from 'react-router-dom';
import { useQuery, useQueryClient } from '@tanstack/react-query';
import classNames from 'classnames';
import {Option} from '../../utils';

import { I18nContext } from '../../../core';
import * as Services from '../../../services';
import { converter } from '../../../services/showdown';

import { IApi, IDocPage, IDocumentation, IDocumentationPages, ResponseError, isError } from '../../../types';
import { IDocPage, IDocumentation, IDocumentationPages, ResponseError, isError } from '../../../types';
import { Spinner } from '../../utils';

import 'highlight.js/styles/monokai.css';
import { ParamKeyValuePair, useSearchParams } from 'react-router-dom';

const asciidoctorConverter = asciidoctor();

Expand All @@ -33,7 +30,7 @@ export const ApiDocumentationCartidge = (props: ApiDocumentationCartidgeProps) =
{pages.map((page) => {
return (
<li className="api-doc-cartridge-link cursor-pointer" key={page.id} style={{ marginLeft: level * 10 }}>
<a className={classNames({active: page.id === props.currentPageId})} onClick={() => props.goTo(page.id)}>
<a className={classNames({ active: page.id === props.currentPageId })} onClick={() => props.goTo(page.id)}>
{page.title}
</a>
{renderLinks(page.children, level + 1)}
Expand Down Expand Up @@ -107,7 +104,10 @@ type ApiDocumentationProps = {
export const ApiDocumentation = (props: ApiDocumentationProps) => {
const { Translation } = useContext(I18nContext);

const [pageId, setPageId] = useState(props.documentation?.pages[0].id)
const [searchParams, setSearchParams] = useSearchParams();

const page = searchParams.get('page');
const [pageId, setPageId] = useState(page || props.documentation?.pages[0].id);

const flattenDoc = (pages?: IDocumentationPages): Array<string> => {
if (!pages) {
Expand All @@ -117,14 +117,21 @@ export const ApiDocumentation = (props: ApiDocumentationProps) => {
}
}

useEffect(() => {
if (pageId) {
setSearchParams({page: pageId})
}
}, [pageId])


const orderedPages = flattenDoc(props.documentation?.pages)

const idx = orderedPages.findIndex(p => p === pageId)
const next = orderedPages[idx + (pageId ? 1 : 2)];
const prev = orderedPages[idx - 1];

return (<>
<ApiDocumentationCartidge documentation={props.documentation} currentPageId={pageId} goTo={setPageId}/>
return (<div className='d-flex flex-row'>
<ApiDocumentationCartidge documentation={props.documentation} currentPageId={pageId} goTo={setPageId} />
<div className="col p-3 d-flex flex-column">
<div className={classNames("d-flex", {
'justify-content-between': !!prev,
Expand All @@ -139,9 +146,9 @@ export const ApiDocumentation = (props: ApiDocumentationProps) => {
<i className="fas fa-chevron-right ms-1" />
</button>)}
</div>
<ApiDocPage pageId={pageId} getDocPage={props.getDocPage}/>
<ApiDocPage pageId={pageId} getDocPage={props.getDocPage} />
</div >
</>);
</div>);
}

const TypeNotSupportedYet = () => <h3>Content type not supported yet !</h3>;
Expand Down
2 changes: 1 addition & 1 deletion daikoku/javascript/src/components/frontend/api/ApiHome.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ export const ApiHome = ({

const navigate = useNavigate();
const defaultParams = useParams();
const apiGroupMatch = useMatch('/:teamId/apigroups/:apiGroupId/apis/:apiId/:versionId/:tab');
const apiGroupMatch = useMatch('/:teamId/apigroups/:apiGroupId/apis/:apiId/:versionId/:tab*');
const params = Option(apiGroupMatch)
.map((match: any) => match.params)
.getOrElse(defaultParams);
Expand Down
135 changes: 72 additions & 63 deletions daikoku/javascript/src/components/frontend/api/ApiPricing.tsx
Original file line number Diff line number Diff line change
@@ -1,31 +1,31 @@
import { getApolloContext } from '@apollo/client';
import { constraints, format, type as formType } from '@maif/react-forms';
import { useQuery } from '@tanstack/react-query';
import classNames from 'classnames';
import difference from 'lodash/difference';
import find from 'lodash/find';
import React, { useContext, useEffect } from 'react';
import React, { useContext, useEffect, useState } from 'react';
import { useSelector } from 'react-redux';
import { Link, useMatch, useNavigate, useParams, useSearchParams } from 'react-router-dom';

import { ModalContext } from '../../../contexts';
import { I18nContext } from '../../../core';
import * as Services from '../../../services';
import { currencies } from '../../../services/currencies';
import { IApi, IBaseUsagePlan, isError, isMiniFreeWithQuotas, IState, IStateContext, ISubscription, ISubscriptionDemand, ISubscriptionWithApiInfo, isValidationStepTeamAdmin, ITeamSimple, IUsagePlan, IUsagePlanFreeWithQuotas, IUsagePlanPayPerUse, IUsagePlanQuotasWithLimits, IUsagePlanQuotasWitoutLimit, IValidationStepTeamAdmin } from '../../../types';
import { INotification } from '../../../types';
import {
IApi, IBaseUsagePlan, isError,
isMiniFreeWithQuotas, IState, IStateContext, ISubscription,
ISubscriptionDemand, ISubscriptionWithApiInfo, isValidationStepTeamAdmin,
ITeamSimple, IUsagePlan
} from '../../../types';
import {
access,
api,
apikey, Can, isPublish, isSubscriptionProcessIsAutomatic, manage,
apikey, Can, isPublish, isSubscriptionProcessIsAutomatic,
Option, queryClient, renderPlanInfo, renderPricing, Spinner
} from '../../utils';
import { ActionWithTeamSelector } from '../../utils/ActionWithTeamSelector';
import { formatPlanType } from '../../utils/formatters';
import classNames from 'classnames';
import { Link, useNavigate } from 'react-router-dom';
import { is } from 'cypress/types/bluebird';
import { useQuery } from '@tanstack/react-query';
import { ApiDocumentation } from './ApiDocumentation';
import { ApiRedoc } from './ApiRedoc';
import { ApiSwagger } from './ApiSwagger';
import { ApiDocumentation } from './ApiDocumentation';

export const currency = (plan?: IBaseUsagePlan) => {
if (!plan) {
Expand Down Expand Up @@ -160,32 +160,6 @@ const ApiPricingCard = (props: ApiPricingCardProps) => {
})
}

const openDocumentationModal = (title: string) => {
openCustomModal({
title,
content: <ApiDocumentation documentation={plan.documentation} getDocPage={(pageId) => Services.getUsagePlanDocPage(props.api._id, props.plan._id, pageId)} />
})
}

const openSwaggerModal = (title: string) => {
openCustomModal({
title,
content: <ApiRedoc swaggerUrl={`/api/teams/${props.api.team}/apis/${props.api._id}/${props.api.currentVersion}/plans/${props.plan._id}/swagger`} />
})
}

const openTestModal = (title: string) => {
openCustomModal({
title,
content: <ApiSwagger _id={plan._id}
testing={plan.testing}
swagger={plan.swagger}
swaggerUrl={`/api/teams/${props.api.team}/apis/${props.api._id}/${props.api.currentVersion}/plans/${props.plan._id}/swagger`}
callUrl={`/api/teams/${props.api.team}/testing/${props.api._id}/plans/${props.plan._id}/call`}
/>
})
}

return (
<div className="col-md-4 card mb-4 shadow-sm usage-plan__card" data-usage-plan={plan.customName}>
<div className="card-img-top card-link card-skin" data-holder-rendered="true">
Expand All @@ -199,9 +173,9 @@ const ApiPricingCard = (props: ApiPricingCardProps) => {
</p>
{tenant.display === 'environment' && (
<div className='flex-shrink-1 d-flex flex-column'>
<button type="button" disabled={!plan.swagger} onClick={() => openSwaggerModal("plan - swagger")} className="btn btn-sm btn-outline-primary mb-1">swagger</button>
<button type="button" disabled={!plan.testing?.enabled} onClick={() => openTestModal('plan - testing')} className="btn btn-sm btn-outline-primary mb-1">test</button>
<button type="button" disabled={!plan.documentation} onClick={() => openDocumentationModal('plan - documentation')} className="btn btn-sm btn-outline-primary">Documentation</button>
<Link to={`./${props.plan.customName}/swagger`} relative='path' className="btn btn-sm btn-outline-primary mb-1">swagger</Link>
<Link to={`./${props.plan.customName}/testing`} relative='path' className="btn btn-sm btn-outline-primary mb-1">test</Link>
<Link to={`./${props.plan.customName}/documentation`} relative='path' className="btn btn-sm btn-outline-primary">Documentation</Link>
</div>
)}
</div>
Expand Down Expand Up @@ -349,6 +323,11 @@ type ApiPricingProps = {
export const ApiPricing = (props: ApiPricingProps) => {
const usagePlansQuery = useQuery(['plans'], () => Services.getVisiblePlans(props.api._id, props.api.currentVersion))

const match = useMatch('/:team/:api/:version/pricing/:env/:tab')

const maybeTab = match?.params.tab
const maybeEnv = match?.params.env

useEffect(() => {
queryClient.invalidateQueries(['plans'])
}, [props.api])
Expand All @@ -364,28 +343,58 @@ export const ApiPricing = (props: ApiPricingProps) => {
props.myTeams.some((team) => plan.authorizedTeams.includes(team._id));
});

return (
<div className="d-flex flex-row pricing-content flex-wrap" id="usage-plans__list">
{possibleUsagePlans
.sort((a, b) => (a.customName || a.type).localeCompare(b.customDescription || b.type))
.map((plan) => <React.Fragment key={plan._id}>
<ApiPricingCard
api={props.api}
key={plan._id}
plan={plan}
myTeams={props.myTeams}
ownerTeam={props.ownerTeam}
subscriptions={props.subscriptions.filter(
(subs) => subs.api === props.api._id && subs.plan === plan._id
)}
inProgressDemands={props.inProgressDemands.filter(
(demand) => demand.api === props.api._id && demand.plan === plan._id
)}
askForApikeys={props.askForApikeys}
/>
</React.Fragment>)}
</div>
);
if (maybeEnv && maybeTab) {
const plan = usagePlansQuery.data.find(p => p.customName === maybeEnv)!
return (
<div>
<div className="d-flex flex-row">
<Link to="../.." relative='path'>
<i className="fa-regular fa-circle-left fa-lg cursor-pointer" />
</Link>
<h5 className='ms-3'>{plan.customName}</h5>
</div>
<div className='d-flex flex-row justify-content-around mb-2'>
<Link to={`../../${plan.customName}/swagger`} relative='path' className="btn btn-sm btn-outline-secondary mb-1">swagger</Link>
<Link to={`../../${plan.customName}/testing`} relative='path' className="btn btn-sm btn-outline-secondary mb-1">test</Link>
<Link to={`../../${plan.customName}/documentation`} relative='path' className="btn btn-sm btn-outline-secondary">Documentation</Link>
</div>
<div>
{maybeTab === 'swagger' && <ApiRedoc swaggerUrl={`/api/teams/${props.api.team}/apis/${props.api._id}/${props.api.currentVersion}/plans/${plan._id}/swagger`} />}
{maybeTab === 'documentation' && <ApiDocumentation documentation={plan.documentation} getDocPage={(pageId) => Services.getUsagePlanDocPage(props.api._id, plan._id, pageId)} />}
{maybeTab === 'testing' && <ApiSwagger _id={plan._id}
testing={plan.testing}
swagger={plan.swagger}
swaggerUrl={`/api/teams/${props.api.team}/apis/${props.api._id}/${props.api.currentVersion}/plans/${plan._id}/swagger`}
callUrl={`/api/teams/${props.api.team}/testing/${props.api._id}/plans/${plan._id}/call`}
/>}
</div>
</div>
)
} else {
return (
<div className="d-flex flex-row pricing-content flex-wrap" id="usage-plans__list">
{possibleUsagePlans
.sort((a, b) => (a.customName || a.type).localeCompare(b.customDescription || b.type))
.map((plan) => <React.Fragment key={plan._id}>
<ApiPricingCard
api={props.api}
key={plan._id}
plan={plan}
myTeams={props.myTeams}
ownerTeam={props.ownerTeam}
subscriptions={props.subscriptions.filter(
(subs) => subs.api === props.api._id && subs.plan === plan._id
)}
inProgressDemands={props.inProgressDemands.filter(
(demand) => demand.api === props.api._id && demand.plan === plan._id
)}
askForApikeys={props.askForApikeys}
/>
</React.Fragment>)}
</div>
);
}

} else {
return <div></div>
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,6 @@ export const FastApiCard = (props: FastApiCardProps) => {
: Services.askForApiKey(apiId, team._id, plan._id, motivation)

const adminStep = plan.subscriptionProcess.find(s => isValidationStepTeamAdmin(s))
console.debug({adminStep, plan})
if (adminStep && isValidationStepTeamAdmin(adminStep)) {
openFormModal<{ motivation: string }>({
title: translate('motivations.modal.title'),
Expand Down
30 changes: 17 additions & 13 deletions daikoku/javascript/src/components/utils/Option.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,22 @@
// export interface TOption<A> {
// map<B>(f: (a: A) => B): TOption<B>;
// flatMap<B>(f: (a: A) => TOption<B>): TOption<B>;
// fold<B>(ifEmpty: B, f: (a: A) => B): B;
// orElse<B extends A>(ob: TOption<B>): TOption<A>;
// getOrElse<B extends A>(a: B): A;
// getOrNull(): A | undefined
// isDefined: boolean;
// exists(f: (a: A) => boolean): boolean;
// filter(f: (a: A) => boolean): TOption<A>;
// }
interface ISome<A> extends IOption<A> {}
interface INone extends IOption<undefined> {}

export const Option = (x) => (x === undefined || x === null ? None : Some(x));
export interface IOption<A> {
map<B>(f: (a: A) => B): ISome<B> | INone;
flatMap<B>(f: (a: A) => IOption<B>): IOption<B> | INone;
fold<B>(ifEmpty: B, f: (a: A) => B): B;
orElse<B>(ob: IOption<B>): IOption<A> | IOption<B> | INone;
getOrElse<B>(b: B): A | B;
getOrNull(): A | undefined
isDefined: boolean;
exists(f: (a: A) => boolean): boolean;
filter(f: (a: A) => boolean): IOption<A> | INone;
}

export const Some = (x) => ({

export const Option = <T>(x: T) => (x === undefined || x === null ? None : Some(x));

export const Some = <T>(x: T) => ({
map: (f) => Option(f(x)),
flatMap: (f) => f(x),
fold: (_ifEmpty, f) => f(x),
Expand Down

0 comments on commit 9c16e90

Please sign in to comment.