Skip to content

Commit

Permalink
Add slider to adjust capacity_adjustment
Browse files Browse the repository at this point in the history
Add slider to adjust capacity adjustment.

Also add a new custom hook, useDebounce.

See: #7777
  • Loading branch information
nixocio committed May 12, 2021
1 parent 7f90a8b commit dfb0710
Show file tree
Hide file tree
Showing 12 changed files with 2,003 additions and 1,366 deletions.
2 changes: 1 addition & 1 deletion awx/ui_next/CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -350,7 +350,7 @@ You can learn more about the ways lingui and its React helpers at [this link](ht
### Setting up .po files to give to translation team

1. `npm run add-locale` to add the language that you want to translate to (we should only have to do this once and the commit to repo afaik). Example: `npm run add-locale en es fr` # Add English, Spanish and French locale
2. `npm run extract-strings` to create .po files for each language specified. The .po files will be placed in src/locales. When updating strings that are used by `<Plural>` or `plural()` you will need to run this command to get the strings to render properly. This commmand will create `.po` files for each of the supported languages that will need to be commited with your PR.
2. `npm run extract-strings` to create .po files for each language specified. The .po files will be placed in src/locales. When updating strings that are used by `<Plural>` or `plural()` you will need to run this command to get the strings to render properly. This command will create `.po` files for each of the supported languages that will need to be committed with your PR.
3. Open up the .po file for the language you want to test and add some translations. In production we would pass this .po file off to the translation team.
4. Once you've edited your .po file (or we've gotten a .po file back from the translation team) run `npm run compile-strings`. This command takes the .po files and turns them into a minified JSON object and can be seen in the `messages.js` file in each locale directory. These files get loaded at the App root level (see: App.jsx).
5. Change the language in your browser and reload the page. You should see your specified translations in place of English strings.
Expand Down
437 changes: 246 additions & 191 deletions awx/ui_next/src/locales/en/messages.po

Large diffs are not rendered by default.

422 changes: 237 additions & 185 deletions awx/ui_next/src/locales/es/messages.po

Large diffs are not rendered by default.

422 changes: 237 additions & 185 deletions awx/ui_next/src/locales/fr/messages.po

Large diffs are not rendered by default.

422 changes: 237 additions & 185 deletions awx/ui_next/src/locales/ja/messages.po

Large diffs are not rendered by default.

422 changes: 237 additions & 185 deletions awx/ui_next/src/locales/nl/messages.po

Large diffs are not rendered by default.

422 changes: 237 additions & 185 deletions awx/ui_next/src/locales/zh/messages.po

Large diffs are not rendered by default.

422 changes: 237 additions & 185 deletions awx/ui_next/src/locales/zu/messages.po

Large diffs are not rendered by default.

247 changes: 183 additions & 64 deletions awx/ui_next/src/screens/InstanceGroup/Instances/InstanceListItem.jsx
Original file line number Diff line number Diff line change
@@ -1,31 +1,55 @@
import React from 'react';
import React, { useState, useCallback } from 'react';
import { bool, func } from 'prop-types';

import { t } from '@lingui/macro';
import { t, Plural } from '@lingui/macro';
import styled from 'styled-components';
import 'styled-components/macro';
import {
Badge as PFBadge,
Progress,
ProgressMeasureLocation,
ProgressSize,
DataListAction,
DataListAction as PFDataListAction,
DataListCheck,
DataListItem,
DataListItemRow,
DataListItemCells,
DataListItem as PFDataListItem,
DataListItemRow as PFDataListItemRow,
DataListItemCells as PFDataListItemCells,
Slider,
} from '@patternfly/react-core';

import _DataListCell from '../../../components/DataListCell';
import InstanceToggle from '../../../components/InstanceToggle';
import { Instance } from '../../../types';
import useRequest, { useDismissableError } from '../../../util/useRequest';
import useDebounce from '../../../util/useDebounce';
import { InstancesAPI } from '../../../api';
import { useConfig } from '../../../contexts/Config';
import AlertModal from '../../../components/AlertModal';
import ErrorDetail from '../../../components/ErrorDetail';

const DataListItem = styled(PFDataListItem)`
display: flex;
flex-direction: column;
justify-content: center;
`;

const DataListItemRow = styled(PFDataListItemRow)`
align-items: center;
`;

const DataListItemCells = styled(PFDataListItemCells)`
align-items: center;
`;

const DataListAction = styled(PFDataListAction)`
align-items: center;
`;
const Unavailable = styled.span`
color: var(--pf-global--danger-color--200);
`;

const DataListCell = styled(_DataListCell)`
white-space: nowrap;
align-items: center;
`;

