Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat/tutorial provider #451

Merged
merged 38 commits into from
Feb 21, 2024
Merged
Show file tree
Hide file tree
Changes from 31 commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
4fd46af
:see_no_evil: added .idea/workspace.xmp to gitignore
aslakihle Jan 12, 2024
5003a1d
:construction: Tutorial provider, highlighting default dialog position
aslakihle Jan 16, 2024
1f36c40
:construction: Dynamic positioning based on overlaps
aslakihle Jan 17, 2024
e96dc42
:construction: Starting tutorials with params from URL
aslakihle Jan 22, 2024
84e9ce3
Merge branch 'main' into feat/tutorialProvider
aslakihle Jan 22, 2024
db5bfe0
:sparkles: Url starting from Params working
aslakihle Jan 23, 2024
54a7dbc
Merge branch 'main' into feat/tutorialProvider
aslakihle Jan 23, 2024
d915fc6
:construction: Mocking hook in story
aslakihle Jan 23, 2024
7b41ab4
:sparkles: Added image handling, and fixed story
aslakihle Jan 24, 2024
2f031ee
:adhesive_bandage: Removed necessary interfaces for env variables
aslakihle Jan 24, 2024
762858b
:goal_net: Handle tutorial being broken
aslakihle Jan 25, 2024
8f119cb
:adhesive_bandage: Changed wording on console.error
aslakihle Jan 26, 2024
8599ec0
:adhesive_bandage: Scroll to view and handle viewport resize
aslakihle Jan 29, 2024
1d13981
:fire: Removed comment
aslakihle Jan 29, 2024
d099a3d
:white_check_mark: (WIP) TutorialProvider tests
aslakihle Jan 30, 2024
49a347b
:bug: Fixed error handling
aslakihle Jan 31, 2024
ce8da60
:white_check_mark: Tests readyish before final changes connecting to …
aslakihle Feb 2, 2024
cc6d69e
:alien: Added TutorialService to api folder
aslakihle Feb 5, 2024
674f7f1
:construction: Including backend
aslakihle Feb 5, 2024
bef17cb
:sparkles: added backend call and handled new booleans
aslakihle Feb 5, 2024
a253ce4
:sparkles: Backend call for sastoken for image + minor stuff
aslakihle Feb 16, 2024
da37f05
Merge branch 'main' into feat/tutorialProvider
aslakihle Feb 16, 2024
d197ff3
:construction: Fixing tests
aslakihle Feb 16, 2024
78e9861
Merge branch 'main' into feat/tutorialProvider
aslakihle Feb 16, 2024
248fb6a
Merge branch 'main' into feat/tutorialProvider
aslakihle Feb 16, 2024
93a5d6f
:bug: Fixed story
aslakihle Feb 16, 2024
e7a8957
:white_check_mark: Final coverage tweaks
aslakihle Feb 20, 2024
43dd6d3
Merge branch 'main' into feat/tutorialProvider
aslakihle Feb 20, 2024
a1a6c7f
:bookmark: 6.3.0
aslakihle Feb 20, 2024
ca2634a
:alien: Changed tutorial services to always use Portal Prod connection
aslakihle Feb 20, 2024
c75ff03
:adhesive_bandage: Minor fixes
aslakihle Feb 20, 2024
6b5b6fa
:memo: Fixed story to be in a separate file from other providers
aslakihle Feb 20, 2024
a437552
:recycle: Improved structure of the stories file abit
aslakihle Feb 20, 2024
7cbaae4
:adhesive_bandage: Changed button on tutorial story
aslakihle Feb 20, 2024
7b2b66b
:adhesive_bandage: Minor fix to TutorialProvider story
aslakihle Feb 21, 2024
cde5382
:fire: Removed story file from vitest ignore
aslakihle Feb 21, 2024
50627e6
:recycle: Moved type exporting to types folder
aslakihle Feb 21, 2024
2e413fc
:recycle: Fixed circular dependencies
aslakihle Feb 21, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@
# testing
/coverage

