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

344 add h5p #383

Merged
merged 10 commits into from
Aug 17, 2022
19 changes: 10 additions & 9 deletions .github/workflows/cdelivery-s3-caller.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,23 +9,24 @@ on:
jobs:
graasp-deploy-s3-workflow:
name: Graasp Builder
uses: graasp/graasp-deploy/.github/workflows/cdelivery-s3.yml@bea834ec77096ee815ec190d28d7e2cf7d94ada1
uses: graasp/graasp-deploy/.github/workflows/cdelivery-s3.yml@1a7c4c74273be6fd1c56eb6c5a8fb4af2d211b86
with:
build-folder: 'build'
tag: ${{ github.event.client_payload.tag }}
secrets:
api-host: ${{ secrets.REACT_APP_API_HOST_STAGE }}
authentication-host: ${{ secrets.REACT_APP_AUTHENTICATION_HOST_STAGE }}
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID_STAGE }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY_STAGE }}
aws-region: ${{ secrets.AWS_REGION_STAGE }}
aws-s3-bucket-name: ${{ secrets.AWS_S3_BUCKET_NAME_GRAASP_COMPOSE_STAGE }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY_STAGE }}
cloudfront-distribution-id: ${{ secrets.CLOUDFRONT_DISTRIBUTION_GRAASP_COMPOSE_STAGE }}
api-host: ${{ secrets.REACT_APP_API_HOST_STAGE }}
show-notifications: ${{ secrets.REACT_APP_SHOW_NOTIFICATIONS }}
authentication-host: ${{ secrets.REACT_APP_AUTHENTICATION_HOST_STAGE }}
graasp-perform-host: ${{ secrets.PLAYER_CLIENT_HOST_STAGE }}
graasp-explorer-host: ${{ secrets.EXPLORER_CLIENT_HOST_STAGE }}
graasp-analyzer-host: ${{ secrets.ANALYZER_CLIENT_HOST_STAGE }}
domain: ${{ secrets.REACT_APP_DOMAIN_STAGE }}
ga-measurement-id: ${{ secrets.REACT_APP_GA_MEASUREMENT_ID_STAGE }}
graasp-analyzer-host: ${{ secrets.ANALYZER_CLIENT_HOST_STAGE }}
graasp-explorer-host: ${{ secrets.EXPLORER_CLIENT_HOST_STAGE }}
graasp-perform-host: ${{ secrets.PLAYER_CLIENT_HOST_STAGE }}
h5p-integration-url: ${{ secrets.H5P_INTEGRATION_URL_STAGE }}
hidden-item-tag-id: ${{ secrets.REACT_APP_HIDDEN_ITEM_TAG_ID_STAGE }}
domain: ${{ secrets.REACT_APP_DOMAIN_STAGE }}
sentry-dsn: ${{ secrets.REACT_APP_SENTRY_DSN }}
show-notifications: ${{ secrets.REACT_APP_SHOW_NOTIFICATIONS }}
21 changes: 11 additions & 10 deletions .github/workflows/cdeployment-s3-caller.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,25 +13,26 @@ jobs:
name: Graasp Builder
# Replace 'main' with the hash of a commit, so it points to an specific version of the reusable workflow that is used
# Reference reusable workflow file. Using the commit SHA is the safest for stability and security
uses: graasp/graasp-deploy/.github/workflows/cdeployment-s3.yml@bea834ec77096ee815ec190d28d7e2cf7d94ada1
# Replace input build-folder if needed.
uses: graasp/graasp-deploy/.github/workflows/cdeployment-s3.yml@1a7c4c74273be6fd1c56eb6c5a8fb4af2d211b86
# Replace input build-folder if needed.
with:
build-folder: 'build'
tag: ${{ github.event.client_payload.tag }}
# Insert required secrets based on repository with the following format: ${{ secrets.SECRET_NAME }}
secrets:
api-host: ${{ secrets.REACT_APP_API_HOST_PROD }}
authentication-host: ${{ secrets.REACT_APP_AUTHENTICATION_HOST_PROD }}
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID_PROD }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY_PROD }}
aws-region: ${{ secrets.AWS_REGION_PROD }}
aws-s3-bucket-name: ${{ secrets.AWS_S3_BUCKET_NAME_GRAASP_COMPOSE_PROD }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY_PROD }}
cloudfront-distribution-id: ${{ secrets.CLOUDFRONT_DISTRIBUTION_GRAASP_COMPOSE_PROD }}
api-host: ${{ secrets.REACT_APP_API_HOST_PROD }}
show-notifications: ${{ secrets.REACT_APP_SHOW_NOTIFICATIONS }}
authentication-host: ${{ secrets.REACT_APP_AUTHENTICATION_HOST_PROD }}
graasp-perform-host: ${{ secrets.PLAYER_CLIENT_HOST_PROD }}
graasp-explorer-host: ${{ secrets.EXPLORER_CLIENT_HOST_PROD }}
graasp-analyzer-host: ${{ secrets.ANALYZER_CLIENT_HOST_PROD }}
domain: ${{ secrets.REACT_APP_DOMAIN_PROD }}
ga-measurement-id: ${{ secrets.REACT_APP_GA_MEASUREMENT_ID_PROD }}
graasp-analyzer-host: ${{ secrets.ANALYZER_CLIENT_HOST_PROD }}
graasp-explorer-host: ${{ secrets.EXPLORER_CLIENT_HOST_PROD }}
graasp-perform-host: ${{ secrets.PLAYER_CLIENT_HOST_PROD }}
h5p-integration-url: ${{ secrets.H5P_INTEGRATION_URL_PROD }}
hidden-item-tag-id: ${{ secrets.REACT_APP_HIDDEN_ITEM_TAG_ID_PROD }}
domain: ${{ secrets.REACT_APP_DOMAIN_PROD }}
sentry-dsn: ${{ secrets.REACT_APP_SENTRY_DSN }}
show-notifications: ${{ secrets.REACT_APP_SHOW_NOTIFICATIONS }}
19 changes: 10 additions & 9 deletions .github/workflows/cintegration-s3-caller.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,22 +16,23 @@ jobs:
graasp-deploy-s3-workflow:
name: Graasp Builder
# Reference reusable workflow file. Using the commit SHA is the safest for stability and security
uses: graasp/graasp-deploy/.github/workflows/cintegration-s3.yml@bea834ec77096ee815ec190d28d7e2cf7d94ada1
uses: graasp/graasp-deploy/.github/workflows/cintegration-s3.yml@1a7c4c74273be6fd1c56eb6c5a8fb4af2d211b86
with:
build-folder: 'build'
secrets:
api-host: ${{ secrets.REACT_APP_API_HOST_DEV }}
authentication-host: ${{ secrets.REACT_APP_AUTHENTICATION_HOST_DEV }}
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID_DEV }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY_DEV }}
aws-region: ${{ secrets.AWS_REGION_DEV }}
aws-s3-bucket-name: ${{ secrets.AWS_S3_BUCKET_NAME_GRAASP_COMPOSE_DEV }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY_DEV }}
cloudfront-distribution-id: ${{ secrets.CLOUDFRONT_DISTRIBUTION_GRAASP_COMPOSE_DEV }}
api-host: ${{ secrets.REACT_APP_API_HOST_DEV }}
show-notifications: ${{ secrets.REACT_APP_SHOW_NOTIFICATIONS }}
authentication-host: ${{ secrets.REACT_APP_AUTHENTICATION_HOST_DEV }}
graasp-perform-host: ${{ secrets.PLAYER_CLIENT_HOST_DEV }}
graasp-explorer-host: ${{ secrets.EXPLORER_CLIENT_HOST_DEV }}
graasp-analyzer-host: ${{ secrets.ANALYZER_CLIENT_HOST_DEV }}
domain: ${{ secrets.REACT_APP_DOMAIN_DEV }}
ga-measurement-id: ${{ secrets.REACT_APP_GA_MEASUREMENT_ID_DEV }}
graasp-analyzer-host: ${{ secrets.ANALYZER_CLIENT_HOST_DEV }}
graasp-explorer-host: ${{ secrets.EXPLORER_CLIENT_HOST_DEV }}
graasp-perform-host: ${{ secrets.PLAYER_CLIENT_HOST_DEV }}
h5p-integration-url: ${{ secrets.H5P_INTEGRATION_URL_DEV }}
hidden-item-tag-id: ${{ secrets.REACT_APP_HIDDEN_ITEM_TAG_ID_DEV }}
domain: ${{ secrets.REACT_APP_DOMAIN_DEV }}
sentry-dsn: ${{ secrets.REACT_APP_SENTRY_DSN }}
show-notifications: ${{ secrets.REACT_APP_SHOW_NOTIFICATIONS }}
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ REACT_APP_API_HOST=http://localhost:3000
PORT=3111
REACT_APP_SHOW_NOTIFICATIONS=true
REACT_APP_AUTHENTICATION_HOST=http://localhost:3001
REACT_APP_H5P_INTEGRATION_URL=
```

4. Run `yarn start`. The client should be accessible at `localhost:3111`
Expand All @@ -36,6 +37,7 @@ REACT_APP_API_HOST=http://localhost:3000
PORT=3111
REACT_APP_SHOW_NOTIFICATIONS=false
REACT_APP_NODE_ENV=test
REACT_APP_H5P_INTEGRATION_URL=
```

