1
0
Fork 0
mirror of https://github.com/portainer/portainer.git synced 2025-07-24 15:59:41 +02:00

chore(data-cy): require data-cy attributes [EE-6880] (#11453)
Some checks are pending
ci / build_images (map[arch:amd64 platform:linux version:]) (push) Waiting to run
ci / build_images (map[arch:amd64 platform:windows version:1809]) (push) Waiting to run
ci / build_images (map[arch:amd64 platform:windows version:ltsc2022]) (push) Waiting to run
ci / build_images (map[arch:arm platform:linux version:]) (push) Waiting to run
ci / build_images (map[arch:arm64 platform:linux version:]) (push) Waiting to run
ci / build_images (map[arch:ppc64le platform:linux version:]) (push) Waiting to run
ci / build_images (map[arch:s390x platform:linux version:]) (push) Waiting to run
ci / build_manifests (push) Blocked by required conditions
/ triage (push) Waiting to run
Lint / Run linters (push) Waiting to run
Test / test-client (push) Waiting to run
Test / test-server (map[arch:amd64 platform:linux]) (push) Waiting to run
Test / test-server (map[arch:amd64 platform:windows version:1809]) (push) Waiting to run
Test / test-server (map[arch:amd64 platform:windows version:ltsc2022]) (push) Waiting to run
Test / test-server (map[arch:arm64 platform:linux]) (push) Waiting to run

This commit is contained in:
Ali 2024-04-11 12:11:38 +12:00 committed by GitHub
parent 3cad13388c
commit d38085a560
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
538 changed files with 2571 additions and 595 deletions

View file

@ -11,6 +11,7 @@ import { PageHeader } from '@@/PageHeader';
import { Widget, WidgetBody } from '@@/Widget';
import { Icon } from '@@/Icon';
import { Button } from '@@/buttons';
import { Input } from '@@/form-components/Input';
interface StringDictionary {
[index: string]: string;
@ -129,7 +130,7 @@ export function ConsoleView() {
<span className="input-group-addon">
<Icon icon={TerminalIcon} className="mr-1" />
</span>
<input
<Input
type="text"
className="form-control"
placeholder="/bin/bash"
@ -141,12 +142,14 @@ export function ConsoleView() {
// https://portainer.atlassian.net/browse/EE-5752
// eslint-disable-next-line jsx-a11y/no-autofocus
autoFocus
data-cy="console-command-input"
/>
</div>
</div>
<div className="row mt-4">
<Button
className="btn btn-primary !ml-0"
data-cy="connect-console-button"
onClick={
connectionStatus === 'closed'
? connectConsole

View file

@ -35,13 +35,18 @@ function UpdateIngressPrompt({
<SwitchField
name="noMatch"
data-cy="kube-update-ingress-prompt-switch"
label={inputLabel}
checked={value}
onChange={setValue}
/>
</Modal.Body>
<Modal.Footer>
<Button onClick={() => onSubmit({ noMatch: value })} color="primary">
<Button
onClick={() => onSubmit({ noMatch: value })}
color="primary"
data-cy="update-ingress-confirm-button"
>
Update
</Button>
</Modal.Footer>

View file

@ -50,6 +50,7 @@ export function ClusterIpServiceForm({
<div className="text-muted vertical-center">ClusterIP</div>
<Button
icon={Trash2}
data-cy={`remove-service_${serviceIndex}`}
color="dangerlight"
className="!ml-0"
onClick={() => {
@ -174,6 +175,7 @@ export function ClusterIpServiceForm({
<div className="flex">
<Button
icon={Plus}
data-cy={`add-port_${serviceIndex}`}
color="default"
className="!ml-0"
onClick={() => {

View file

@ -27,6 +27,7 @@ export function ServiceTabs({
>
<input
type="radio"
data-cy="service-type-radio"
name="widget-tabs"
className="hidden"
value={serviceTypeOptions[index].value}

View file

@ -121,12 +121,14 @@ export function AppIngressPathForm({
};
onChangeIngressPath(newIngressPath);
}}
data-cy="k8sAppCreate-ingressPathHostSelect"
/>
<InputGroup.ButtonWrapper>
<Button
icon={RefreshCw}
color="default"
onClick={() => ingressesQuery.refetch()}
data-cy="k8sAppCreate-ingressPathHostRefreshButton"
/>
</InputGroup.ButtonWrapper>
</InputGroup>
@ -138,6 +140,7 @@ export function AppIngressPathForm({
to="kubernetes.ingresses.create"
target="_blank"
rel="noopener noreferrer"
data-cy="k8sAppCreate-ingressPathCreateIngressLink"
>
create an ingress
</Link>
@ -153,6 +156,7 @@ export function AppIngressPathForm({
<InputGroup.Addon required>Path</InputGroup.Addon>
<InputGroup.Input
value={ingressPath?.Path ?? ''}
data-cy={`k8sAppCreate-ingressPathPathInput-${ingressPath?.Host}`}
placeholder="/example"
onChange={(e) => {
const newIngressPath = {
@ -174,6 +178,7 @@ export function AppIngressPathForm({
size="medium"
className="!ml-0"
onClick={() => onRemoveIngressPath()}
data-cy="k8sAppCreate-removeIngressPathButton"
/>
</div>
</div>

View file

@ -115,6 +115,7 @@ export function AppIngressPathsForm({
to="kubernetes.ingresses"
target="_blank"
rel="noopener noreferrer"
data-cy="applicationCreate-ingressesLink"
>
Ingresses screen
</Link>{' '}
@ -153,6 +154,7 @@ export function AppIngressPathsForm({
<div className="flex w-full flex-wrap gap-2">
<Button
icon={Plus}
data-cy={`applicationCreate-addIngressPath-${serviceIndex}-${portIndex}`}
className="!ml-0"
size="small"
color="default"

View file

@ -55,6 +55,7 @@ export function LoadBalancerServiceForm({
<div className="text-muted vertical-center">LoadBalancer</div>
<Button
icon={Trash2}
data-cy={`remove-service-${serviceIndex}`}
color="dangerlight"
className="!ml-0"
onClick={() => {
@ -213,6 +214,7 @@ export function LoadBalancerServiceForm({
<div className="flex">
<Button
icon={Plus}
data-cy={`k8sAppCreate-addPortButton_${serviceIndex}`}
color="default"
className="!ml-0"
onClick={() => {

View file

@ -76,6 +76,7 @@ export function LoadBalancerServicesForm({
to="kubernetes.cluster.setup"
target="_blank"
rel="noopener noreferrer"
data-cy="k8sAppCreate-clusterSetupLink"
>
Cluster Setup
</Link>{' '}
@ -88,6 +89,7 @@ export function LoadBalancerServicesForm({
<div className="flex">
<Button
icon={RefreshCw}
data-cy="k8sAppCreate-refreshLoadBalancerButton"
color="default"
className="!ml-0"
onClick={() => loadBalancerEnabledQuery.refetch()}

View file

@ -56,6 +56,7 @@ export function NodePortServiceForm({
<div className="text-muted vertical-center">NodePort</div>
<Button
icon={Trash2}
data-cy={`remove-service-${serviceIndex}`}
color="dangerlight"
className="!ml-0"
onClick={() => {
@ -214,6 +215,7 @@ export function NodePortServiceForm({
<div className="flex">
<Button
icon={Plus}
data-cy={`k8sAppCreate-addNodePortButton_${serviceIndex}`}
color="default"
className="!ml-0"
onClick={() => {

View file

@ -35,6 +35,7 @@ export function ApplicationYAMLEditor() {
identifier="application-yaml"
data={fullApplicationYaml}
hideMessage
data-cy="application-yaml"
/>
</WidgetBody>
</Widget>

View file

@ -60,6 +60,7 @@ export function ApplicationContainersDatatable() {
titleIcon={Server}
getRowId={(row) => row.podName} // use pod name because it's unique (name is not unique)
disableSelect
data-cy="k8s-application-containers-datatable"
/>
);
}

View file

@ -18,6 +18,7 @@ export function getActions(isServerMetricsEnabled: boolean) {
className="flex items-center gap-1"
to="kubernetes.applications.application.stats"
params={{ pod: container.podName, container: container.name }}
data-cy={`application-container-stats-${container.name}`}
>
<Icon icon={BarChart} />
Stats
@ -27,6 +28,7 @@ export function getActions(isServerMetricsEnabled: boolean) {
className="flex items-center gap-1"
to="kubernetes.applications.application.logs"
params={{ pod: container.podName, container: container.name }}
data-cy={`application-container-logs-${container.name}`}
>
<Icon icon={FileText} />
Logs
@ -37,6 +39,7 @@ export function getActions(isServerMetricsEnabled: boolean) {
className="flex items-center gap-1"
to="kubernetes.applications.application.console"
params={{ pod: container.podName, container: container.name }}
data-cy={`application-container-console-${container.name}`}
>
<Icon icon={Terminal} />
Console

View file

@ -14,7 +14,11 @@ export const node = columnHelper.accessor('nodeName', {
childrenUnauthorized={nodeName}
adminOnlyCE
>
<Link to="kubernetes.cluster.node" params={{ nodeName }}>
<Link
to="kubernetes.cluster.node"
params={{ nodeName }}
data-cy={`application-container-node-${nodeName}`}
>
<div className="max-w-xs truncate" title={nodeName}>
{nodeName}
</div>

View file

@ -67,7 +67,10 @@ export function ApplicationDetailsWidget() {
{!isSystemNamespace && (
<div className="mb-4 flex flex-wrap gap-2">
<Authorized authorizations="K8sApplicationDetailsW">
<Link to="kubernetes.applications.application.edit">
<Link
to="kubernetes.applications.application.edit"
data-cy="k8sAppDetail-editAppLink"
>
<Button
type="button"
color="light"

View file

@ -98,6 +98,7 @@ export function ApplicationEnvVarsTable({ namespace, app }: Props) {
namespace,
}}
className="flex items-center"
data-cy={`configmap-link-${envVar.resourseName}`}
>
<Icon
icon={envVar.type === 'configMap' ? FileCode : Lock}

View file

@ -48,6 +48,7 @@ export function ApplicationIngressesTable({
<Link
to="kubernetes.ingresses.edit"
params={{ name: ingressPath.ingressName, namespace }}
data-cy={`ingress-link-${ingressPath.ingressName}`}
>
{ingressPath.ingressName}
</Link>

View file

@ -109,6 +109,7 @@ export function ApplicationPersistentDataTable({
name: `${persistedFolder.volume.persistentVolumeClaim.claimName}-${persistedFolder.volumeMount?.pod?.metadata?.name}`,
namespace,
}}
data-cy={`k8sAppDetail-volMountPath-${index}`}
>
<Icon icon={Database} className="!mr-1 shrink-0" />
{`${persistedFolder.volume.persistentVolumeClaim.claimName}-${persistedFolder.volumeMount?.pod?.metadata?.name}`}
@ -146,6 +147,7 @@ export function ApplicationPersistentDataTable({
.claimName,
namespace,
}}
data-cy={`k8sAppDetail-volMountPath-${index}`}
>
<Icon icon={Database} className="!mr-1 shrink-0" />
{

View file

@ -81,6 +81,7 @@ export function ApplicationVolumeConfigsTable({ namespace, app }: Props) {
className="flex items-center"
to="kubernetes.secrets.secret"
params={{ name: volumeConfigName, namespace }}
data-cy={`secret-link-${volumeConfigName}`}
>
<Icon icon={Plus} className="!mr-1" />
{volumeConfigName}
@ -90,6 +91,7 @@ export function ApplicationVolumeConfigsTable({ namespace, app }: Props) {
className="flex items-center"
to="kubernetes.configmaps.configmap"
params={{ name: volumeConfigName, namespace }}
data-cy={`config-link-${volumeConfigName}`}
>
<Icon icon={Plus} className="!mr-1" />
{volumeConfigName}

View file

@ -100,7 +100,7 @@ export function ApplicationSummaryWidget() {
<>
{failedCreateCondition && (
<div
className="flex gap-1 items-start alert alert-danger mb-2"
className="alert alert-danger mb-2 flex items-start gap-1"
data-cy="k8sAppDetail-failedCreateMessage"
>
<div className="mt-0.5">
@ -118,7 +118,7 @@ export function ApplicationSummaryWidget() {
</div>
</div>
)}
<DetailsTable>
<DetailsTable dataCy="k8sAppDetail-table">
<tr>
<td>Name</td>
<td>
@ -153,6 +153,7 @@ export function ApplicationSummaryWidget() {
>
<Link
to="kubernetes.resourcePools.resourcePool"
data-cy="k8sAppDetail-namespaceLink"
params={{ id: namespace }}
>
{namespace}

View file

@ -68,6 +68,7 @@ export function PlacementsDatatable({
renderSubRow={(row) => (
<SubRow node={row.original} cellCount={row.getVisibleCells().length} />
)}
data-cy="kubernetes-application-placements-datatable"
/>
);
}

View file

@ -72,7 +72,7 @@ export function ApplicationsStacksDatatable({
emptyContentLabel="No stack available."
description={
<div className="w-full">
<div className="min-w-[140px] float-right mr-2">
<div className="float-right mr-2 min-w-[140px]">
<NamespaceFilter
namespaces={namespaces}
value={namespace}
@ -96,6 +96,7 @@ export function ApplicationsStacksDatatable({
/>
)}
getRowId={(row) => `${row.Name}-${row.ResourcePool}`}
data-cy="applications-stacks-datatable"
/>
);
}

View file

@ -56,6 +56,7 @@ export function NamespaceFilter({
</InputGroup.Addon>
<Select
className="!h-[30px] py-1"
data-cy="app-stacks-namespace-filter"
value={value || ''}
onChange={(e) => onChange(e.target.value)}
options={[

View file

@ -31,6 +31,7 @@ export function SubRows({
<Link
to="kubernetes.applications.application"
params={{ name: app.Name, namespace: app.ResourcePool }}
data-cy={`app-stack-application-link-${app.Name}`}
>
{app.Name}
</Link>

View file

@ -21,13 +21,14 @@ export const columns = [
columnHelper.accessor('ResourcePool', {
id: 'namespace',
header: 'Namespace',
cell: ({ getValue }) => {
cell: ({ getValue, row }) => {
const value = getValue();
return (
<div className="flex gap-2">
<Link
to="kubernetes.resourcePools.resourcePool"
params={{ id: value }}
data-cy={`app-stack-namespace-link-${row.original.Name}`}
>
{value}
</Link>
@ -52,6 +53,7 @@ export const columns = [
to="kubernetes.stacks.stack.logs"
params={{ namespace: item.ResourcePool, name: item.Name }}
className="flex items-center gap-1"
data-cy={`app-stack-logs-link-${item.Name}`}
>
<Icon icon={FileText} />
Logs

View file

@ -29,6 +29,7 @@ export function AutoScalingFormSection({
{!isMetricsEnabled && <NoMetricsServerWarning />}
<SwitchField
disabled={!isMetricsEnabled}
data-cy="k8sAppCreate-autoScaleSwitch"
label="Enable auto scaling for this application"
labelClass="col-sm-3 col-lg-2"
checked={values.isUsed}
@ -50,9 +51,9 @@ export function AutoScalingFormSection({
}}
/>
{values.isUsed && (
<div className="grid grid-cols-1 md:grid-cols-3 w-full gap-x-4 gap-y-2 my-3">
<div className="flex flex-col min-w-fit">
<label htmlFor="min-instances" className="font-normal text-xs">
<div className="my-3 grid w-full grid-cols-1 gap-x-4 gap-y-2 md:grid-cols-3">
<div className="flex min-w-fit flex-col">
<label htmlFor="min-instances" className="text-xs font-normal">
Minimum instances
</label>
<Input
@ -71,8 +72,8 @@ export function AutoScalingFormSection({
/>
{errors?.minReplicas && <FormError>{errors.minReplicas}</FormError>}
</div>
<div className="flex flex-col min-w-fit">
<label htmlFor="max-instances" className="font-normal text-xs">
<div className="flex min-w-fit flex-col">
<label htmlFor="max-instances" className="text-xs font-normal">
Maximum instances
</label>
<Input
@ -90,10 +91,10 @@ export function AutoScalingFormSection({
/>
{errors?.maxReplicas && <FormError>{errors.maxReplicas}</FormError>}
</div>
<div className="flex flex-col min-w-fit">
<div className="flex min-w-fit flex-col">
<label
htmlFor="cpu-threshold"
className="font-normal text-xs flex items-center"
className="flex items-center text-xs font-normal"
>
Target CPU usage (<b>%</b>)
<Tooltip message="The autoscaler will ensure enough instances are running to maintain an average CPU usage across all instances." />
@ -135,7 +136,10 @@ function NoMetricsServerWarning() {
{isAdmin && (
<>
Server metrics features must be enabled in the{' '}
<Link to="kubernetes.cluster.setup">
<Link
to="kubernetes.cluster.setup"
data-cy="environment-configuration-view"
>
environment configuration view
</Link>
.

View file

@ -46,8 +46,7 @@ export function ConfigMapsFormSection({
onChange={onChange}
errors={errors}
isDeleteButtonHidden
deleteButtonDataCy="k8sAppCreate-configRemoveButton"
addButtonDataCy="k8sAppCreate-configAddButton"
data-cy="k8sAppCreate-config"
disabled={configMaps.length === 0}
addButtonError={
configMaps.length === 0

View file

@ -74,6 +74,7 @@ export function ConfigurationItem({
className={clsx('!ml-0', { active: !item.overriden })}
onClick={() => onToggleOverride(false)}
icon={RotateCw}
data-cy={`k8sAppCreate-add${configurationType}AutoButton_${index}`}
>
Auto
</Button>
@ -83,6 +84,7 @@ export function ConfigurationItem({
className={clsx('!ml-0 mr-1', { active: item.overriden })}
onClick={() => onToggleOverride(true)}
icon={List}
data-cy={`k8sAppCreate-add${configurationType}OverrideButton_${index}`}
>
Override
</Button>
@ -92,8 +94,9 @@ export function ConfigurationItem({
color="dangerlight"
size="medium"
onClick={onRemoveItem}
className="!ml-0 vertical-center btn-only-icon"
className="vertical-center btn-only-icon !ml-0"
icon={Trash2}
data-cy={`k8sAppCreate-remove${configurationType}Button_${index}`}
/>
</div>
{!item.overriden && (

View file

@ -36,10 +36,15 @@ export function ConfigurationData({
? overrideKeysErrors[keyIndex]
: undefined;
return (
<div className="flex items-start gap-x-2 gap-y-2 flex-wrap">
<div className="flex flex-wrap items-start gap-x-2 gap-y-2">
<InputGroup size="small" className="min-w-[250px]">
<InputGroup.Addon>Key</InputGroup.Addon>
<InputGroup.Input type="text" value={value.key} disabled />
<InputGroup.Input
type="text"
value={value.key}
disabled
data-cy={`k8sAppCreate-${dataCyType}KeyInput_${configurationIndex}_${keyIndex}`}
/>
</InputGroup>
<InputGroup size="small">
<InputGroup.ButtonWrapper>

View file

@ -46,8 +46,7 @@ export function SecretsFormSection({
onChange={onChange}
errors={errors}
isDeleteButtonHidden
deleteButtonDataCy="k8sAppCreate-secretRemoveButton"
addButtonDataCy="k8sAppCreate-secretAddButton"
data-cy="k8sAppCreate-secret"
disabled={secrets.length === 0}
addButtonError={
secrets.length === 0

View file

@ -29,6 +29,7 @@ export function EditYamlFormSection({
return (
<div>
<WebEditorForm
data-cy="k8s-yaml-editor"
value={values}
readonly={!isAllowedToEdit}
titleContent={<TitleContent isComposeFormat={isComposeFormat} />}

View file

@ -44,7 +44,7 @@ export function PersistedFolderItem({
const formikError = isErrorType(error) ? error : undefined;
return (
<div className="flex items-start flex-wrap gap-x-2 gap-y-2">
<div className="flex flex-wrap items-start gap-x-2 gap-y-2">
<div>
<InputGroup
size="small"
@ -99,7 +99,7 @@ export function PersistedFolderItem({
<InputGroup
size="small"
className={clsx(
'min-w-fit flex',
'flex min-w-fit',
item.needsDeletion && 'striked'
)}
>
@ -107,7 +107,7 @@ export function PersistedFolderItem({
Requested size
</InputGroup.Addon>
<Input
className="!rounded-none -mr-[1px] !w-20"
className="-mr-[1px] !w-20 !rounded-none"
type="number"
placeholder="e.g. 20"
min="0"

View file

@ -61,8 +61,7 @@ export function PersistedFoldersFormSection({
isEdit && applicationValues.ApplicationType === 'StatefulSet'
}
canUndoDelete={isEdit}
deleteButtonDataCy="k8sAppCreate-persistentFolderRemoveButton"
addButtonDataCy="k8sAppCreate-persistentFolderAddButton"
data-cy="k8sAppCreate-persistentFolder"
disabled={storageClasses.length === 0}
addButtonError={getAddButtonError(storageClasses)}
isAddButtonHidden={!isAddPersistentFolderButtonShown}

View file

@ -42,7 +42,7 @@ export function PlacementFormSection({ values, onChange, errors }: Props) {
following placement rules. Placement rules are based on node labels.
</TextTip>
)}
<InputList
<InputList<Placement>
value={values.placements}
onChange={(placements) => onChange({ ...values, placements })}
renderItem={(item, onChange, index, error) => (
@ -63,7 +63,7 @@ export function PlacementFormSection({ values, onChange, errors }: Props) {
errors={errors?.placements}
addLabel="Add rule"
canUndoDelete
deleteButtonDataCy="k8sAppCreate-deletePlacementButton"
data-cy="k8sAppCreate-placement"
disabled={Object.keys(availableNodeLabels).length === 0}
addButtonError={
Object.keys(availableNodeLabels).length === 0

View file

@ -32,7 +32,7 @@ export function PlacementItem({
return (
<div className="w-full">
<div className="flex w-full gap-2">
<div className="basis-1/2 grow">
<div className="grow basis-1/2">
<Select
options={labelOptions}
value={{ label: item.label, value: item.label }}
@ -54,7 +54,7 @@ export function PlacementItem({
<FormError>{placementError.label}</FormError>
)}
</div>
<div className="basis-1/2 grow">
<div className="grow basis-1/2">
<Select
options={valueOptions}
value={valueOptions?.find((option) => option.value === item.value)}