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

Add slider to adjust capacity_adjustment #10063

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
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.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch

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
Copy link
Contributor Author

@nixocio nixocio Apr 30, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Slider does not support ouiaId yet. @tiagodread I added data-cy in order to ease testing. Let me know whether they are good enough.

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