# Webstorm user specific settings
.idea/workspace.xml

# production
/build
/storybook-static
Expand Down
13 changes: 13 additions & 0 deletions .idea/runConfigurations/build_and_use_recap.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@equinor/amplify-components",
"version": "6.2.7",
"version": "6.3.0",
"description": "Frontend Typescript components for the Amplify team",
"main": "dist/esm/index.js",
"types": "dist/types/index.d.ts",
Expand Down
4 changes: 4 additions & 0 deletions src/api/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,12 @@ export { request } from './core/request';
export type { OpenAPIConfig } from './core/OpenAPI';

export type { Feature } from './models/Feature';
export type { CustomTutorialStep } from './models/CustomTutorialStep';
export type { GenericTutorialStep } from './models/GenericTutorialStep';
export type { Tutorial } from './models/Tutorial';
export type { FeatureToggleDto } from './models/FeatureToggleDto';
export type { GraphUser } from './models/GraphUser';
export type { ServiceNowIncidentResponse } from './models/ServiceNowIncidentResponse';
export type { ServiceNowIncidentAttachmentResponse } from './models/ServiceNowIncidentAttachmentResponse';
export { TutorialPosition } from './models/TutorialPosition';
export { ServiceNowUrgency } from './models/ServiceNowUrgency';
10 changes: 10 additions & 0 deletions src/api/models/CustomTutorialStep.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
/* generated using openapi-typescript-codegen -- do no edit */
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */

import { TutorialStepBase } from './TutorialStepBase';

export type CustomTutorialStep = TutorialStepBase & {
key: string;
};
13 changes: 13 additions & 0 deletions src/api/models/GenericTutorialStep.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
/* generated using openapi-typescript-codegen -- do no edit */
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */

import { TutorialStepBase } from './TutorialStepBase';

export type GenericTutorialStep = TutorialStepBase & {
title: string;
body: string;
key?: undefined;
imgUrl?: string;
};
19 changes: 19 additions & 0 deletions src/api/models/Tutorial.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
/* generated using openapi-typescript-codegen -- do no edit */
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */

import { CustomTutorialStep } from './CustomTutorialStep';
import { GenericTutorialStep } from './GenericTutorialStep';

export type Tutorial = {
id: string;
name: string;
shortName: string;
path: string;
application: string;
steps: Array<CustomTutorialStep | GenericTutorialStep>;
showInProd: boolean;
willPopUp: boolean;
dynamicPositioning?: boolean | null;
};
12 changes: 12 additions & 0 deletions src/api/models/TutorialPosition.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
/* generated using openapi-typescript-codegen -- do no edit */
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */

export enum TutorialPosition {
TOP_LEFT = 'TOP_LEFT',
TOP_RIGHT = 'TOP_RIGHT',
BOTTOM_LEFT = 'BOTTOM_LEFT',
BOTTOM_RIGHT = 'BOTTOM_RIGHT',
CENTER = 'CENTER',
}
10 changes: 10 additions & 0 deletions src/api/models/TutorialStepBase.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
/* generated using openapi-typescript-codegen -- do no edit */
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */

import type { TutorialPosition } from './TutorialPosition';

export type TutorialStepBase = {
position?: TutorialPosition;
};
36 changes: 36 additions & 0 deletions src/api/services/TutorialService.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/* generated using openapi-typescript-codegen -- do no edit */
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
import type { Tutorial } from '../models/Tutorial';

import type { CancelablePromise } from '../core/CancelablePromise';
import { OpenAPI_Portal_Prod } from '../core/OpenAPI';
import { request as __request } from '../core/request';

export class TutorialService {
/**
* Gets all tutorials for Application
* @param applicationName
* @returns Tutorial Success
* @throws ApiError
*/
public static getTutorialsForApplication(
applicationName: string
): CancelablePromise<Array<Tutorial>> {
return __request(OpenAPI_Portal_Prod, {
method: 'GET',
url: '/api/v1/Tutorial/{applicationName}',
path: {
applicationName: applicationName,
},
});
}

public static getTutorialSasToken(): CancelablePromise<string> {
return __request(OpenAPI_Portal_Prod, {
method: 'GET',
url: '/api/v1/Tutorial/SASToken',
});
}
}
6 changes: 5 additions & 1 deletion src/components/DataDisplay/Tutorial/Tutorial.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,11 @@ export type TutorialProps = {
imageSource?: string;
tutorialIntro: string;
};

