diff --git a/.forgejo/workflows/ci.yml b/.forgejo/workflows/ci.yml new file mode 100644 index 00000000..b6742f80 --- /dev/null +++ b/.forgejo/workflows/ci.yml @@ -0,0 +1,257 @@ +--- +name: Debian Image Builder Frontend CI/CD + +on: + push: + branches: [main, develop] + pull_request: + branches: [main] + workflow_dispatch: + +env: + NODE_VERSION: "18" + DEBIAN_FRONTEND: noninteractive + +jobs: + build-and-test: + name: Build and Test Frontend + runs-on: ubuntu-latest + container: + image: node:18-bullseye + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Setup Node.js environment + run: | + node --version + npm --version + + - name: Install build dependencies + run: | + apt-get update + apt-get install -y \ + build-essential \ + git \ + ca-certificates \ + python3 + + - name: Install Node.js dependencies + run: | + npm ci + npm run build || echo "Build script not found" + + - name: Run tests + run: | + if [ -f package.json ] && npm run test; then + npm test + else + echo "No test script found, skipping tests" + fi + + - name: Run linting + run: | + if [ -f package.json ] && npm run lint; then + npm run lint + else + echo "No lint script found, skipping linting" + fi + + - name: Build production bundle + run: | + if [ -f package.json ] && npm run build; then + npm run build + else + echo "No build script found" + fi + + - name: Upload build artifacts + uses: actions/upload-artifact@v4 + with: + name: frontend-build + path: | + dist/ + build/ + retention-days: 30 + + package: + name: Package Frontend + runs-on: ubuntu-latest + container: + image: node:18-bullseye + needs: build-and-test + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Setup Node.js environment + run: | + node --version + npm --version + + - name: Install build dependencies + run: | + apt-get update + apt-get install -y \ + build-essential \ + devscripts \ + debhelper \ + git \ + ca-certificates \ + python3 + + - name: Install Node.js dependencies + run: npm ci + + - name: Build production bundle + run: | + if [ -f package.json ] && npm run build; then + npm run build + else + echo "No build script found" + fi + + - name: Create debian directory + run: | + mkdir -p debian + cat > debian/control << EOF +Source: debian-image-builder-frontend +Section: web +Priority: optional +Maintainer: Debian Forge Team +Build-Depends: debhelper (>= 13), nodejs, npm, git, ca-certificates +Standards-Version: 4.6.2 + +Package: debian-image-builder-frontend +Architecture: all +Depends: \${misc:Depends}, nodejs, nginx +Description: Debian Image Builder Frontend + Web-based frontend for Debian Image Builder with Cockpit integration. + Provides a user interface for managing image builds, blueprints, + and system configurations through a modern React application. +EOF + + cat > debian/rules << EOF +#!/usr/bin/make -f +%: + dh \$@ + +override_dh_auto_install: + dh_auto_install + mkdir -p debian/debian-image-builder-frontend/usr/share/debian-image-builder-frontend + mkdir -p debian/debian-image-builder-frontend/etc/nginx/sites-available + mkdir -p debian/debian-image-builder-frontend/etc/cockpit + + # Copy built frontend files + if [ -d dist ]; then + cp -r dist/* debian/debian-image-builder-frontend/usr/share/debian-image-builder-frontend/ + elif [ -d build ]; then + cp -r build/* debian/debian-image-builder-frontend/usr/share/debian-image-builder-frontend/ + fi + + # Copy source files for development + cp -r src debian/debian-image-builder-frontend/usr/share/debian-image-builder-frontend/ + cp package.json debian/debian-image-builder-frontend/usr/share/debian-image-builder-frontend/ + + # Create nginx configuration + cat > debian/debian-image-builder-frontend/etc/nginx/sites-available/debian-image-builder-frontend << 'NGINX_EOF' +server { + listen 80; + server_name localhost; + root /usr/share/debian-image-builder-frontend; + index index.html; + + location / { + try_files \$uri \$uri/ /index.html; + } + + location /api/ { + proxy_pass http://localhost:8080/; + proxy_set_header Host \$host; + proxy_set_header X-Real-IP \$remote_addr; + } +} +NGINX_EOF + + # Create cockpit manifest + cat > debian/debian-image-builder-frontend/etc/cockpit/debian-image-builder.manifest << 'COCKPIT_EOF' +{ + "version": 1, + "manifest": { + "name": "debian-image-builder", + "version": "1.0.0", + "title": "Debian Image Builder", + "description": "Build and manage Debian atomic images", + "url": "/usr/share/debian-image-builder-frontend", + "icon": "debian-logo", + "requires": { + "cockpit": ">= 200" + } + } +} +COCKPIT_EOF +EOF + + cat > debian/changelog << EOF +debian-image-builder-frontend (1.0.0-1) unstable; urgency=medium + + * Initial release + * Debian Image Builder Frontend with Cockpit integration + * React-based web interface for image management + + -- Debian Forge Team $(date -R) +EOF + + cat > debian/compat << EOF +13 +EOF + + chmod +x debian/rules + + - name: Build Debian package + run: | + dpkg-buildpackage -us -uc -b + ls -la ../*.deb + + - name: Upload Debian package + uses: actions/upload-artifact@v4 + with: + name: debian-image-builder-frontend-deb + path: ../*.deb + retention-days: 30 + + cockpit-integration: + name: Test Cockpit Integration + runs-on: ubuntu-latest + container: + image: node:18-bullseye + needs: build-and-test + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Setup Node.js environment + run: | + node --version + npm --version + + - name: Install Node.js dependencies + run: npm ci + + - name: Test cockpit integration + run: | + echo "Testing Cockpit integration..." + if [ -d cockpit ]; then + echo "Cockpit directory found:" + ls -la cockpit/ + else + echo "No cockpit directory found" + fi + + if [ -f package.json ]; then + echo "Package.json scripts:" + npm run + fi diff --git a/.forgejo/workflows/ci.yml.disabled b/.forgejo/workflows/ci.yml.disabled new file mode 100644 index 00000000..b6742f80 --- /dev/null +++ b/.forgejo/workflows/ci.yml.disabled @@ -0,0 +1,257 @@ +--- +name: Debian Image Builder Frontend CI/CD + +on: + push: + branches: [main, develop] + pull_request: + branches: [main] + workflow_dispatch: + +env: + NODE_VERSION: "18" + DEBIAN_FRONTEND: noninteractive + +jobs: + build-and-test: + name: Build and Test Frontend + runs-on: ubuntu-latest + container: + image: node:18-bullseye + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Setup Node.js environment + run: | + node --version + npm --version + + - name: Install build dependencies + run: | + apt-get update + apt-get install -y \ + build-essential \ + git \ + ca-certificates \ + python3 + + - name: Install Node.js dependencies + run: | + npm ci + npm run build || echo "Build script not found" + + - name: Run tests + run: | + if [ -f package.json ] && npm run test; then + npm test + else + echo "No test script found, skipping tests" + fi + + - name: Run linting + run: | + if [ -f package.json ] && npm run lint; then + npm run lint + else + echo "No lint script found, skipping linting" + fi + + - name: Build production bundle + run: | + if [ -f package.json ] && npm run build; then + npm run build + else + echo "No build script found" + fi + + - name: Upload build artifacts + uses: actions/upload-artifact@v4 + with: + name: frontend-build + path: | + dist/ + build/ + retention-days: 30 + + package: + name: Package Frontend + runs-on: ubuntu-latest + container: + image: node:18-bullseye + needs: build-and-test + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Setup Node.js environment + run: | + node --version + npm --version + + - name: Install build dependencies + run: | + apt-get update + apt-get install -y \ + build-essential \ + devscripts \ + debhelper \ + git \ + ca-certificates \ + python3 + + - name: Install Node.js dependencies + run: npm ci + + - name: Build production bundle + run: | + if [ -f package.json ] && npm run build; then + npm run build + else + echo "No build script found" + fi + + - name: Create debian directory + run: | + mkdir -p debian + cat > debian/control << EOF +Source: debian-image-builder-frontend +Section: web +Priority: optional +Maintainer: Debian Forge Team +Build-Depends: debhelper (>= 13), nodejs, npm, git, ca-certificates +Standards-Version: 4.6.2 + +Package: debian-image-builder-frontend +Architecture: all +Depends: \${misc:Depends}, nodejs, nginx +Description: Debian Image Builder Frontend + Web-based frontend for Debian Image Builder with Cockpit integration. + Provides a user interface for managing image builds, blueprints, + and system configurations through a modern React application. +EOF + + cat > debian/rules << EOF +#!/usr/bin/make -f +%: + dh \$@ + +override_dh_auto_install: + dh_auto_install + mkdir -p debian/debian-image-builder-frontend/usr/share/debian-image-builder-frontend + mkdir -p debian/debian-image-builder-frontend/etc/nginx/sites-available + mkdir -p debian/debian-image-builder-frontend/etc/cockpit + + # Copy built frontend files + if [ -d dist ]; then + cp -r dist/* debian/debian-image-builder-frontend/usr/share/debian-image-builder-frontend/ + elif [ -d build ]; then + cp -r build/* debian/debian-image-builder-frontend/usr/share/debian-image-builder-frontend/ + fi + + # Copy source files for development + cp -r src debian/debian-image-builder-frontend/usr/share/debian-image-builder-frontend/ + cp package.json debian/debian-image-builder-frontend/usr/share/debian-image-builder-frontend/ + + # Create nginx configuration + cat > debian/debian-image-builder-frontend/etc/nginx/sites-available/debian-image-builder-frontend << 'NGINX_EOF' +server { + listen 80; + server_name localhost; + root /usr/share/debian-image-builder-frontend; + index index.html; + + location / { + try_files \$uri \$uri/ /index.html; + } + + location /api/ { + proxy_pass http://localhost:8080/; + proxy_set_header Host \$host; + proxy_set_header X-Real-IP \$remote_addr; + } +} +NGINX_EOF + + # Create cockpit manifest + cat > debian/debian-image-builder-frontend/etc/cockpit/debian-image-builder.manifest << 'COCKPIT_EOF' +{ + "version": 1, + "manifest": { + "name": "debian-image-builder", + "version": "1.0.0", + "title": "Debian Image Builder", + "description": "Build and manage Debian atomic images", + "url": "/usr/share/debian-image-builder-frontend", + "icon": "debian-logo", + "requires": { + "cockpit": ">= 200" + } + } +} +COCKPIT_EOF +EOF + + cat > debian/changelog << EOF +debian-image-builder-frontend (1.0.0-1) unstable; urgency=medium + + * Initial release + * Debian Image Builder Frontend with Cockpit integration + * React-based web interface for image management + + -- Debian Forge Team $(date -R) +EOF + + cat > debian/compat << EOF +13 +EOF + + chmod +x debian/rules + + - name: Build Debian package + run: | + dpkg-buildpackage -us -uc -b + ls -la ../*.deb + + - name: Upload Debian package + uses: actions/upload-artifact@v4 + with: + name: debian-image-builder-frontend-deb + path: ../*.deb + retention-days: 30 + + cockpit-integration: + name: Test Cockpit Integration + runs-on: ubuntu-latest + container: + image: node:18-bullseye + needs: build-and-test + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Setup Node.js environment + run: | + node --version + npm --version + + - name: Install Node.js dependencies + run: npm ci + + - name: Test cockpit integration + run: | + echo "Testing Cockpit integration..." + if [ -d cockpit ]; then + echo "Cockpit directory found:" + ls -la cockpit/ + else + echo "No cockpit directory found" + fi + + if [ -f package.json ]; then + echo "Package.json scripts:" + npm run + fi diff --git a/cockpit/cockpit-image-builder.spec b/cockpit/cockpit-image-builder.spec index 85988df4..2c739e91 100644 --- a/cockpit/cockpit-image-builder.spec +++ b/cockpit/cockpit-image-builder.spec @@ -1,5 +1,5 @@ Name: cockpit-image-builder -Version: 75 +Version: 76 Release: 1%{?dist} Summary: Image builder plugin for Cockpit diff --git a/src/Components/Blueprints/BlueprintsSideBar.tsx b/src/Components/Blueprints/BlueprintsSideBar.tsx index 6c1dd852..9422f8e3 100644 --- a/src/Components/Blueprints/BlueprintsSideBar.tsx +++ b/src/Components/Blueprints/BlueprintsSideBar.tsx @@ -1,4 +1,4 @@ -import React, { useCallback, useEffect, useState } from 'react'; +import React, { useCallback } from 'react'; import { Bullseye, @@ -17,7 +17,6 @@ import { import { PlusCircleIcon, SearchIcon } from '@patternfly/react-icons'; import { SVGIconProps } from '@patternfly/react-icons/dist/esm/createIcon'; import useChrome from '@redhat-cloud-services/frontend-components/useChrome'; -import { ChromeUser } from '@redhat-cloud-services/types'; import debounce from 'lodash/debounce'; import { Link } from 'react-router-dom'; @@ -29,6 +28,7 @@ import { PAGINATION_LIMIT, PAGINATION_OFFSET, } from '../../constants'; +import { useGetUser } from '../../Hooks'; import { useGetBlueprintsQuery } from '../../store/backendApi'; import { selectBlueprintSearchInput, @@ -60,8 +60,8 @@ type emptyBlueprintStateProps = { }; const BlueprintsSidebar = () => { - const [userData, setUserData] = useState(undefined); const { analytics, auth } = useChrome(); + const { userData } = useGetUser(auth); const selectedBlueprintId = useAppSelector(selectSelectedBlueprintId); const blueprintSearchInput = useAppSelector(selectBlueprintSearchInput); @@ -73,16 +73,6 @@ const BlueprintsSidebar = () => { offset: blueprintsOffset, }; - useEffect(() => { - (async () => { - const data = await auth.getUser(); - setUserData(data); - })(); - // This useEffect hook should run *only* on mount and therefore has an empty - // dependency array. eslint's exhaustive-deps rule does not support this use. - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); - if (blueprintSearchInput) { searchParams.search = blueprintSearchInput; } diff --git a/src/Components/Blueprints/BuildImagesButton.tsx b/src/Components/Blueprints/BuildImagesButton.tsx index 6304d98a..431b765b 100644 --- a/src/Components/Blueprints/BuildImagesButton.tsx +++ b/src/Components/Blueprints/BuildImagesButton.tsx @@ -1,4 +1,4 @@ -import React, { useEffect, useState } from 'react'; +import React, { useState } from 'react'; import { Button, @@ -16,11 +16,13 @@ import { } from '@patternfly/react-core'; import { MenuToggleElement } from '@patternfly/react-core/dist/esm/components/MenuToggle/MenuToggle'; import useChrome from '@redhat-cloud-services/frontend-components/useChrome'; -import { ChromeUser } from '@redhat-cloud-services/types'; import { skipToken } from '@reduxjs/toolkit/query'; import { AMPLITUDE_MODULE_NAME, targetOptions } from '../../constants'; -import { useComposeBPWithNotification as useComposeBlueprintMutation } from '../../Hooks'; +import { + useComposeBPWithNotification as useComposeBlueprintMutation, + useGetUser, +} from '../../Hooks'; import { useGetBlueprintQuery } from '../../store/backendApi'; import { selectSelectedBlueprintId } from '../../store/BlueprintSlice'; import { useAppSelector } from '../../store/hooks'; @@ -37,18 +39,7 @@ export const BuildImagesButton = ({ children }: BuildImagesButtonPropTypes) => { const { trigger: buildBlueprint, isLoading: imageBuildLoading } = useComposeBlueprintMutation(); const { analytics, auth } = useChrome(); - - const [userData, setUserData] = useState(undefined); - - useEffect(() => { - (async () => { - const data = await auth.getUser(); - setUserData(data); - })(); - // This useEffect hook should run *only* on mount and therefore has an empty - // dependency array. eslint's exhaustive-deps rule does not support this use. - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); + const { userData } = useGetUser(auth); const onBuildHandler = async () => { if (selectedBlueprintId) { diff --git a/src/Components/Blueprints/DeleteBlueprintModal.tsx b/src/Components/Blueprints/DeleteBlueprintModal.tsx index afd209d5..230deeed 100644 --- a/src/Components/Blueprints/DeleteBlueprintModal.tsx +++ b/src/Components/Blueprints/DeleteBlueprintModal.tsx @@ -1,4 +1,4 @@ -import React, { useEffect, useState } from 'react'; +import React from 'react'; import { Button, @@ -9,14 +9,16 @@ import { ModalVariant, } from '@patternfly/react-core'; import useChrome from '@redhat-cloud-services/frontend-components/useChrome'; -import { ChromeUser } from '@redhat-cloud-services/types'; import { AMPLITUDE_MODULE_NAME, PAGINATION_LIMIT, PAGINATION_OFFSET, } from '../../constants'; -import { useDeleteBPWithNotification as useDeleteBlueprintMutation } from '../../Hooks'; +import { + useDeleteBPWithNotification as useDeleteBlueprintMutation, + useGetUser, +} from '../../Hooks'; import { backendApi, useGetBlueprintsQuery } from '../../store/backendApi'; import { selectBlueprintSearchInput, @@ -42,17 +44,7 @@ export const DeleteBlueprintModal: React.FunctionComponent< const blueprintsLimit = useAppSelector(selectLimit) || PAGINATION_LIMIT; const dispatch = useAppDispatch(); const { analytics, auth } = useChrome(); - const [userData, setUserData] = useState(undefined); - - useEffect(() => { - (async () => { - const data = await auth.getUser(); - setUserData(data); - })(); - // This useEffect hook should run *only* on mount and therefore has an empty - // dependency array. eslint's exhaustive-deps rule does not support this use. - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); + const { userData } = useGetUser(auth); const searchParams: GetBlueprintsApiArg = { limit: blueprintsLimit, diff --git a/src/Components/CreateImageWizard/steps/Registration/index.tsx b/src/Components/CreateImageWizard/steps/Registration/index.tsx index bd96d505..081fd54e 100644 --- a/src/Components/CreateImageWizard/steps/Registration/index.tsx +++ b/src/Components/CreateImageWizard/steps/Registration/index.tsx @@ -1,4 +1,4 @@ -import React, { useEffect, useState } from 'react'; +import React from 'react'; import { ClipboardCopy, @@ -16,6 +16,7 @@ import ActivationKeysList from './components/ActivationKeysList'; import Registration from './components/Registration'; import SatelliteRegistration from './components/SatelliteRegistration'; +import { useGetUser } from '../../../../Hooks'; import { useAppSelector } from '../../../../store/hooks'; import { selectActivationKey, @@ -24,18 +25,7 @@ import { const RegistrationStep = () => { const { auth } = useChrome(); - const [orgId, setOrgId] = useState(undefined); - - useEffect(() => { - (async () => { - const userData = await auth.getUser(); - const id = userData?.identity?.internal?.org_id; - setOrgId(id); - })(); - // This useEffect hook should run *only* on mount and therefore has an empty - // dependency array. eslint's exhaustive-deps rule does not support this use. - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); + const { orgId } = useGetUser(auth); const activationKey = useAppSelector(selectActivationKey); const registrationType = useAppSelector(selectRegistrationType); diff --git a/src/Components/CreateImageWizard/steps/Review/Footer/CreateDropdown.tsx b/src/Components/CreateImageWizard/steps/Review/Footer/CreateDropdown.tsx index f05d4668..ba26cfda 100644 --- a/src/Components/CreateImageWizard/steps/Review/Footer/CreateDropdown.tsx +++ b/src/Components/CreateImageWizard/steps/Review/Footer/CreateDropdown.tsx @@ -1,4 +1,4 @@ -import React, { useEffect, useState } from 'react'; +import React, { useState } from 'react'; import { Button, @@ -14,12 +14,12 @@ import { Spinner, } from '@patternfly/react-core'; import useChrome from '@redhat-cloud-services/frontend-components/useChrome'; -import { ChromeUser } from '@redhat-cloud-services/types'; import { AMPLITUDE_MODULE_NAME } from '../../../../../constants'; import { useComposeBPWithNotification as useComposeBlueprintMutation, useCreateBPWithNotification as useCreateBlueprintMutation, + useGetUser, } from '../../../../../Hooks'; import { setBlueprintId } from '../../../../../store/BlueprintSlice'; import { CockpitCreateBlueprintRequest } from '../../../../../store/cockpit/types'; @@ -44,19 +44,8 @@ export const CreateSaveAndBuildBtn = ({ setIsOpen, isDisabled, }: CreateDropdownProps) => { - const [userData, setUserData] = useState(undefined); - const { analytics, auth, isBeta } = useChrome(); - - useEffect(() => { - (async () => { - const data = await auth.getUser(); - setUserData(data); - })(); - // This useEffect hook should run *only* on mount and therefore has an empty - // dependency array. eslint's exhaustive-deps rule does not support this use. - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); + const { userData } = useGetUser(auth); const packages = useAppSelector(selectPackages); @@ -113,17 +102,7 @@ export const CreateSaveButton = ({ isDisabled, }: CreateDropdownProps) => { const { analytics, auth, isBeta } = useChrome(); - const [userData, setUserData] = useState(undefined); - - useEffect(() => { - (async () => { - const data = await auth.getUser(); - setUserData(data); - })(); - // This useEffect hook should run *only* on mount and therefore has an empty - // dependency array. eslint's exhaustive-deps rule does not support this use. - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); + const { userData } = useGetUser(auth); const packages = useAppSelector(selectPackages); diff --git a/src/Components/CreateImageWizard/steps/Review/Footer/EditDropdown.tsx b/src/Components/CreateImageWizard/steps/Review/Footer/EditDropdown.tsx index cc025a9f..6cc19cc3 100644 --- a/src/Components/CreateImageWizard/steps/Review/Footer/EditDropdown.tsx +++ b/src/Components/CreateImageWizard/steps/Review/Footer/EditDropdown.tsx @@ -1,4 +1,4 @@ -import React, { useEffect, useState } from 'react'; +import React from 'react'; import { DropdownItem, @@ -9,11 +9,11 @@ import { Spinner, } from '@patternfly/react-core'; import useChrome from '@redhat-cloud-services/frontend-components/useChrome'; -import { ChromeUser } from '@redhat-cloud-services/types'; import { AMPLITUDE_MODULE_NAME } from '../../../../../constants'; import { useComposeBPWithNotification as useComposeBlueprintMutation, + useGetUser, useUpdateBPWithNotification as useUpdateBlueprintMutation, } from '../../../../../Hooks'; import { CockpitCreateBlueprintRequest } from '../../../../../store/cockpit/types'; @@ -37,19 +37,8 @@ export const EditSaveAndBuildBtn = ({ blueprintId, isDisabled, }: EditDropdownProps) => { - const [userData, setUserData] = useState(undefined); - const { analytics, auth, isBeta } = useChrome(); - - useEffect(() => { - (async () => { - const data = await auth.getUser(); - setUserData(data); - })(); - // This useEffect hook should run *only* on mount and therefore has an empty - // dependency array. eslint's exhaustive-deps rule does not support this use. - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); + const { userData } = useGetUser(auth); const { trigger: buildBlueprint } = useComposeBlueprintMutation(); const packages = useAppSelector(selectPackages); @@ -105,19 +94,8 @@ export const EditSaveButton = ({ blueprintId, isDisabled, }: EditDropdownProps) => { - const [userData, setUserData] = useState(undefined); - const { analytics, auth, isBeta } = useChrome(); - - useEffect(() => { - (async () => { - const data = await auth.getUser(); - setUserData(data); - })(); - // This useEffect hook should run *only* on mount and therefore has an empty - // dependency array. eslint's exhaustive-deps rule does not support this use. - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); + const { userData } = useGetUser(auth); const packages = useAppSelector(selectPackages); diff --git a/src/Components/CreateImageWizard/steps/Review/Footer/Footer.tsx b/src/Components/CreateImageWizard/steps/Review/Footer/Footer.tsx index c12d590f..f6a35745 100644 --- a/src/Components/CreateImageWizard/steps/Review/Footer/Footer.tsx +++ b/src/Components/CreateImageWizard/steps/Review/Footer/Footer.tsx @@ -18,6 +18,7 @@ import { EditSaveAndBuildBtn, EditSaveButton } from './EditDropdown'; import { useCreateBPWithNotification as useCreateBlueprintMutation, + useGetUser, useUpdateBPWithNotification as useUpdateBlueprintMutation, } from '../../../../../Hooks'; import { resolveRelPath } from '../../../../../Utilities/path'; @@ -33,6 +34,7 @@ const ReviewWizardFooter = () => { const { isSuccess: isUpdateSuccess, reset: resetUpdate } = useUpdateBlueprintMutation({ fixedCacheKey: 'updateBlueprintKey' }); const { auth } = useChrome(); + const { orgId } = useGetUser(auth); const { composeId } = useParams(); const [isOpen, setIsOpen] = useState(false); const store = useStore(); @@ -52,14 +54,12 @@ const ReviewWizardFooter = () => { const getBlueprintPayload = async () => { if (!process.env.IS_ON_PREMISE) { - const userData = await auth.getUser(); - const orgId = userData?.identity?.internal?.org_id; const requestBody = orgId && mapRequestFromState(store, orgId); return requestBody; } - // NOTE: This should be fine on-prem, we should - // be able to ignore the `org-id` + // NOTE: This is fine for on prem because we save the org id + // to state through a form field in the registration step return mapRequestFromState(store, ''); }; diff --git a/src/Components/ImagesTable/ImageDetails.tsx b/src/Components/ImagesTable/ImageDetails.tsx index 809b0394..21b8a0db 100644 --- a/src/Components/ImagesTable/ImageDetails.tsx +++ b/src/Components/ImagesTable/ImageDetails.tsx @@ -1,4 +1,4 @@ -import React, { useEffect, useState } from 'react'; +import React from 'react'; import { Alert, @@ -13,11 +13,11 @@ import { } from '@patternfly/react-core'; import { ExternalLinkAltIcon } from '@patternfly/react-icons'; import useChrome from '@redhat-cloud-services/frontend-components/useChrome'; -import { ChromeUser } from '@redhat-cloud-services/types'; import ClonesTable from './ClonesTable'; import { AMPLITUDE_MODULE_NAME } from '../../constants'; +import { useGetUser } from '../../Hooks'; import { useGetComposeStatusQuery } from '../../store/backendApi'; import { extractProvisioningList } from '../../store/helpers'; import { @@ -134,19 +134,9 @@ type AwsDetailsPropTypes = { export const AwsDetails = ({ compose }: AwsDetailsPropTypes) => { const options = compose.request.image_requests[0].upload_request.options; - const [userData, setUserData] = useState(undefined); const { analytics, auth } = useChrome(); - - useEffect(() => { - (async () => { - const data = await auth.getUser(); - setUserData(data); - })(); - // This useEffect hook should run *only* on mount and therefore has an empty - // dependency array. eslint's exhaustive-deps rule does not support this use. - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); + const { userData } = useGetUser(auth); if (!isAwsUploadRequestOptions(options)) { throw TypeError( diff --git a/src/Components/ImagesTable/ImagesTable.tsx b/src/Components/ImagesTable/ImagesTable.tsx index 8c9118a1..69af395c 100644 --- a/src/Components/ImagesTable/ImagesTable.tsx +++ b/src/Components/ImagesTable/ImagesTable.tsx @@ -25,7 +25,6 @@ import { Tr, } from '@patternfly/react-table'; import useChrome from '@redhat-cloud-services/frontend-components/useChrome'; -import { ChromeUser } from '@redhat-cloud-services/types'; import { useFlag } from '@unleash/proxy-client-react'; import { useDispatch } from 'react-redux'; import { NavigateFunction, useNavigate } from 'react-router-dom'; @@ -59,6 +58,7 @@ import { SEARCH_INPUT, STATUS_POLLING_INTERVAL, } from '../../constants'; +import { useGetUser } from '../../Hooks'; import { useGetBlueprintComposesQuery, useGetBlueprintsQuery, @@ -94,7 +94,6 @@ import { OciLaunchModal } from '../Launch/OciLaunchModal'; const ImagesTable = () => { const [page, setPage] = useState(1); const [perPage, setPerPage] = useState(10); - const [userData, setUserData] = useState(undefined); const selectedBlueprintId = useAppSelector(selectSelectedBlueprintId); const blueprintSearchInput = @@ -107,16 +106,7 @@ const ImagesTable = () => { const blueprintsLimit = useAppSelector(selectLimit) || PAGINATION_LIMIT; const { analytics, auth } = useChrome(); - - useEffect(() => { - (async () => { - const data = await auth.getUser(); - setUserData(data); - })(); - // This useEffect hook should run *only* on mount and therefore has an empty - // dependency array. eslint's exhaustive-deps rule does not support this use. - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); + const { userData } = useGetUser(auth); const searchParamsGetBlueprints: GetBlueprintsApiArg = { limit: blueprintsLimit, @@ -481,18 +471,8 @@ type AwsRowPropTypes = { const AwsRow = ({ compose, composeStatus, rowIndex }: AwsRowPropTypes) => { const navigate = useNavigate(); - const [userData, setUserData] = useState(undefined); const { analytics, auth } = useChrome(); - - useEffect(() => { - (async () => { - const data = await auth.getUser(); - setUserData(data); - })(); - // This useEffect hook should run *only* on mount and therefore has an empty - // dependency array. eslint's exhaustive-deps rule does not support this use. - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); + const { userData } = useGetUser(auth); const target = ; const status = ; @@ -567,18 +547,8 @@ const Row = ({ details, instance, }: RowPropTypes) => { - const [userData, setUserData] = useState(undefined); const { analytics, auth } = useChrome(); - - useEffect(() => { - (async () => { - const data = await auth.getUser(); - setUserData(data); - })(); - // This useEffect hook should run *only* on mount and therefore has an empty - // dependency array. eslint's exhaustive-deps rule does not support this use. - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); + const { userData } = useGetUser(auth); const [isExpanded, setIsExpanded] = useState(false); const handleToggle = () => setIsExpanded(!isExpanded); diff --git a/src/Components/ImagesTable/Instance.tsx b/src/Components/ImagesTable/Instance.tsx index 4af6f5e9..254f0b05 100644 --- a/src/Components/ImagesTable/Instance.tsx +++ b/src/Components/ImagesTable/Instance.tsx @@ -1,4 +1,4 @@ -import React, { Suspense, useEffect, useState } from 'react'; +import React, { Suspense, useState } from 'react'; import path from 'path'; @@ -20,7 +20,6 @@ import { } from '@patternfly/react-core/dist/esm/components/List/List'; import { ExternalLinkAltIcon } from '@patternfly/react-icons'; import useChrome from '@redhat-cloud-services/frontend-components/useChrome'; -import { ChromeUser } from '@redhat-cloud-services/types'; import { useLoadModule, useScalprum } from '@scalprum/react-core'; import cockpit from 'cockpit'; import { useNavigate } from 'react-router-dom'; @@ -31,6 +30,7 @@ import { MODAL_ANCHOR, SEARCH_INPUT, } from '../../constants'; +import { useGetUser } from '../../Hooks'; import { useGetBlueprintsQuery, useGetComposeStatusQuery, @@ -101,19 +101,9 @@ const ProvisioningLink = ({ composeStatus, }: ProvisioningLinkPropTypes) => { const launchEofFlag = useFlag('image-builder.launcheof'); - const [userData, setUserData] = useState(undefined); const { analytics, auth } = useChrome(); - - useEffect(() => { - (async () => { - const data = await auth.getUser(); - setUserData(data); - })(); - // This useEffect hook should run *only* on mount and therefore has an empty - // dependency array. eslint's exhaustive-deps rule does not support this use. - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); + const { userData } = useGetUser(auth); const [isModalOpen, setIsModalOpen] = useState(false); const [exposedScalprumModule, error] = useLoadModule( diff --git a/src/Components/ImagesTable/Status.tsx b/src/Components/ImagesTable/Status.tsx index b3c4ca8a..fbe08ceb 100644 --- a/src/Components/ImagesTable/Status.tsx +++ b/src/Components/ImagesTable/Status.tsx @@ -1,4 +1,4 @@ -import React, { useEffect, useState } from 'react'; +import React from 'react'; import './ImageBuildStatus.scss'; import { @@ -24,13 +24,13 @@ import { PendingIcon, } from '@patternfly/react-icons'; import useChrome from '@redhat-cloud-services/frontend-components/useChrome'; -import { ChromeUser } from '@redhat-cloud-services/types'; import { AMPLITUDE_MODULE_NAME, AWS_S3_EXPIRATION_TIME_IN_HOURS, OCI_STORAGE_EXPIRATION_TIME_IN_DAYS, } from '../../constants'; +import { useGetUser } from '../../Hooks'; import { useGetComposeStatusQuery } from '../../store/backendApi'; import { CockpitComposesResponseItem } from '../../store/cockpit/types'; import { @@ -122,18 +122,8 @@ export const CloudStatus = ({ compose }: CloudStatusPropTypes) => { const { data, isSuccess } = useGetComposeStatusQuery({ composeId: compose.id, }); - const [userData, setUserData] = useState(undefined); const { analytics, auth } = useChrome(); - - useEffect(() => { - (async () => { - const data = await auth.getUser(); - setUserData(data); - })(); - // This useEffect hook should run *only* on mount and therefore has an empty - // dependency array. eslint's exhaustive-deps rule does not support this use. - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); + const { userData } = useGetUser(auth); if (!isSuccess) { return ; diff --git a/src/Components/ShareImageModal/RegionsSelect.tsx b/src/Components/ShareImageModal/RegionsSelect.tsx index 13ca0dcc..abd20390 100644 --- a/src/Components/ShareImageModal/RegionsSelect.tsx +++ b/src/Components/ShareImageModal/RegionsSelect.tsx @@ -156,7 +156,7 @@ const RegionsSelect = ({ composeId, handleClose }: RegionsSelectPropTypes) => { diff --git a/src/Hooks/index.tsx b/src/Hooks/index.tsx index df6abc94..cb2f6aef 100644 --- a/src/Hooks/index.tsx +++ b/src/Hooks/index.tsx @@ -4,3 +4,4 @@ export { useDeleteBPWithNotification } from './MutationNotifications/useDeleteBP export { useFixupBPWithNotification } from './MutationNotifications/useFixupBPWithNotification'; export { useComposeBPWithNotification } from './MutationNotifications/useComposeBPWithNotification'; export { useCloneComposeWithNotification } from './MutationNotifications/useCloneComposeWithNotification'; +export { useGetUser } from './useGetUser'; diff --git a/src/Hooks/useGetUser.tsx b/src/Hooks/useGetUser.tsx new file mode 100644 index 00000000..445bdf22 --- /dev/null +++ b/src/Hooks/useGetUser.tsx @@ -0,0 +1,24 @@ +import { useEffect, useState } from 'react'; + +import { ChromeUser } from '@redhat-cloud-services/types'; + +export const useGetUser = (auth: { getUser(): Promise }) => { + const [userData, setUserData] = useState(undefined); + const [orgId, setOrgId] = useState(undefined); + + useEffect(() => { + (async () => { + if (!process.env.IS_ON_PREMISE) { + const data = await auth.getUser(); + const id = data?.identity.internal?.org_id; + setUserData(data); + setOrgId(id); + } + })(); + // This useEffect hook should run *only* on mount and therefore has an empty + // dependency array. eslint's exhaustive-deps rule does not support this use. + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + return { userData, orgId }; +};