Skip to content

Commit

Permalink
Merge pull request #1458 from thehyve/infra/FAIRSPC-23_dev_temp
Browse files Browse the repository at this point in the history
Merge changes from dev
  • Loading branch information
tgreenwood authored Jan 4, 2024
2 parents 139175f + 91ff047 commit cc2c960
Show file tree
Hide file tree
Showing 6 changed files with 122 additions and 92 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/build_and_upload_on_push_to_dev.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ env:
on:
push:
branches:
- "dev"
- "infra/FAIRSPC-23_github_actions_CI"

jobs:
# A job to generate one shared unique version tag per build cycle for all built artifacts
Expand Down
53 changes: 35 additions & 18 deletions projects/mercury/src/metadata/common/LinkedDataLink.js
Original file line number Diff line number Diff line change
@@ -1,28 +1,45 @@
import React, {useContext} from 'react';
import {Link as RouterLink} from "react-router-dom";
import * as PropTypes from "prop-types";
import {Link} from '@mui/material';
import {METADATA_PATH} from "../../constants";
import {Box, Modal, Tooltip} from '@mui/material';
import withStyles from '@mui/styles/withStyles';
import CloseIcon from '@mui/icons-material/Close';
import LinkedDataEntityPage from "./LinkedDataEntityPage";
import UserContext from '../../users/UserContext';