const Badge = styled(PFBadge)`
Expand All @@ -40,7 +64,37 @@ const ListGroup = styled.span`
}
`;

const SliderHolder = styled.div`
display: flex;
align-items: center;
justify-content: space-between;
`;

const SliderForks = styled.div`
flex-grow: 1;
margin-right: 8px;
margin-left: 8px;
text-align: center;
`;

function computeForks(memCapacity, cpuCapacity, selectedCapacityAdjustment) {
const minCapacity = Math.min(memCapacity, cpuCapacity);
const maxCapacity = Math.max(memCapacity, cpuCapacity);

return Math.floor(
minCapacity + (maxCapacity - minCapacity) * selectedCapacityAdjustment
);
}

function InstanceListItem({ instance, isSelected, onSelect, fetchInstances }) {
const { me = {} } = useConfig();
const [forks, setForks] = useState(
computeForks(
instance.mem_capacity,
instance.cpu_capacity,
instance.capacity_adjustment
)
);
const labelId = `check-action-${instance.id}`;

function usedCapacity(item) {
Expand All @@ -57,68 +111,133 @@ function InstanceListItem({ instance, isSelected, onSelect, fetchInstances }) {
return <Unavailable>{t`Unavailable`}</Unavailable>;
}

const { error: updateInstanceError, request: updateInstance } = useRequest(
useCallback(
async values => {
await InstancesAPI.update(instance.id, values);
},
[instance]
)
);

const {
error: updateError,
dismissError: dismissUpdateError,
} = useDismissableError(updateInstanceError);

const debounceUpdateInstance = useDebounce(updateInstance, 200);

const handleChangeValue = value => {
const roundedValue = Math.round(value * 100) / 100;
setForks(
computeForks(instance.mem_capacity, instance.cpu_capacity, roundedValue)
);
debounceUpdateInstance({ capacity_adjustment: roundedValue });
};

return (
<DataListItem
aria-labelledby={labelId}
id={`${instance.id}`}
key={instance.id}
>
<DataListItemRow>
<DataListCheck
aria-labelledby={labelId}
checked={isSelected}
id={`instances-${instance.id}`}
onChange={onSelect}
/>
<>
<DataListItem
aria-labelledby={labelId}
id={`${instance.id}`}
key={instance.id}
>
<DataListItemRow>
<DataListCheck
aria-labelledby={labelId}
checked={isSelected}
id={`instances-${instance.id}`}
onChange={onSelect}
/>

<DataListItemCells
dataListCells={[
<DataListCell key="name" aria-label={t`instance host name`}>
<b>{instance.hostname}</b>
</DataListCell>,
<DataListCell key="type" aria-label={t`instance type`}>
<b css="margin-right: 24px">{t`Type`}</b>
<span id={labelId}>
{instance.managed_by_policy ? t`Auto` : t`Manual`}
</span>
</DataListCell>,
<DataListCell
key="related-field-counts"
aria-label={t`instance counts`}
width={2}
>
<ListGroup>
<b>{t`Running jobs`}</b>
<Badge isRead>{instance.jobs_running}</Badge>
</ListGroup>
<ListGroup>
<b>{t`Total jobs`}</b>
<Badge isRead>{instance.jobs_total}</Badge>
</ListGroup>
</DataListCell>,
<DataListCell
key="capacity"
aria-label={t`instance group used capacity`}
>
{usedCapacity(instance)}
</DataListCell>,
]}
/>
<DataListAction
aria-label={t`actions`}
aria-labelledby={labelId}
id={labelId}
>
<InstanceToggle
css="display: inline-flex;"
fetchInstances={fetchInstances}
instance={instance}
<DataListItemCells
dataListCells={[
<DataListCell key="name" aria-label={t`instance host name`}>
<b>{instance.hostname}</b>
</DataListCell>,
<DataListCell key="type" aria-label={t`instance type`}>
<b css="margin-right: 24px">{t`Type`}</b>
<span id={labelId}>
{instance.managed_by_policy ? t`Auto` : t`Manual`}
</span>
</DataListCell>,
<DataListCell
key="related-field-counts"
aria-label={t`instance counts`}
width={3}
>
<ListGroup>
<b>{t`Running jobs`}</b>
<Badge isRead>{instance.jobs_running}</Badge>
</ListGroup>
<ListGroup>
<b>{t`Total jobs`}</b>
<Badge isRead>{instance.jobs_total}</Badge>
</ListGroup>
</DataListCell>,
<DataListCell
key="capacity-adjustment"
aria-label={t`capacity adjustment`}
width={4}
>
<SliderHolder data-cy="slider-holder">
<div data-cy="cpu-capacity">{t`CPU ${instance.cpu_capacity}`}</div>
<SliderForks data-cy="slider-forks">
<div data-cy="number-forks">
<Plural value={forks} one="# fork" other="# forks" />
</div>
<Slider
areCustomStepsContinuous
max={1}
min={0}
step={0.1}
value={instance.capacity_adjustment}
onChange={handleChangeValue}
isDisabled={!me?.is_superuser || !instance.enabled}
data-cy="slider"
/>
</SliderForks>

<div data-cy="mem-capacity">{t`RAM ${instance.mem_capacity}`}</div>
</SliderHolder>
</DataListCell>,

<DataListCell
key="capacity"
aria-label={t`instance group used capacity`}
>
{usedCapacity(instance)}
</DataListCell>,
]}
/>
</DataListAction>
</DataListItemRow>
</DataListItem>
<DataListAction
aria-label={t`actions`}
aria-labelledby={labelId}
id={labelId}
>
<InstanceToggle
css="display: inline-flex;"
fetchInstances={fetchInstances}
instance={instance}
/>
</DataListAction>
</DataListItemRow>
</DataListItem>
{updateError && (
<AlertModal
variant="error"
title={t`Error!`}
isOpen
onClose={dismissUpdateError}
>
{t`Failed to update capacity adjustment.`}
<ErrorDetail error={updateError} />
</AlertModal>
)}
</>
);
}

InstanceListItem.prototype = {
instance: Instance.isRequired,
isSelected: bool.isRequired,
Expand Down
Loading

0 comments on commit dfb0710

Please sign in to comment.