/**
* @deprecated since version 6.3.0. We use TutorialProvider instead, and add tutorials to a database on amplify-portal.
aslakihle marked this conversation as resolved.
Show resolved Hide resolved
* You can read a small guide in the front-end docs on the JS devops (JS devops -> Overview -> Wiki -> Documentation ->
* Front-end docs -> Guides -> Creating tutorial for TutorialProvider)
*/
const Tutorial = forwardRef<HTMLDivElement, TutorialProps>(
({ steps, tutorialTitle, tutorialIntro, imageSource }, ref) => {
const { showTutorialIntro, setTutorialStep, setShowTutorialIntro } =
Expand Down
183 changes: 183 additions & 0 deletions src/providers/TutorialProvider/TutorialDialog.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
import { CSSProperties, FC, useMemo } from 'react';

import { Button, Typography } from '@equinor/eds-core-react';

import { useTutorial } from './TutorialProvider';
import {
DIALOG_EDGE_MARGIN,
TUTORIAL_LOCALSTORAGE_VALUE_STRING,
} from './TutorialProvider.const';
import { useGetTutorialSasToken } from './TutorialProvider.hooks';
import {
DialogActions,
DialogContent,
DialogImage,
DialogWrapper,
NavigateSteps,
StyledTutorialDialog,
} from './TutorialProvider.styles';
import { getBestPositionWithoutOverlap } from './TutorialProvider.utils';
import TutorialStepIndicator from './TutorialStepIndicator';
import { TutorialPosition } from 'src/api';

const TutorialDialog: FC = () => {
const { data: sasToken } = useGetTutorialSasToken();

const {
activeTutorial,
currentStep,
setCurrentStep,
setActiveTutorial,
dialogRef,
allElementsToHighlight,
customStepComponents,
isLastStep,
currentStepObject,
setAllElementsToHighlight,
shortNameFromParams,
viewportWidth,
clearSearchParam,
} = useTutorial();

const dialogContent = useMemo(() => {
if (!currentStepObject) return;
if (currentStepObject.key && customStepComponents) {
return customStepComponents.find(
(step) => step.key === currentStepObject.key
)?.element;
} else if (
currentStepObject.key === undefined ||
currentStepObject.key === null
) {
return (
<>
<Typography>{currentStepObject.title}</Typography>
<Typography>{currentStepObject.body}</Typography>
{currentStepObject.imgUrl && sasToken && (
<DialogImage
data-testid="tutorial-image"
alt="tutorial-image"
src={`${currentStepObject.imgUrl}?${sasToken}`}
/>
)}
</>
);
}
}, [currentStepObject, customStepComponents, sasToken]);

const dialogPosition: TutorialPosition | undefined = useMemo(() => {
if (
!activeTutorial ||
!viewportWidth ||
!allElementsToHighlight ||
!dialogRef.current
)
return;
if (activeTutorial.steps[currentStep].position)
return activeTutorial.steps[currentStep].position;
if (activeTutorial.dynamicPositioning) {
return getBestPositionWithoutOverlap(
allElementsToHighlight[currentStep].getBoundingClientRect(),
dialogRef.current.getBoundingClientRect()
);
}
return TutorialPosition.BOTTOM_RIGHT;
}, [
activeTutorial,
viewportWidth,
allElementsToHighlight,
dialogRef,
currentStep,
]);

const dialogPositionStyle: CSSProperties | undefined = useMemo(() => {
if (!dialogPosition || dialogPosition === TutorialPosition.CENTER) return;
switch (dialogPosition as TutorialPosition) {
case TutorialPosition.TOP_LEFT:
return {
marginTop: `${DIALOG_EDGE_MARGIN}px`,
marginLeft: `${DIALOG_EDGE_MARGIN}px`,
};
case TutorialPosition.TOP_RIGHT:
return {
marginTop: `${DIALOG_EDGE_MARGIN}px`,
marginRight: `${DIALOG_EDGE_MARGIN}px`,
};
case TutorialPosition.BOTTOM_LEFT:
return {
marginBottom: `${DIALOG_EDGE_MARGIN}px`,
marginLeft: `${DIALOG_EDGE_MARGIN}px`,
};
case TutorialPosition.BOTTOM_RIGHT:
default:
return {
marginBottom: `${DIALOG_EDGE_MARGIN}px`,
marginRight: `${DIALOG_EDGE_MARGIN}px`,
};
}
}, [dialogPosition]);

const stopTutorial = () => {
if (shortNameFromParams) clearSearchParam();
if (activeTutorial) {
window.localStorage.setItem(
activeTutorial?.shortName,
TUTORIAL_LOCALSTORAGE_VALUE_STRING
);
setActiveTutorial(undefined);
dialogRef.current?.close();
setCurrentStep(0);
setAllElementsToHighlight(undefined);
}
};
const handleOnSkip = () => {
stopTutorial();
};

const handleOnPrev = () => {
setCurrentStep((prev) => prev - 1);
};

const handleOnNext = () => {
if (isLastStep) {
stopTutorial();
return;
}
setCurrentStep((prev) => prev + 1);
};

return (
<DialogWrapper>
<StyledTutorialDialog
data-testid="tutorial-dialog"
ref={dialogRef}
style={dialogPositionStyle ?? undefined}
>
<DialogContent>
{dialogContent}
<TutorialStepIndicator
steps={activeTutorial?.steps ?? []}
currentStep={currentStep}
/>
<DialogActions>
<Button variant="ghost" onClick={handleOnSkip}>
Skip
</Button>
<NavigateSteps>
{currentStep && currentStep !== 0 ? (
<Button variant="ghost" onClick={handleOnPrev}>
Previous
</Button>
) : null}
<Button variant="outlined" onClick={handleOnNext}>
{isLastStep ? 'Done' : 'Next'}
</Button>
</NavigateSteps>
</DialogActions>
</DialogContent>
</StyledTutorialDialog>
</DialogWrapper>
);
};

export default TutorialDialog;
8 changes: 8 additions & 0 deletions src/providers/TutorialProvider/TutorialProvider.const.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
export const HIGHLIGHT_PADDING = 8;
export const DIALOG_EDGE_MARGIN = 24;
export const TUTORIAL_SEARCH_PARAM_KEY = 'tutorial';
export const TUTORIAL_LOCALSTORAGE_VALUE_STRING = 'hasCompletedTutorial';
export const TUTORIAL_HIGHLIGHTER_DATATEST_ID = 'tutorial-highlighter';

export const GET_TUTORIALS_FOR_APP = 'getTutorialsForApp';
export const GET_TUTORIALS_SAS_TOKEN = 'getTutorialsSasToken';
24 changes: 24 additions & 0 deletions src/providers/TutorialProvider/TutorialProvider.hooks.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { useQuery } from '@tanstack/react-query';

import {
GET_TUTORIALS_FOR_APP,
GET_TUTORIALS_SAS_TOKEN,
} from './TutorialProvider.const';
import { TutorialService } from 'src/api/services/TutorialService';
import { environment } from 'src/utils';

const { getAppName } = environment;

export const useGetTutorialsForApp = () => {
const appName = getAppName(import.meta.env.VITE_NAME);
return useQuery({
queryKey: [GET_TUTORIALS_FOR_APP, appName],
queryFn: () => TutorialService.getTutorialsForApplication(appName),
});
};
export const useGetTutorialSasToken = () => {
return useQuery({
queryKey: [GET_TUTORIALS_SAS_TOKEN],
queryFn: () => TutorialService.getTutorialSasToken(),
});
};
Loading
Loading