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

feature/CLAN6-43--widget-person-draw #155

Merged
merged 14 commits into from
Jan 10, 2020
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import com.cognifide.cogboard.widget.type.DefaultWidget
import com.cognifide.cogboard.widget.type.ExampleWidget
import com.cognifide.cogboard.widget.type.IframeEmbedWidget
import com.cognifide.cogboard.widget.type.JenkinsJobWidget
import com.cognifide.cogboard.widget.type.PersonDrawWidget
import com.cognifide.cogboard.widget.type.ServiceCheckWidget
import com.cognifide.cogboard.widget.type.SonarQubeWidget
import com.cognifide.cogboard.widget.type.TextWidget
Expand Down Expand Up @@ -36,6 +37,7 @@ class WidgetIndex {
IframeEmbedWidget::class.java.simpleName -> IframeEmbedWidget(vertx, config)
WorldClockWidget::class.java.simpleName -> WorldClockWidget(vertx, config)
CheckboxWidget::class.java.simpleName -> CheckboxWidget(vertx, config)
PersonDrawWidget::class.java.simpleName -> PersonDrawWidget(vertx, config)
szymon-owczarzak marked this conversation as resolved.
Show resolved Hide resolved
// add here
else -> DefaultWidget.INSTANCE
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
package com.cognifide.cogboard.widget.type

import com.cognifide.cogboard.CogboardConstants
import com.cognifide.cogboard.config.service.BoardsConfigService
import com.cognifide.cogboard.storage.ContentRepository
import com.cognifide.cogboard.widget.BaseWidget
import io.vertx.core.Vertx
import io.vertx.core.json.JsonArray
import io.vertx.core.json.JsonObject
import java.time.Instant
import java.time.LocalDateTime
import java.time.ZoneId
import java.time.ZoneOffset
import kotlin.random.Random

class PersonDrawWidget(vertx: Vertx, config: JsonObject, boardService: BoardsConfigService = BoardsConfigService()) :
jplucinski marked this conversation as resolved.
Show resolved Hide resolved
BaseWidget(vertx, config, boardService) {

private val valuesArch: MutableList<String> = mutableListOf()
jplucinski marked this conversation as resolved.
Show resolved Hide resolved
private var updateDate: LocalDateTime = LocalDateTime.now()
private var currentIndex: Int = -1
private var usedIndexes: MutableList<Int> = mutableListOf()

init {
super.config.put(CogboardConstants.PROP_SCHEDULE_PERIOD, SYNC_INTERVAL)
if (currentIndex == -1) {
jplucinski marked this conversation as resolved.
Show resolved Hide resolved
currentIndex = ContentRepository().get(id).getInteger(PROP_CONTENT_INDEX, -1)
jplucinski marked this conversation as resolved.
Show resolved Hide resolved
val updateDateMillis = ContentRepository().get(id).getLong(PROP_CONTENT_UPDATE_DATE, -1)
if (updateDateMillis > 0) {
val ofEpochMilli = Instant.ofEpochMilli(updateDateMillis)
updateDate = ofEpochMilli.atZone(ZoneId.systemDefault()).toLocalDateTime()
}
}

createDynamicChangeSubscriber()
.handler { cycle(forceCycle = true) }
}

override fun updateState() {
cycle()
}

private fun cycle(forceCycle: Boolean = false) {
val isDaily = config.getBoolean(PROP_IS_DAILY, false)
val values = toStringList(config.getJsonArray(PROP_VALUES, JsonArray()))
val randomize = config.getBoolean(PROP_RANDOMIZE, false)
val interval = config.getLong(PROP_INTERVAL, SELECT_INTERVAL)

if (!isDaily && updateDate.isAfter(LocalDateTime.now().plusMinutes(interval))) {
updateDate = LocalDateTime.now()
}

if (forceCycle || shouldCycle(currentIndex, updateDate) || values != valuesArch) {
syncValues(values)
currentIndex = newIndex(randomize, currentIndex, values, usedIndexes)
updateDate = calculateNextUpdateDate(isDaily, interval)
sendUpdate(currentIndex, updateDate)
}
}

private fun syncValues(values: MutableList<String>) {
valuesArch.clear()
jplucinski marked this conversation as resolved.
Show resolved Hide resolved
valuesArch.addAll(values)
}

private fun toStringList(values: JsonArray): MutableList<String> {
jplucinski marked this conversation as resolved.
Show resolved Hide resolved
val list = mutableListOf<String>()
jplucinski marked this conversation as resolved.
Show resolved Hide resolved
for (i in 0 until values.size()) list.add(values.getString(i))
return list
}

private fun shouldCycle(currentIndex: Int, updateDate: LocalDateTime) =
currentIndex < 0 || LocalDateTime.now().isAfter(updateDate)

private fun sendUpdate(nextIndex: Int, updateDate: LocalDateTime) {
val content = JsonObject().apply {
put(PROP_CONTENT_INDEX, nextIndex)
put(PROP_CONTENT_UPDATE_DATE, updateDate.toInstant(ZoneOffset.UTC).toEpochMilli())
put(PROP_VALUES, config.getValue(PROP_VALUES))
}
send(JsonObject().put(CogboardConstants.PROP_CONTENT, content))
}

private fun calculateNextUpdateDate(isDaily: Boolean, interval: Long): LocalDateTime = if (isDaily) {
LocalDateTime.now().apply {
plusDays(1)
withHour(0)
withMinute(1)
}
} else {
LocalDateTime.now().plusMinutes(interval)
}

private fun newIndex(randomize: Boolean, currentIndex: Int, values: List<String>, usedIndexes: MutableList<Int>) =
if (randomize) getRandomIndex(currentIndex, values, usedIndexes) else getNextIndex(currentIndex, values)

private fun getNextIndex(currentIndex: Int, values: List<String>): Int {
val nextIndex = currentIndex + 1
return if (values.isNotEmpty() && nextIndex < values.size) nextIndex else 0
}

private fun getRandomIndex(currentIndex: Int, values: List<String>, usedIndexes: MutableList<Int>): Int {
usedIndexes.add(currentIndex)
val indexes = values.mapIndexed { index, _ -> index }.filter { i -> !usedIndexes.contains(i) }

return if (indexes.isNotEmpty()) indexes[(getRandomNumber(indexes.size))] else getRandomNumber(values.size)
}

private fun getRandomNumber(size: Int): Int = Random.nextInt(0, size)

companion object {
const val SELECT_INTERVAL = 120L
const val SYNC_INTERVAL = 60L
const val PROP_IS_DAILY = "personDrawDailySwitch"
const val PROP_VALUES = "multiTextInput"
const val PROP_RANDOMIZE = "randomizeCheckbox"
const val PROP_INTERVAL = "personDrawInterval"
const val PROP_CONTENT_INDEX = "index"
const val PROP_CONTENT_UPDATE_DATE = "updateDate"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import React, { useState } from 'react';
import {
FormControl,
IconButton,
List,
ListItem,
ListItemSecondaryAction,
ListItemText,
TextField
} from '@material-ui/core';
import DeleteIcon from '@material-ui/icons/Delete';
import { remove } from 'ramda';
import { v4 } from 'uuid';
import { prepareChangeEvent } from './helpers';

const MultiTextInput = ({ value, onChange }) => {
const [items, setItems] = useState(() => value || []);
const [formValue, setFormValue] = useState('');

const handleChangeVal = e => setFormValue(e.target.value);
jplucinski marked this conversation as resolved.
Show resolved Hide resolved
const resetInput = () => setFormValue('');
const handleDelete = itemIndex => {
let itemList = remove(itemIndex, 1, items);
setItems(itemList);
onChange(prepareChangeEvent(itemList, 'array'));
};

const handleSave = itemText => {
const trimmedText = itemText.trim();
if (trimmedText.length > 0) {
let updatedItems = [...items, itemText];
setItems(updatedItems);
onChange(prepareChangeEvent(updatedItems, 'array'));
}
};

const handleKeyPressed = (e, _) => {
if (e.which === 13 || e.key === 'Enter') {
jplucinski marked this conversation as resolved.
Show resolved Hide resolved
e.preventDefault();

if (!formValue) {
return false;
}

handleSave(formValue);
resetInput();

return true;
}

return false;
jplucinski marked this conversation as resolved.
Show resolved Hide resolved
};

return (
<FormControl>
<TextField
label="Entries:"
placeholder="..."
fullWidth
margin="normal"
value={formValue}
onChange={handleChangeVal}
onKeyPress={handleKeyPressed}
/>
<List>
{items.map((item, index) => (
<ListItem key={v4()} dense button>
jplucinski marked this conversation as resolved.
Show resolved Hide resolved
<ListItemText primary={item} />
<ListItemSecondaryAction>
<IconButton
aria-label="Delete"
onClick={() => {
handleDelete(index);
}}
>
<DeleteIcon />
</IconButton>
</ListItemSecondaryAction>
</ListItem>
))}
</List>
</FormControl>
);
};

export default MultiTextInput;
Original file line number Diff line number Diff line change
Expand Up @@ -30,3 +30,12 @@ export const transformMinValueToHalf = () => {
value < prevValue ? Math.floor(value) : Math.ceil(value));
};
};

export const prepareChangeEvent = (value, type) => {
return {
target: {
value: value,
type: type
}
};
};
42 changes: 42 additions & 0 deletions cogboard-webapp/src/components/widgets/dialogFields/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import SwitchInput from './SwitchInput';
import { StyledNumberInput } from './styled';
import CredentialInput from './Credentialnput';
import PasswordInput from './PasswordInput';
import MultiTextInput from './MultiTextInput';

const dialogFields = {
LabelField: {
Expand Down Expand Up @@ -348,6 +349,47 @@ const dialogFields = {
initialValue: false,
validator: () => boolean()
},
MultiTextInput: {
component: MultiTextInput,
name: 'multiTextInput',
label: 'Multi Text Component',
initialValue: [],
validator: () =>
array()
.ensure()
.min(1, vm.FIELD_MIN_ITEMS())
.of(string())
},
DailySwitch: {
component: SwitchInput,
name: 'personDrawDailySwitch',
label: 'Daily',
initialValue: false,
validator: () => boolean()
},
PersonDrawInterval: {
component: conditionallyHidden(
NumberInput,
'personDrawDailySwitch',
value => !value
),
name: 'personDrawInterval',
label: 'Interval [min]',
initialValue: 120,
validator: () =>
number().when('personDrawDailySwitch', {
is: true,
then: number().required(),
otherwise: number().notRequired()
})
},
RandomCheckbox: {
component: CheckboxInput,
szymon-owczarzak marked this conversation as resolved.
Show resolved Hide resolved
name: 'randomizeCheckbox',
label: 'Randomize',
initialValue: false,
validator: () => boolean()
},
ExpandableContent: {
component: CheckboxInput,
name: 'expandContent',
Expand Down
13 changes: 13 additions & 0 deletions cogboard-webapp/src/components/widgets/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import WorldClockWidget from './types/WorldClockWidget';
import CheckboxWidget from './types/CheckboxWidget';
import AemHealthcheckWidget from './types/AemHealthcheckWidget';
import IframeEmbedWidget from './types/IframeEmbedWidget';
import PersonDrawWidget from './types/PersonDrawWidget';

const widgetTypes = {
DefaultWidget: {
Expand Down Expand Up @@ -146,6 +147,18 @@ const widgetTypes = {
SchedulePeriod: { min: 3 },
AemHealthcheckInput: { minArrayLength: 1 }
}
},
PersonDrawWidget: {
name: 'Person Draw Widget',
jplucinski marked this conversation as resolved.
Show resolved Hide resolved
component: PersonDrawWidget,
dialogFields: [
'RandomCheckbox',
'DailySwitch',
'PersonDrawInterval',
'MultiTextInput'
],
showUpdateTime: false,
validationConstraints: {}
}
};

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import React from 'react';

import IconButton from '@material-ui/core/IconButton';
import { TypographyVariant } from './styled';
import { CenterWrapper } from '../TextWidget/styled';
import { Refresh } from '@material-ui/icons';
import { getIsAuthenticated } from '../../../../selectors';
import { useSelector } from 'react-redux';
import { array, bool, number } from 'prop-types';
import { postWidgetContentUpdate } from '../../../../utils/fetch';

const PersonDrawWidget = ({ id, multiTextInput, index }) => {
const isAuthenticated = useSelector(getIsAuthenticated);
const caption =
multiTextInput && multiTextInput.length - 1 >= index
? multiTextInput[index]
: '';
const handleForceCycle = () => {
postWidgetContentUpdate({
id,
content: { forceCycle: true }
}).catch(e => console.log(e));
szymon-owczarzak marked this conversation as resolved.
Show resolved Hide resolved
};

return (
<>
<CenterWrapper>
<TypographyVariant>{caption}</TypographyVariant>
jplucinski marked this conversation as resolved.
Show resolved Hide resolved
</CenterWrapper>
{isAuthenticated && (
<IconButton
color="primary"
aria-label="refresh"
component="span"
onClick={handleForceCycle}
>
<Refresh />
</IconButton>
)}
</>
);
};

PersonDrawWidget.propTypes = {
randomizeCheckbox: bool,
personDrawInterval: number,
personDrawDailySwitch: bool,
multiTextInput: array,
index: number
};

PersonDrawWidget.defaultProps = {
index: -1,
randomizeCheckbox: false,
personDrawInterval: 120,
personDrawDailySwitch: false,
multiTextInput: []
};

export default PersonDrawWidget;
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import styled from '@emotion/styled/macro';

import { Typography } from '@material-ui/core';

export const TypographyVariant = styled(Typography)`
jplucinski marked this conversation as resolved.
Show resolved Hide resolved
height: 100%;
`;
Loading