Run `yarn cypress`. This should run every tests headlessly.
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@
"@commitlint/config-conventional": "16.2.1",
"@cypress/code-coverage": "3.9.12",
"@cypress/instrument-cra": "1.4.0",
"@graasp/websockets": "github:graasp/graasp-websockets.git#master",
"@graasp/websockets": "github:graasp/graasp-websockets.git",
"@testing-library/jest-dom": "^5.16.3",
"@testing-library/react": "^12.1.4",
"@testing-library/user-event": "^13.5.0",
Expand Down
43 changes: 29 additions & 14 deletions src/components/item/ItemContent.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,15 @@
import React, { useContext } from 'react';
import PropTypes from 'prop-types';
import { Record } from 'immutable';
import { Api, MUTATION_KEYS } from '@graasp/query-client';
import { AppItem, DocumentItem, FileItem, H5PItem, LinkItem } from '@graasp/ui';
import { makeStyles } from '@material-ui/core';
import { FileItem, DocumentItem, LinkItem, AppItem } from '@graasp/ui';
import { MUTATION_KEYS, Api } from '@graasp/query-client';
import { Record } from 'immutable';
import PropTypes from 'prop-types';
import React, { useContext } from 'react';
import {
API_HOST,
CONTEXT_BUILDER,
H5P_INTEGRATION_URL,
ITEM_DEFAULT_HEIGHT,
} from '../../config/constants';
import { hooks, useMutation } from '../../config/queryClient';
import {
buildFileItemId,
Expand All @@ -13,19 +19,14 @@ import {
ITEM_SCREEN_ERROR_ALERT_ID,
} from '../../config/selectors';
import { ITEM_KEYS, ITEM_TYPES } from '../../enums';
import Loader from '../common/Loader';
import { buildDocumentExtra, getDocumentExtra } from '../../utils/itemExtra';
import ErrorAlert from '../common/ErrorAlert';
import {
API_HOST,
ITEM_DEFAULT_HEIGHT,
CONTEXT_BUILDER,
} from '../../config/constants';
import Loader from '../common/Loader';
import { CurrentUserContext } from '../context/CurrentUserContext';
import { LayoutContext } from '../context/LayoutContext';
import ItemActions from '../main/ItemActions';
import Items from '../main/Items';
import { buildDocumentExtra, getDocumentExtra } from '../../utils/itemExtra';
import NewItemButton from '../main/NewItemButton';
import { CurrentUserContext } from '../context/CurrentUserContext';
import ItemActions from '../main/ItemActions';

const { useChildren, useFileContent } = hooks;

Expand Down Expand Up @@ -173,6 +174,20 @@ const ItemContent = ({ item, enableEditing, permission }) => {
/>
</>
);
case ITEM_TYPES.H5P: {
const contentId = item.get('extra')?.h5p?.contentId;
if (!contentId) {
return <ErrorAlert id={ITEM_SCREEN_ERROR_ALERT_ID} />;
}

return (
<H5PItem
itemId={itemId}
contentId={contentId}
integrationUrl={H5P_INTEGRATION_URL}
/>
);
}

default:
return <ErrorAlert id={ITEM_SCREEN_ERROR_ALERT_ID} />;
Expand Down
99 changes: 99 additions & 0 deletions src/components/main/ImportH5P.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
import { routines } from '@graasp/query-client';
import Typography from '@material-ui/core/Typography';
import '@uppy/dashboard/dist/style.css';
import { Dashboard } from '@uppy/react';
import React, { useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { useMatch } from 'react-router';
import notifier from '../../config/notifier';
import { buildItemPath } from '../../config/paths';
import { H5P_DASHBOARD_UPLOADER_ID } from '../../config/selectors';
import { configureH5PImportUppy } from '../../utils/uppy';

const ImportH5P = () => {
const [uppy, setUppy] = useState(null);
const match = useMatch(buildItemPath());
const itemId = match?.params?.itemId;
const { t } = useTranslation();

const onComplete = (result) => {
// update app on complete
// todo: improve with websockets or by receiving corresponding items
if (!result?.failed.length) {
notifier({ type: routines.importH5PRoutine.SUCCESS });
}
};
const onError = (error) => {
notifier({ type: routines.importH5PRoutine.FAILURE, payload: { error } });
};

const onUpload = () => {
notifier({ type: routines.importH5PRoutine.REQUEST });
};

const applyUppy = () =>
setUppy(
configureH5PImportUppy({
itemId,
onComplete,
onError,
onUpload,
}),
);

useEffect(() => {
applyUppy();

return () => {
uppy?.close();
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);

useEffect(() => {
applyUppy();
// update uppy configuration each time itemId changes
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [itemId]);

if (!uppy) {
return null;
}

return (
<>
<Typography variant="h6">{t('Import H5P rich content')}</Typography>
<Typography variant="body">
{t(
'You can upload H5P rich content by uploading exported .h5p files (e.g. from H5P.com, external Moodle services, etc).',
)}
</Typography>
<br />
<Typography variant="body">
{t(
'Once your file is accepted, it will take several minutes for it to be available.',
)}
</Typography>
<div id={H5P_DASHBOARD_UPLOADER_ID}>
<Dashboard
uppy={uppy}
height={200}
width="100%"
proudlyDisplayPoweredByUppy={false}
note={t('Upload a file')}
locale={{
strings: {
// Text to show on the droppable area.
// `%{browse}` is replaced with a link that opens the system file selection dialog.
dropPaste: `${t('Drop here or')} %{browse}`,
// Used as the label for the link that opens the system file selection dialog.
browse: t('Browse'),
},
}}
/>
</div>
</>
);
};

export default ImportH5P;
8 changes: 8 additions & 0 deletions src/components/main/ItemTypeTabs.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
CREATE_ITEM_DOCUMENT_ID,
CREATE_ITEM_APP_ID,
CREATE_ITEM_ZIP_ID,
CREATE_ITEM_H5P_ID,
} from '../../config/selectors';

const useStyles = makeStyles((theme) => ({
Expand Down Expand Up @@ -97,6 +98,13 @@ const ItemTypeTabs = ({ onTypeChange, initialValue }) => {
icon={zipIcon}
classes={{ wrapper: classes.wrapper }}
/>
<Tab
id={CREATE_ITEM_H5P_ID}
value={ITEM_TYPES.H5P}
label={t('Import H5P')}
icon={<ItemIcon type={ITEM_TYPES.H5P} iconClass={classes.icon} />}
classes={{ wrapper: classes.wrapper }}
/>
</Tabs>
);
};
Expand Down
4 changes: 4 additions & 0 deletions src/components/main/NewItemModal.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import AppForm from '../item/form/AppForm';
import ItemTypeTabs from './ItemTypeTabs';
import ImportZip from './ImportZip';
import { DOUBLE_CLICK_DELAY_MS } from '../../config/constants';
import ImportH5P from './ImportH5P';

const useStyles = makeStyles((theme) => ({
dialogContent: {
Expand Down Expand Up @@ -104,6 +105,8 @@ const NewItemModal = ({ open, handleClose }) => {
return <FileDashboardUploader />;
case ITEM_TYPES.ZIP:
return <ImportZip />;
case ITEM_TYPES.H5P:
return <ImportH5P />;
case ITEM_TYPES.APP:
return (
<AppForm
Expand Down Expand Up @@ -158,6 +161,7 @@ const NewItemModal = ({ open, handleClose }) => {
);
case ITEM_TYPES.FILE:
case ITEM_TYPES.ZIP:
case ITEM_TYPES.H5P:
return (
<Button id={CREATE_ITEM_CLOSE_BUTTON_ID} onClick={handleClose}>
{t('Close')}
Expand Down
12 changes: 10 additions & 2 deletions src/config/constants.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Context, buildSignInPath } from '@graasp/utils';
import { buildSignInPath, Context } from '@graasp/utils';
import { ITEM_LAYOUT_MODES, ITEM_TYPES, PERMISSION_LEVELS } from '../enums';
import env from '../env.json';
import { ITEM_LAYOUT_MODES, PERMISSION_LEVELS, ITEM_TYPES } from '../enums';

const {
API_HOST: ENV_API_HOST,
Expand All @@ -11,6 +11,7 @@ const {
GA_MEASUREMENT_ID: ENV_GA_MEASUREMENT_ID,
HIDDEN_ITEM_TAG_ID: ENV_HIDDEN_ITEM_TAG_ID,
GRAASP_EXPLORE_HOST: ENV_GRAASP_EXPLORE_HOST,
H5P_INTEGRATION_URL: ENV_H5P_INTEGRATION_URL,
REACT_APP_SENTRY_DSN: ENV_SENTRY_DSN,
DOMAIN: ENV_DOMAIN,
} = env;
Expand Down Expand Up @@ -53,6 +54,11 @@ export const GRAASP_LIBRARY_HOST =
process.env.REACT_APP_GRAASP_EXPLORE_HOST ||
'http://localhost:3005';

export const H5P_INTEGRATION_URL =
ENV_H5P_INTEGRATION_URL ||
process.env.REACT_APP_H5P_INTEGRATION_URL ||
`${API_HOST}/p/h5p-integration`;

export const GRAASP_ANALYZER_HOST =
process.env.REACT_APP_GRAASP_ANALYZER_HOST || 'http://localhost:3113';

Expand Down Expand Up @@ -190,6 +196,8 @@ export const THUMBNAIL_EXTENSION = 'image/jpeg';
export const THUMBNAIL_SETTING_MAX_WIDTH = 300;
export const THUMBNAIL_SETTING_MAX_HEIGHT = 200;

export const H5P_FILE_DOT_EXTENSION = '.h5p';

export const CATEGORY_TYPES = {
LEVEL: 'level',
DISCIPLINE: 'discipline',
Expand Down
2 changes: 2 additions & 0 deletions src/config/messages.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,8 @@ export const IMPORT_ZIP_FAILURE_MESSAGE =
'An error occurred while importing The ZIP archive.';
export const IMPORT_ZIP_PROGRESS_MESSAGE =
'The ZIP is being processed. Please wait a moment.';
export const IMPORT_H5P_PROGRESS_MESSAGE =
'The H5P is being processed. Please wait a moment.';
export const EXPORT_ZIP_FAILURE_MESSAGE =
'An error occurred while downloading the item as ZIP archive. Please try again later.';

Expand Down
Loading