1
0
Fork 0
mirror of https://github.com/portainer/portainer.git synced 2025-08-02 20:35:25 +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

@ -39,7 +39,11 @@ export function StackName({
<br />
You can leave the stack name empty, or even turn off Kubernetes Stacks
functionality entirely via{' '}
<Link to="portainer.settings" target="_blank">
<Link
to="portainer.settings"
target="_blank"
data-cy="k8s-deploy-stack-input-settings-link"
>
Kubernetes Settings
</Link>
.
@ -71,6 +75,7 @@ export function StackName({
onChange={setStackName}
placeholder="e.g. myStack"
inputId="stack_name"
data-cy="k8s-deploy-stack-input"
/>
</div>
</div>

View file

@ -13,7 +13,11 @@ export function StackNameLabelInsight() {
<>
<br />
Kubernetes Stacks functionality can be turned off entirely via{' '}
<Link to="portainer.settings" target="_blank">
<Link
to="portainer.settings"
target="_blank"
data-cy="k8s-deploy-settings-link"
>
Kubernetes Settings
</Link>
.

View file

@ -40,6 +40,7 @@ export function AnnotationsBeTeaser() {
message="Allows specifying of annotations on this resource."
featureId={FeatureId.K8S_ANNOTATIONS}
buttonClassName="!ml-0"
data-cy="annotations-be-teaser"
/>
</div>
</div>

View file

@ -46,10 +46,11 @@ export function AnnotationsForm({
onChange={(e: ChangeEvent<HTMLInputElement>) =>
handleAnnotationChange(i, 'Key', e.target.value)
}
data-cy={`annotation-key-${i}`}
/>
</div>
{annotationErrors?.[i]?.Key && (
<FormError className="mt-1 !mb-0">
<FormError className="!mb-0 mt-1">
{annotationErrors[i]?.Key}
</FormError>
)}
@ -66,10 +67,11 @@ export function AnnotationsForm({
onChange={(e: ChangeEvent<HTMLInputElement>) =>
handleAnnotationChange(i, 'Value', e.target.value)
}
data-cy={`annotation-value-${i}`}
/>
</div>
{annotationErrors?.[i]?.Value && (
<FormError className="mt-1 !mb-0">
<FormError className="!mb-0 mt-1">
{annotationErrors[i]?.Value}
</FormError>
)}
@ -77,6 +79,7 @@ export function AnnotationsForm({
<div className="col-sm-3 !m-0 !pl-0">
<Button
size="small"
data-cy={`remove-annotation-${i}`}
color="dangerlight"
className="btn-only-icon !ml-0"
type="button"

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)}

View file

@ -0,0 +1,13 @@
diff a/app/react/kubernetes/cluster/ConfigureView/ConfigureForm/EnableMetricsInput.tsx b/app/react/kubernetes/cluster/ConfigureView/ConfigureForm/EnableMetricsInput.tsx (rejected hunks)
@@ -103,7 +103,10 @@ export function EnableMetricsInput({ value, error, environmentId }: Props) {
<TextTip color="red" icon={XCircle}>
Unable to reach metrics API. You can enable the metrics-server
addon in the{' '}
- <Link to="kubernetes.cluster">Cluster Details view</Link>.
+ <Link to="kubernetes.cluster" data-cy="cluster-details-view-link">
+ Cluster Details view
+ </Link>
+ .
</TextTip>
)}
{metricsFound === true && (

View file

@ -37,7 +37,7 @@ export function StorageClassDatatable({ storageClassValues }: Props) {
checked
)
}
className="mr-2 mb-0"
className="mb-0 mr-2"
id={`kubeSetup-storageToggle${storageClassValue.Name}`}
name={`kubeSetup-storageToggle${storageClassValue.Name}`}
data-cy={`kubeSetup-storageToggle${storageClassValue.Name}`}
@ -68,7 +68,7 @@ export function StorageClassDatatable({ storageClassValues }: Props) {
checked
)
}
className="mr-2 mb-0"
className="mb-0 mr-2"
data-cy={`kubeSetup-storageExpansionToggle${storageClassValue.Name}`}
id={`kubeSetup-storageExpansionToggle${storageClassValue.Name}`}
name={`kubeSetup-storageExpansionToggle${storageClassValue.Name}`}

View file

@ -65,6 +65,7 @@ export function NodesDatatable() {
/>
</TableSettingsMenu>
)}
data-cy="k8s-nodes-datatable"
/>
);
}

View file

@ -38,6 +38,7 @@ function ActionsCell({
to="kubernetes.cluster.node.stats"
params={{ nodeName }}
className="flex items-center p-1"
data-cy="nodeStatsButton"
>
<Icon icon={BarChart} />
</Link>

View file

@ -25,7 +25,11 @@ function NameCell({
childrenUnauthorized={nodeName}
adminOnlyCE
>
<Link to="kubernetes.cluster.node" params={{ nodeName }}>
<Link
to="kubernetes.cluster.node"
params={{ nodeName }}
data-cy={`node-name-link-${nodeName}`}
>
{nodeName}
</Link>
</Authorized>

View file

@ -51,6 +51,7 @@ export function NodeApplicationsDatatable({
/>
</TableSettingsMenu>
)}
data-cy="node-applications-datatable"
/>
);
}

View file

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

View file

@ -26,6 +26,7 @@ export function useColumns(areStacksVisible: boolean) {
<Link
to="kubernetes.resourcePools.resourcePool"
params={{ id: namespace }}
data-cy={`namespace-link-${namespace}`}
>
{namespace}
</Link>

View file

@ -51,8 +51,10 @@ export function AccessTable({
</>
}
onConfirmed={() => onRemove(selectedItems)}
data-cy="remove-registry-access-button"
/>
)}
data-cy="registry-access-datatable"
/>
);
}

View file

@ -12,7 +12,7 @@ interface Props {
value: string[];
onChange(value: string[]): void;
namespaces: Namespace[];
dataCy?: string;
dataCy: string;
inputId?: string;
placeholder?: string;
}

View file

@ -50,6 +50,7 @@ export function IngressClassDatatable({
getRowId={(row) => `${row.Name}-${row.ClassName}-${row.Type}`}
renderTableActions={(selectedRows) => renderTableActions(selectedRows)}
description={renderIngressClassDescription()}
data-cy="ingress-class-datatable"
/>
</div>
);
@ -59,6 +60,7 @@ export function IngressClassDatatable({
<div className="flex items-start">
<ButtonGroup>
<Button
data-cy="disallow-ingress-controllers-button"
disabled={
selectedRows.filter((row) => row.Availability === true).length ===
0
@ -72,6 +74,7 @@ export function IngressClassDatatable({
Disallow selected
</Button>
<Button
data-cy="allow-ingress-controllers-button"
disabled={
selectedRows.filter((row) => row.Availability === false)
.length === 0

View file

@ -105,6 +105,7 @@ export function IngressClassDatatableAngular({
getRowId={(row) => `${row.Name}-${row.ClassName}-${row.Type}`}
renderTableActions={(selectedRows) => renderTableActions(selectedRows)}
description={renderIngressClassDescription()}
data-cy="k8s-ingress-classes-datatable"
/>
</div>
);
@ -127,6 +128,7 @@ export function IngressClassDatatableAngular({
false
)
}
data-cy="k8s-disallow-selected-ingress-controllers-button"
>
Disallow selected
</Button>
@ -144,6 +146,7 @@ export function IngressClassDatatableAngular({
true
)
}
data-cy="k8s-allow-selected-ingress-controllers-button"
>
Allow selected
</Button>

View file

@ -21,12 +21,14 @@ export function IntegratedAppsDatatable({
isLoading,
tableKey,
tableTitle,
dataCy,
}: {
dataset: Array<IntegratedApp>;
onRefresh: () => void;
isLoading: boolean;
tableKey: string;
tableTitle: string;
dataCy: string;
}) {
const tableState = useTableStateWithStorage<TableSettings>(
tableKey,
@ -54,6 +56,7 @@ export function IntegratedAppsDatatable({
/>
</TableSettingsMenu>
)}
data-cy={dataCy}
/>
);
}

View file

@ -15,6 +15,7 @@ function Cell({ row: { original: item } }: CellContext<IntegratedApp, string>) {
<Link
to="kubernetes.applications.application"
params={{ name: item.Name, namespace: item.ResourcePool }}
data-cy={`application-link-${item.Name}`}
>
{item.Name}
</Link>

View file

@ -3,6 +3,7 @@ import YAML from 'yaml';
import { Minus, Plus } from 'lucide-react';
import { FeatureId } from '@/react/portainer/feature-flags/enums';
import { AutomationTestingProps } from '@/types';
import { WebEditorForm } from '@@/WebEditorForm';
import { Button } from '@@/buttons';
@ -12,15 +13,21 @@ type Props = {
identifier: string;
data: string;
hideMessage?: boolean;
};
} & AutomationTestingProps;
export function YAMLInspector({ identifier, data, hideMessage }: Props) {
export function YAMLInspector({
identifier,
data,
hideMessage,
'data-cy': dataCy,
}: Props) {
const [expanded, setExpanded] = useState(false);
const yaml = useMemo(() => cleanYamlUnwantedFields(data), [data]);
return (
<div>
<WebEditorForm
data-cy={dataCy}
value={yaml}
placeholder={
hideMessage
@ -37,6 +44,7 @@ export function YAMLInspector({ identifier, data, hideMessage }: Props) {
<div className="flex items-center justify-between py-5">
<Button
icon={expanded ? Minus : Plus}
data-cy={`expand-collapse-yaml-${identifier}`}
color="default"
className="!ml-0"
onClick={() => setExpanded(!expanded)}
@ -48,6 +56,7 @@ export function YAMLInspector({ identifier, data, hideMessage }: Props) {
heading="Apply YAML changes"
message="Applies any changes that you make in the YAML editor by calling the Kubernetes API to patch the relevant resources. Any resource removals or unexpected resource additions that you make in the YAML will be ignored. Note that editing is disabled for resources in namespaces marked as system."
buttonText="Apply changes"
data-cy="yaml-inspector-apply-changes-teaser-button"
/>
</div>
</div>

View file

@ -103,6 +103,7 @@ export function ConfigMapsDatatable() {
showSystemResources={tableState.showSystemResources}
/>
}
data-cy="k8s-configmaps-datatable"
/>
);
}

View file

@ -58,6 +58,7 @@ function Cell({ row }: CellContext<ConfigMapRowData, string>) {
}}
title={name}
className="w-fit max-w-xs truncate xl:max-w-sm 2xl:max-w-md"
data-cy={`configmap-name-link-${name}`}
>
{name}
</Link>

View file

@ -23,6 +23,7 @@ export const namespace = columnHelper.accessor(
id: namespace,
}}
title={namespace}
data-cy={`configmap-namespace-link-${namespace}`}
>
{namespace}
</Link>

View file

@ -103,6 +103,7 @@ export function SecretsDatatable() {
showSystemResources={tableState.showSystemResources}
/>
}
data-cy="k8s-secrets-datatable"
/>
);
}

View file

@ -59,6 +59,7 @@ function Cell({ row }: CellContext<SecretRowData, string>) {
}}
title={name}
className="w-fit max-w-xs truncate xl:max-w-sm 2xl:max-w-md"
data-cy={`secret-name-link-${name}`}
>
{name}
</Link>

View file

@ -23,6 +23,7 @@ export const namespace = columnHelper.accessor(
id: namespace,
}}
title={namespace}
data-cy={`secret-namespace-link-${namespace}`}
>
{namespace}
</Link>

View file

@ -14,6 +14,7 @@ export function SystemResourcesSettings({
<Authorized authorizations="K8sAccessSystemNamespaces" adminOnlyCE>
<Checkbox
id="show-system-resources"
data-cy="show-system-resources"
label="Show system resources"
checked={value}
onChange={(e) => onChange(e.target.checked)}

View file

@ -488,6 +488,7 @@ export function CreateIngressView() {
params={{ id: environmentId }}
className="text-primary"
target="_blank"
data-cy="ingresses-create-application-link"
>
Applications
</Link>
@ -613,6 +614,7 @@ export function CreateIngressView() {
<div className="col-sm-12">
<Button
onClick={() => handleCreateIngressRules()}
data-cy="ingresses-create-button"
disabled={Object.keys(errors).length > 0}
>
{isEdit ? 'Update' : 'Create'}

View file

@ -15,6 +15,7 @@ import { InlineLoader } from '@@/InlineLoader';
import { Select } from '@@/form-components/ReactSelect';
import { Card } from '@@/Card';
import { InputGroup } from '@@/form-components/InputGroup';
import { Input } from '@@/form-components/Input';
import { AnnotationsForm } from '../../annotations/AnnotationsForm';
@ -182,6 +183,7 @@ export function IngressForm({
: 'No namespaces available'
}
noOptionsMessage={() => 'No namespaces available'}
data-cy="k8sAppCreate-namespaceSelect"
/>
)}
</div>
@ -204,7 +206,7 @@ export function IngressForm({
{isEdit ? (
rule.IngressName
) : (
<input
<Input
name="ingress_name"
type="text"
className="form-control"
@ -214,6 +216,7 @@ export function IngressForm({
handleIngressChange('IngressName', e.target.value)
}
disabled={isEdit}
data-cy="k8sAppCreate-ingressNameInput"
/>
)}
{errors.ingressName && !isEdit && (
@ -262,6 +265,7 @@ export function IngressForm({
)
}
noOptionsMessage={() => 'No ingress classes available'}
data-cy="k8sAppCreate-ingressClassSelect"
/>
{errors.className && (
<FormError className="error-inline mt-1">
@ -318,6 +322,7 @@ export function IngressForm({
<span>
<Button
className="btn btn-sm btn-light !ml-0 mb-2"
data-cy="add-annotation-button"
onClick={() => addNewAnnotation()}
icon={Plus}
>
@ -408,6 +413,7 @@ export function IngressForm({
</InputGroup.Addon>
<InputGroup.Input
name={`ingress_host_${hostIndex}`}
data-cy={`ingress-host_${hostIndex}`}
id={`ingress_host_${hostIndex}`}
type="text"
className="form-control form-control-sm"
@ -457,6 +463,7 @@ export function IngressForm({
}
noOptionsMessage={() => 'No TLS secrets available'}
size="sm"
data-cy={`k8sAppCreate-tlsSelect_${hostIndex}`}
/>
{!host.NoHost && (
<div className="input-group-btn">
@ -464,6 +471,7 @@ export function IngressForm({
className="btn btn-light btn-sm !ml-0 !rounded-l-none"
onClick={() => reloadTLSCerts()}
icon={RefreshCw}
data-cy={`k8sAppCreate-tlsRefreshButton_${hostIndex}`}
/>
</div>
)}
@ -478,6 +486,7 @@ export function IngressForm({
params={{ id: environmentID }}
className="text-primary"
target="_blank"
data-cy={`k8sAppCreate-createSecretLink_${hostIndex}`}
>
Create secret
</Link>{' '}
@ -553,6 +562,7 @@ export function IngressForm({
}
noOptionsMessage={() => 'No services available'}
size="sm"
data-cy={`k8sAppCreate-serviceSelect_${hostIndex}_${pathIndex}`}
/>
</InputGroup>
{errors[
@ -614,6 +624,7 @@ export function IngressForm({
}
noOptionsMessage={() => 'No ports available'}
size="sm"
data-cy={`k8sAppCreate-servicePortSelect_${hostIndex}_${pathIndex}`}
/>
</InputGroup>
{errors[
@ -672,6 +683,7 @@ export function IngressForm({
}
noOptionsMessage={() => 'No path types available'}
size="sm"
data-cy={`k8sAppCreate-pathTypeSelect_${hostIndex}_${pathIndex}`}
/>
</InputGroup>
{errors[
@ -748,8 +760,7 @@ export function IngressForm({
type="button"
onClick={() => addNewIngressRoute(hostIndex)}
icon={Plus}
size="small"
color="default"
data-cy={`k8sAppCreate-addPathButton_${hostIndex}`}
>
Add path
</Button>
@ -766,8 +777,7 @@ export function IngressForm({
type="button"
onClick={() => addNewIngressHost()}
icon={Plus}
color="default"
size="small"
data-cy="k8sAppCreate-addHostButton"
>
Add new host
</Button>
@ -778,8 +788,7 @@ export function IngressForm({
onClick={() => addNewIngressHost(true)}
disabled={hasNoHostRule}
icon={Plus}
color="default"
size="small"
data-cy="k8sAppCreate-addFallbackButton"
>
Add fallback rule
</Button>

View file

@ -90,6 +90,7 @@ export function IngressDatatable() {
/>
}
disableSelect={useCheckboxes()}
data-cy="k8s-ingresses-datatable"
/>
);
@ -114,15 +115,15 @@ export function IngressDatatable() {
<DeleteButton
disabled={selectedFlatRows.length === 0}
onConfirmed={() => handleRemoveClick(selectedFlatRows)}
data-cy="k8sSecret-removeSecretButton"
confirmMessage="Are you sure you want to delete the selected ingresses?"
data-cy="remove-ingresses-button"
/>
<AddButton to=".create" color="secondary">
<AddButton to=".create" color="secondary" data-cy="add-ingress-button">
Add with form
</AddButton>
<CreateFromManifestButton />
<CreateFromManifestButton data-cy="k8s-ingress-deploy-button" />
</Authorized>
);
}

View file

@ -30,6 +30,7 @@ function Cell({ row, getValue }: CellContext<Ingress, string>) {
name,
}}
title={name}
data-cy={`ingress-name-link-${name}`}
>
{name}
</Link>

View file

@ -24,7 +24,7 @@ export const namespace = columnHelper.accessor('Namespace', {
enableColumnFilter: true,
});
function Cell({ getValue }: CellContext<Ingress, string>) {
function Cell({ getValue, row }: CellContext<Ingress, string>) {
const namespace = getValue();
return (
<Link
@ -33,6 +33,7 @@ function Cell({ getValue }: CellContext<Ingress, string>) {
id: namespace,
}}
title={namespace}
data-cy={`ingress-namespace-link-${row.original.Name}`}
>
{namespace}
</Link>

View file

@ -11,7 +11,7 @@ interface Props {
value: Option[];
onChange(value: readonly Option[]): void;
options: Option[];
dataCy?: string;
dataCy: string;
inputId?: string;
placeholder?: string;
}

View file

@ -50,6 +50,7 @@ export function NamespaceAppsDatatable({
/>
</TableSettingsMenu>
)}
data-cy="namespace-apps-datatable"
/>
);
}

View file

@ -20,6 +20,7 @@ export const columns = [
<Link
to="kubernetes.applications.application"
params={{ name: item.Name, namespace: item.ResourcePool }}
data-cy={`application-link-${item.Name}`}
>
{item.Name}
</Link>

View file

@ -70,11 +70,14 @@ export function NamespacesDatatable({
<DeleteButton
onClick={() => onRemove(selectedItems)}
disabled={selectedItems.length === 0}
data-cy="delete-namespace-button"
/>
<AddButton color="secondary">Add with form</AddButton>
<AddButton color="secondary" data-cy="add-namespace-form-button">
Add with form
</AddButton>
<CreateFromManifestButton />
<CreateFromManifestButton data-cy="k8s-namespaces-deploy-button" />
</Authorized>
)}
renderTableSettings={() => (

View file

@ -37,8 +37,10 @@ function Cell({
props={{
to: 'kubernetes.resourcePools.resourcePool.access',
params: { id: item.Namespace.Name },
'data-cy': `manage-access-link-${item.Namespace.Name}`,
}}
icon={Users}
data-cy={`manage-access-button-${item.Namespace.Name}`}
>
Manage access
</Button>

View file

@ -34,6 +34,7 @@ export function useColumns() {
params={{
id: name,
}}
data-cy={`namespace-link-${name}`}
>
{name}
</Link>

View file

@ -24,11 +24,15 @@ export function RegistriesSelector({
return (
<>
{options.length === 0 && (
<p className="text-muted text-xs mb-1 mt-2">
<p className="text-muted mb-1 mt-2 text-xs">
{isPureAdmin ? (
<span>
No registries available. Head over to the{' '}
<Link to="portainer.registries" target="_blank">
<Link
to="portainer.registries"
target="_blank"
data-cy="namespace-permissions-registries-selector"
>
registry view
</Link>{' '}
to define a container registry.

View file

@ -15,7 +15,7 @@ export function StorageQuotaItem() {
<span>standard</span>
</div>
</FormSectionTitle>
<hr className="mt-2 mb-0 w-full" />
<hr className="mb-0 mt-2 w-full" />
<div className="form-group">
<div className="col-sm-12">
<SwitchField

View file

@ -90,6 +90,7 @@ export function ServicesDatatable() {
/>
}
renderRow={servicesRenderRow}
data-cy="k8s-services-datatable"
/>
);
}
@ -156,9 +157,10 @@ function TableActions({ selectedItems }: TableActionsProps) {
</ul>
</>
}
data-cy="k8s-remove-services-button"
/>
<CreateFromManifestButton />
<CreateFromManifestButton data-cy="k8s-create-service-button" />
</Authorized>
);

View file

@ -30,6 +30,7 @@ function Cell({ row, getValue }: CellContext<Service, string>) {
name: appName,
}}
title={appName}
data-cy={`service-application-link-${appName}`}
>
{appName}
</Link>

View file

@ -11,7 +11,7 @@ import { columnHelper } from './helper';
export const namespace = columnHelper.accessor('Namespace', {
header: 'Namespace',
id: 'namespace',
cell: ({ getValue }) => {
cell: ({ getValue, row }) => {
const namespace = getValue();
return (
@ -21,6 +21,7 @@ export const namespace = columnHelper.accessor('Namespace', {
id: namespace,
}}
title={namespace}
data-cy={`service-namespace-link-${row.original.Name}`}
>
{namespace}
</Link>

View file

@ -66,6 +66,7 @@ export function StorageDatatable({
)}
getRowCanExpand={(row) => row.original.Volumes.length > 0}
renderSubRow={(row) => <SubRow item={row.original} />}
data-cy="k8s-storage-datatable"
/>
);
}
@ -83,6 +84,7 @@ function SubRow({ item }: { item: StorageClassViewModel }) {
name: vol.PersistentVolumeClaim.Name,
namespace: vol.PersistentVolumeClaim.Namespace,
}}
data-cy={`volume-link-${vol.PersistentVolumeClaim.Name}`}
>
{vol.PersistentVolumeClaim.Name}
</Link>

View file

@ -70,8 +70,9 @@ export function VolumesDatatable({
confirmMessage="Do you want to remove the selected volume(s)?"
onConfirmed={() => onRemove(selectedItems)}
disabled={selectedItems.length === 0}
data-cy="k8s-volumes-delete-button"
/>
<CreateFromManifestButton />
<CreateFromManifestButton data-cy="k8s-volumes-deploy-button" />
</>
)}
renderTableSettings={() => (

View file

@ -33,6 +33,7 @@ export function NameCell({
namespace: item.ResourcePool.Namespace.Name,
name: item.PersistentVolumeClaim.Name,
}}
data-cy={`volume-link-${item.PersistentVolumeClaim.Name}`}
>
{item.PersistentVolumeClaim.Name}
</Link>

View file

@ -16,6 +16,7 @@ export const columns = [
<Link
to="kubernetes.resourcePools.resourcePool"
params={{ id: namespace }}
data-cy={`volume-namespace-link-${namespace}`}
>
{namespace}
</Link>
@ -37,6 +38,7 @@ export const columns = [
name: item.Applications[0].Name,
namespace: item.ResourcePool.Namespace.Name,
}}
data-cy={`volume-application-link-${item.Applications[0].Name}`}
>
{item.Applications[0].Name}
</Link>