import styles from './LinkedDataLink.styles';
/**
* Renders a link to the metadata editor.
*
* @param props
* @constructor
* Renders a link to the metadata editor in a modal dialog when clicked.
*/
const LinkedDataLink = ({uri, children}) => {

const renderModal = (classes, open, handleClose, uri) => (
<Modal
open={open}
onClose={handleClose}
aria-labelledby="modal-modal-title"
aria-describedby="modal-modal-description"
>
<Box className={classes.modalDialog}>
<Tooltip title="Close - click or press 'Esc'">
<CloseIcon onClick={handleClose} className={classes.closeButton} />
</Tooltip>
<LinkedDataEntityPage title="Metadata" subject={uri} />
</Box>
</Modal>
);

const LinkedDataLink = ({classes, uri, children}) => {
const [open, setOpen] = React.useState(false);
const handleOpen = () => setOpen(true);
const handleClose = () => setOpen(false);

const {currentUser} = useContext(UserContext);
if (currentUser && currentUser.canViewPublicMetadata) {
return (
<Link
component={RouterLink}
to={{pathname: METADATA_PATH, search: "?iri=" + encodeURIComponent(uri)}}
color="inherit"
underline="hover"
>
{children}
</Link>
<div>
<div onClick={handleOpen} className={classes.clickableDiv}>
{children}
</div>
{renderModal(classes, open, handleClose, uri)}
</div>
);
}
return children;
Expand All @@ -33,4 +50,4 @@ LinkedDataLink.propTypes = {
children: PropTypes.any.isRequired
};

export default LinkedDataLink;
export default withStyles(styles)(LinkedDataLink);
29 changes: 29 additions & 0 deletions projects/mercury/src/metadata/common/LinkedDataLink.styles.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
const styles = (theme) => ({
modalDialog: {
background: theme.palette.grey['200'],
position: 'absolute',
top: '50%',
left: '50%',
transform: 'translate(-50%, -50%)',
width: 800,
bgcolor: 'background.paper',
border: '0px solid #000',
boxShadow: 0,
outline: "none",
p: 4,
},
closeButton: {
float: 'right',
marginTop: 8,
marginRight: 8

},
clickableDiv: {
'cursor': 'pointer',
'&:hover': {
textDecoration: 'underline'
}
}
});

export default styles;
89 changes: 30 additions & 59 deletions projects/mercury/src/metadata/common/__tests__/LinkedDataLink.js
Original file line number Diff line number Diff line change
@@ -1,88 +1,59 @@
/* eslint-disable jsx-a11y/anchor-has-content */
import React from 'react';
import {configure, mount} from "enzyme";
import {configure} from "enzyme";
import Adapter from "@wojtekmaj/enzyme-adapter-react-17";

import {Router} from 'react-router-dom';
// eslint-disable-next-line import/no-extraneous-dependencies
import {createMemoryHistory} from 'history';
import LinkedDataLink from "../LinkedDataLink";
import {METADATA_PATH} from "../../../constants";
import {fireEvent, render} from "@testing-library/react";
import theme from "../../../App.theme";
import {ThemeProvider} from "@mui/material/styles";
import UserContext from '../../../users/UserContext';

// Enzyme is obsolete, the Adapter allows running our old tests.
// For new tests use React Testing Library. Consider migrating enzyme tests when refactoring.
configure({adapter: new Adapter()});
jest.mock('../LinkedDataEntityPage', () => () => <div>Mocked Metadata Page</div>);

describe('LinkedDataLink', () => {
it('should render internal link for any uri', () => {
const history: History = createMemoryHistory();
history.push = jest.fn();

const wrapper = mount(
it('renders LinkedDataLink without crashing', () => {
const { getByText } = render(
<UserContext.Provider value={{currentUser: {canViewPublicMetadata: true}}}>
<Router history={history}>
<LinkedDataLink uri="http://google.nl/some-path?search#hash">Go</LinkedDataLink>
</Router>
<ThemeProvider theme={theme}>
<LinkedDataLink uri="testUri">Test Content</LinkedDataLink>
</ThemeProvider>
</UserContext.Provider>
);

expect(wrapper.find('a').isEmpty()).toBeFalsy();
const anchor = wrapper.find('a').first();
const expectedLocation = `${METADATA_PATH}?iri=${encodeURIComponent("http://google.nl/some-path?search#hash")}`;
expect(anchor.prop('href')).toEqual(expectedLocation);
expect(anchor.text()).toEqual('Go');
expect(anchor.prop('onClick')).toBeTruthy();

anchor.prop('onClick')(new MouseEvent('click'));
expect(history.push).toBeCalledTimes(1);
expect(history.push).toBeCalledWith({
pathname: METADATA_PATH,
search: `?iri=${encodeURIComponent("http://google.nl/some-path?search#hash")}`
});
expect(getByText('Test Content')).toBeInTheDocument();
});

it('should display child elements for users without access to public metadata', () => {
const history: History = createMemoryHistory();
history.push = jest.fn();

const wrapper = mount(
<UserContext.Provider value={{currentUser: {canViewPublicMetadata: false}}}>
<Router history={history}>
<LinkedDataLink uri="http://google.nl/some-path?search#hash">Go</LinkedDataLink>
</Router>
it('shows the modal when clicked', () => {
const { getByText, queryByText } = render(
<UserContext.Provider value={{currentUser: {canViewPublicMetadata: true}}}>
<ThemeProvider theme={theme}>
<LinkedDataLink uri="testUri">Test Content</LinkedDataLink>
</ThemeProvider>
</UserContext.Provider>
);
expect(queryByText('Mocked Metadata Page')).not.toBeInTheDocument();

expect(wrapper.find('a').isEmpty()).toBeTruthy();
expect(wrapper.text()).toEqual('Go');
fireEvent.click(getByText('Test Content'));
expect(getByText('Mocked Metadata Page')).toBeInTheDocument();
});

it('should not break on an invalid url (return children only)', () => {
const uri = `some-invalid-url`;
const history: History = createMemoryHistory();
history.push = jest.fn();

const wrapper = mount(
it('closes the modal when close icon is clicked', () => {
const { getByText, getByTestId, queryByText } = render(
<UserContext.Provider value={{currentUser: {canViewPublicMetadata: true}}}>
<Router history={history}>
<LinkedDataLink uri={uri}>something</LinkedDataLink>
</Router>
<ThemeProvider theme={theme}>
<LinkedDataLink uri="testUri">Test Content</LinkedDataLink>
</ThemeProvider>
</UserContext.Provider>
);

expect(wrapper.find('a').isEmpty()).toBeFalsy();
const anchor = wrapper.find('a').first();
const expectedLocation = `${METADATA_PATH}?iri=${encodeURIComponent(uri)}`;
expect(anchor.prop('href')).toEqual(expectedLocation);
expect(anchor.text()).toEqual('something');
expect(anchor.prop('onClick')).toBeTruthy();
fireEvent.click(getByText('Test Content'));
expect(getByText('Mocked Metadata Page')).toBeInTheDocument();

anchor.prop('onClick')(new MouseEvent('click'));
expect(history.push).toBeCalledTimes(1);
expect(history.push).toBeCalledWith({
pathname: METADATA_PATH,
search: `?iri=${encodeURIComponent(uri)}`
});
const close = getByTestId('CloseIcon');
fireEvent.click(close);
expect(queryByText('Mocked Metadata Page')).not.toBeInTheDocument();
});
});
2 changes: 1 addition & 1 deletion projects/mercury/src/metadata/common/values/DateValue.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ class DateValue extends React.Component {
return (
<LocalizationProvider dateAdapter={AdapterDateFns}>
<DatePicker
format={DATE_FORMAT}
inputFormat={DATE_FORMAT}
invalidDateMessage="Invalid date format"
value={this.state.value}
onChange={this.handleChange}
Expand Down
39 changes: 26 additions & 13 deletions projects/mercury/src/metadata/views/facets/DateSelectionFacet.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,26 +41,39 @@ const DateSelectionFacet = (props: MetadataViewFacetProperties) => {
if (val.toString() === 'Invalid Date') {
return false;
}
return (val >= minDate && val <= maxDate);
// lib.es5 Date constructor (used in the getRangeLimit method) converts two-digits (XX) input year to 1900+XX.
// For instance, 68 turns to 1900 + 68 -> 1968.
// Thus, only 4 digits year are considered as valid.
const fourDigitsYearEntered = val.getFullYear().toString().length === 4;
return (val >= minDate && val <= maxDate && fourDigitsYearEntered);
};

const handleChange = (newValue) => {
if (value !== newValue) {
setValue(newValue);
if (isValid(newValue[0]) && isValid(newValue[1])) {
onChange(newValue);
} else {
onChange(null);
}
const isValidInterval = (startDate: Date, endDate: Date): boolean => (
!startDate || !endDate || (startDate <= endDate)
);

const handleChange = (newDateInterval) => {
if (isValidInterval(newDateInterval)) {
onChange(newDateInterval);
}
};

const handleMinDateChange = (newValue) => {
handleChange([getRangeLimit(newValue), value[1]]);
const oldEndDate = value[1];
setValue([newValue, oldEndDate]);
if (isValid(newValue)) {
const newDateInterval = [getRangeLimit(newValue), oldEndDate];
handleChange(newDateInterval);
}
};

const handleMaxDateChange = (newValue) => {
handleChange([value[0], getRangeLimit(newValue, true)]);
const oldStartDate = value[0];
setValue([oldStartDate, newValue]);
if (isValid(newValue)) {
const newDateInterval = [oldStartDate, getRangeLimit(newValue, true)];
handleChange(newDateInterval);
}
};

const renderDate = (val: any): string => {
Expand All @@ -79,7 +92,7 @@ const DateSelectionFacet = (props: MetadataViewFacetProperties) => {
<DatePicker
disableToolbar
variant="inline"
format={DATE_FORMAT}
inputFormat={DATE_FORMAT}
invalidDateMessage="Invalid date format"
margin="normal"
label={label}
Expand All @@ -88,7 +101,7 @@ const DateSelectionFacet = (props: MetadataViewFacetProperties) => {
autoOk
minDate={min || minDate}
maxDate={max || maxDate}
initialFocusedDate={placeholderDate}
defaultCalendarMonth={placeholderDate}
placeholder={renderDate(placeholderDate)}
KeyboardButtonProps={{
'aria-label': 'change date',
Expand Down

0 comments on commit cc2c960

Please sign in to comment.