diff --git a/cypress/e2e/general/navigation.cy.ts b/cypress/e2e/general/navigation.cy.ts index d2e931b1..69d583a7 100644 --- a/cypress/e2e/general/navigation.cy.ts +++ b/cypress/e2e/general/navigation.cy.ts @@ -46,7 +46,7 @@ describe('Navigation tests', () => { navigation.getLinkElement('account').click(); - const redirect = `${Cypress.env('keycloak')}/realms/lagoon/account/`; + const redirect = `${Cypress.env('keycloak')}/realms/lagoon/account`; cy.origin(redirect, { args: { redirect } }, ({ redirect }) => { cy.location().should(loc => { expect(loc.toString()).to.eq(redirect); diff --git a/server.js b/server.js index 7e18a30b..12fa5861 100644 --- a/server.js +++ b/server.js @@ -173,6 +173,12 @@ app }); }); + server.get('/organizations/:organizationSlug/variables', (req, res) => { + app.render(req, res, '/organizations/variables', { + organizationSlug: req.params.organizationSlug, + }); + }); + server.get('/organizations/:organizationSlug/notifications', (req, res) => { app.render(req, res, '/organizations/notifications', { organizationSlug: req.params.organizationSlug, diff --git a/src/components/AddVariable/index.js b/src/components/AddVariable/index.js index 0bf3674f..be554341 100644 --- a/src/components/AddVariable/index.js +++ b/src/components/AddVariable/index.js @@ -28,6 +28,7 @@ const scopeOptions = [ ]; export const AddVariable = ({ + varOrganization, varProject, varEnvironment, varValues, @@ -49,8 +50,10 @@ export const AddVariable = ({ setClear, setEnvironmentErrorAlert, setProjectErrorAlert, + setOrganizationErrorAlert, action, loading, + orgEnvValues, prjEnvValues, envValues, }) => { @@ -73,14 +76,20 @@ export const AddVariable = ({ setUpdateScope(varScope); }, [varName, varValue]); const handleError = () => { - setProjectErrorAlert ? setProjectErrorAlert(true) : setEnvironmentErrorAlert(true); + if (setOrganizationErrorAlert) { + setOrganizationErrorAlert(true); + } else if (setProjectErrorAlert) { + setProjectErrorAlert(true); + } else { + setEnvironmentErrorAlert(true); + } }; const handlePermissionCheck = () => { let waitForGQL = setTimeout(() => { openModal(); }, [1000]); - if (prjEnvValues || envValues) { + if (orgEnvValues || prjEnvValues || envValues) { clearTimeout(waitForGQL); openModal(); } @@ -193,6 +202,7 @@ export const AddVariable = ({ name: updateName ? updateName : inputName, value: updateValue ? updateValue : inputValue, scope: updateScope ? updateScope.toUpperCase() : inputScope, + organization: varOrganization, project: varProject, environment: varEnvironment, }, @@ -224,13 +234,9 @@ export const AddVariable = ({ } onClick={showPopconfirm} > - {varTarget === 'Environment' - ? updateVar || varName - ? 'Update environment variable' - : 'Add environment variable' - : updateVar || varName - ? 'Update project variable' - : 'Add project variable'} + {updateVar || varName + ? `Update ${varTarget.toLowerCase()} variable` + : `Add ${varTarget.toLowerCase()} variable`} ); @@ -242,13 +248,9 @@ export const AddVariable = ({ } onClick={addOrUpdateEnvVariableHandler} > - {varTarget === 'Environment' - ? updateVar || varName - ? 'Update environment variable' - : 'Add environment variable' - : updateVar || varName - ? 'Update project variable' - : 'Add project variable'} + {updateVar || varName + ? `Update ${varTarget.toLowerCase()} variable` + : `Add ${varTarget.toLowerCase()} variable`} ); } diff --git a/src/components/DeleteVariable/index.js b/src/components/DeleteVariable/index.js index 9204d4f8..6bac22ed 100644 --- a/src/components/DeleteVariable/index.js +++ b/src/components/DeleteVariable/index.js @@ -24,11 +24,13 @@ export const DeleteVariable = ({ setClear, varEnvironment, varProject, + varOrganization, open, openModal, closeModal, envValues, prjEnvValues, + orgEnvValues, loading, valueState, }) => { @@ -36,7 +38,7 @@ export const DeleteVariable = ({ let waitForGQL = setTimeout(() => { openModal(); }, [1000]); - if (prjEnvValues || envValues) { + if (orgEnvValues || prjEnvValues || envValues) { clearTimeout(waitForGQL); openModal(); } @@ -81,6 +83,7 @@ export const DeleteVariable = ({ variables: { input: { name: deleteName, + organization: varOrganization, project: varProject, environment: varEnvironment, }, diff --git a/src/components/NavTabs/NavTabsSkeleton.tsx b/src/components/NavTabs/NavTabsSkeleton.tsx index f01c1e21..6ec99ba4 100644 --- a/src/components/NavTabs/NavTabsSkeleton.tsx +++ b/src/components/NavTabs/NavTabsSkeleton.tsx @@ -1,6 +1,8 @@ import React, { FC } from 'react'; import Skeleton from 'react-loading-skeleton'; +import getConfig from 'next/config'; + import { CheckSquareOutlined, ReadOutlined, @@ -16,6 +18,12 @@ import TasksLink from 'components/link/Tasks'; import { StyledNavigation } from './StylednavTabs'; +/* eslint-disable-next-line + @typescript-eslint/no-unsafe-assignment, + @typescript-eslint/no-unsafe-call +*/ +const { publicRuntimeConfig } = getConfig(); + interface NavSkeletonProps { activeTab: string; projectName: string; @@ -48,12 +56,21 @@ const NavTabsSkeleton: FC = ({ activeTab, projectName, openshi Tasks -
  • - - - Variables - -
  • + { + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access + publicRuntimeConfig.LAGOON_UI_VIEW_ENV_VARIABLES == null && ( +
  • + + + Variables + +
  • + ) + } diff --git a/src/components/Organizations/NavTabs/OrgNavTabsSkeleton.tsx b/src/components/Organizations/NavTabs/OrgNavTabsSkeleton.tsx index 563e10d8..c150591f 100644 --- a/src/components/Organizations/NavTabs/OrgNavTabsSkeleton.tsx +++ b/src/components/Organizations/NavTabs/OrgNavTabsSkeleton.tsx @@ -1,5 +1,7 @@ import React, { FC } from 'react'; +import getConfig from 'next/config'; + import { BellOutlined, GlobalOutlined, @@ -7,10 +9,17 @@ import { ReadOutlined, SettingOutlined, TeamOutlined, + UnorderedListOutlined, } from '@ant-design/icons'; import { StyledNavigation } from './StyledNavTabs'; +/* eslint-disable-next-line + @typescript-eslint/no-unsafe-assignment, + @typescript-eslint/no-unsafe-call +*/ +const { publicRuntimeConfig } = getConfig(); + interface NavSkeletonProps { activeTab: string; } @@ -42,6 +51,17 @@ const OrgNavTabsSkeleton: FC = ({ activeTab }) => ( Projects + { + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access + publicRuntimeConfig.LAGOON_UI_VIEW_ENV_VARIABLES == null && ( +
  • + + + Variables + +
  • + ) + }
  • diff --git a/src/components/Organizations/NavTabs/index.js b/src/components/Organizations/NavTabs/index.js index 27da26d0..9176d030 100644 --- a/src/components/Organizations/NavTabs/index.js +++ b/src/components/Organizations/NavTabs/index.js @@ -1,5 +1,7 @@ import React from 'react'; +import getConfig from 'next/config'; + import { BellOutlined, GlobalOutlined, @@ -7,6 +9,7 @@ import { ReadOutlined, SettingOutlined, TeamOutlined, + UnorderedListOutlined, } from '@ant-design/icons'; import OrgGroupsLink from 'components/link/Organizations/Groups'; import OrgManageLink from 'components/link/Organizations/Manage'; @@ -14,9 +17,12 @@ import OrgNotificationsLink from 'components/link/Organizations/Notifications'; import OrganizationLink from 'components/link/Organizations/Organization'; import OrgProjectsLink from 'components/link/Organizations/Projects'; import OrgUsersLink from 'components/link/Organizations/Users'; +import OrgVariablesLink from 'components/link/Organizations/Variables'; import { StyledNavigation } from './StyledNavTabs'; +const { publicRuntimeConfig } = getConfig(); + const OrgNavTabs = ({ activeTab, organization }) => { return ( @@ -64,6 +70,14 @@ const OrgNavTabs = ({ activeTab, organization }) => { Projects
  • + {publicRuntimeConfig.LAGOON_UI_VIEW_ENV_VARIABLES == null && ( +
  • + + + Variables + +
  • + )}
  • * { + display: flex; + align-items: center; + justify-content: center; + } +`; + +export const StyledVariablesDetails = styled.div` + padding: 32px calc((100vw / 16) * 1); + width: 100%; + @media ${bp.xs_smallUp} { + min-width: 100%; + padding-left: calc(((100vw / 16) * 1)); + padding-top: 48px; + width: 100%; + } + @media ${bp.extraWideUp} { + padding-left: calc(((100vw / 16) * 1)); + } + .showHide { + margin-left: 8px !important; + width: 30px !important; + } + .showHideContainer span { + vertical-align: middle; + overflow: visible !important; + } + .unauthorized-add-var { + margin-top: 8px; + padding: 4px; + } + .field-wrapper { + &::before { + left: calc(((-100vw / 16) * 1.5) - 28px); + display: block; + position: absolute; + } + @media ${bp.xs_smallUp} { + min-width: 50%; + position: relative; + width: 50%; + } + @media ${bp.wideUp} { + min-width: 33.33%; + width: 33.33%; + } + @media ${bp.extraWideUp} { + min-width: 25%; + width: 25%; + } + + &.env-vars { + width: 100%; + display: block; + margin-top: 16px; + } + + &.source { + width: 100%; + + &::before { + background-image: url('/static/images/git-lab.svg'); + background-size: 19px 17px; + } + + .field { + color: ${color.linkBlue}; + max-width: 100%; + overflow: hidden; + text-overflow: ellipsis; + } + } + + & > div { + width: 100%; + } + } + .varName { + word-break: break-word; + } + .varValue { + & span { + cursor: pointer; + } + + & .margins { + margin-left: 10%; + } + } + .header { + display: flex; + justify-content: space-between; + margin: 16px 0; + align-items: center; + + &.no-vars { + justify-content: flex-end; + } + } + .header-buttons { + display: flex; + margin: 0 4px; + + .add-variable { + width: 54px; + height: 38px; + } + + @media ${bp.xs_smallUp} { + .show-value-btn { + min-width: 116px; + } + } + + button { + margin-right: 4px; + } + } + .loader { + display: inline-block; + width: 36px; + height: 36px; + + &.value-btn { + width: 90px; + height: 17px; + } + } + .loader:after { + content: ' '; + display: block; + width: 24px; + height: 24px; + margin: 8px; + border-radius: 50%; + border: 2px solid ${color.blue}; + border-color: ${color.blue} transparent ${color.blue} transparent; + animation: loader 1.2s linear infinite; + } + .loader.value-btn:after { + margin: 0 auto; + border: 2px solid ${color.white}; + border-color: ${color.white} transparent ${color.white} transparent; + } + @keyframes loader { + 0% { + transform: rotate(0deg); + } + 100% { + transform: rotate(360deg); + } + } +`; + +export const StyledVariableTable = styled.div` + .table-header { + @media ${bp.tinyUp} { + margin: 0 0 14px; + } + @media ${bp.smallOnly} { + flex-wrap: wrap; + } + @media ${bp.tabletUp} { + margin-top: 40px; + } + + display: flex; + justify-content: space-between; + + div.name, + div.scope, + div.delete { + display: none; + width: 45%; + @media ${bp.tinyUp} { + display: block; + text-align: left; + } + &.name { + width: 65%; + padding-left: 20px; + + @media ${bp.tabletDown} { + width: 45%; + } + } + &.scope { + width: 40%; + } + &.delete { + width: 5%; + } + } + } + + .values-present.table-header { + width: 80%; + @media ${bp.tinyUp} { + margin: 0 0 14px; + } + @media ${bp.smallOnly} { + flex-wrap: wrap; + } + @media ${bp.tabletUp} { + margin-top: 40px; + } + + div.name, + div.scope, + div.value, + div.delete { + display: none; + width: 33%; + @media ${bp.tinyUp} { + display: block; + text-align: left; + } + + &.name { + padding-left: 20px; + } + &.scope { + text-align: center; + } + &.delete { + width: 5%; + } + } + } + + .data-table { + background-color: ${props => props.theme.backgrounds.table}; + border: 1px solid ${props => props.theme.borders.tableRow}; + border-radius: 3px; + box-shadow: 0px 4px 8px 0px rgba(0, 0, 0, 0.03); + } + + .data-none { + border: 1px solid ${props => props.theme.borders.tableRow}; + border-bottom: 1px solid ${props => props.theme.borders.tableRow}; + border-radius: 3px; + line-height: 1.5rem; + padding: 8px 0 7px 0; + text-align: center; + } + + .values-present.data-row { + border: 1px solid ${props => props.theme.borders.tableRow}; + border-bottom: 1px solid ${props => props.theme.borders.tableRow}; + border-radius: 0; + line-height: 1.5rem; + padding: 8px 0 7px 0; + word-break: break-word; + + @media ${bp.tinyUp} { + display: flex; + justify-content: space-between; + } + + & > div { + text-align: left; + @media ${bp.tinyUp} { + width: 20%; + } + @media ${bp.tabletUp} { + width: 30%; + } + @media ${bp.wideUp} { + width: 30%; + } + } + + & .varName { + padding-left: 20px; + width: 31%; + @media ${bp.wideDown} { + width: 35%; + } + } + & .varScope { + text-align: center; + } + & .varValue { + width: 32.5%; + } + & .varUpdate { + display: flex; + padding: 0; + + button { + background-color: #fff; + } + } + & .varActions { + width: 20%; + display: flex; + align-items: center; + + &:last-child { + justify-content: flex-end; + -webkit-box-pack: end; + } + } + } + + .data-row { + border: 1px solid ${props => props.theme.borders.tableRow}; + border-bottom: 1px solid ${props => props.theme.borders.tableRow}; + border-radius: 0; + line-height: 1.5rem; + align-items: center; + padding: 8px 0 7px 0; + + @media ${bp.tinyUp} { + display: flex; + justify-content: space-between; + } + + & > div { + text-align: left; + @media ${bp.tinyUp} { + width: 40%; + } + @media ${bp.wideUp} { + width: 45%; + } + } + + & .varName { + width: 65%; + padding-left: 20px; + + @media ${bp.tabletDown} { + width: 45%; + } + } + & .varScope { + width: 30%; + } + & .varActions { + width: 10%; + display: flex; + + &:last-child { + justify-content: flex-end; + -webkit-box-pack: end; + } + } + & .varDelete { + padding-right: 10px; + } + + &.skeleton { + padding: 20px 0; + } + &:hover { + border: 1px solid ${color.brightBlue}; + } + + &:first-child { + border-top-left-radius: 3px; + border-top-right-radius: 3px; + } + + &:last-child { + border-bottom-left-radius: 3px; + border-bottom-right-radius: 3px; + } + } + + .collapsing { + display: none; + transition: unset; + } +`; diff --git a/src/components/Organizations/Variables/VariablesSkeleton.tsx b/src/components/Organizations/Variables/VariablesSkeleton.tsx new file mode 100644 index 00000000..c5979318 --- /dev/null +++ b/src/components/Organizations/Variables/VariablesSkeleton.tsx @@ -0,0 +1,47 @@ +import React from 'react'; +import Button from 'react-bootstrap/Button'; +import Skeleton from 'react-loading-skeleton'; + +import { StyledVariableTable, StyledVariablesDetails } from './StyledVariables'; + +const VariablesSkeleton = () => { + const numberOfVariableFields = 3; + + const skeletonItem = (idx: number) => ( +
    +
    + +
    +
    + +
    +
    + ); + + return ( + +
    + +
    + + +
    +
    + +
    +
    + +
    +
    + +
    +
    +
    + {[...Array(numberOfVariableFields)].map((_, idx) => skeletonItem(idx))} +
    +
    +
    + ); +}; + +export default VariablesSkeleton; diff --git a/src/components/Organizations/Variables/index.js b/src/components/Organizations/Variables/index.js new file mode 100644 index 00000000..530b08d1 --- /dev/null +++ b/src/components/Organizations/Variables/index.js @@ -0,0 +1,290 @@ +import React, { Fragment, useState } from 'react'; +import Button from 'react-bootstrap/Button'; +import Collapse from 'react-bootstrap/Collapse'; + +import Image from 'next/image'; + +import { LoadingOutlined } from '@ant-design/icons'; +import { useLazyQuery } from '@apollo/react-hooks'; +import { Tag } from 'antd'; +import { Tooltip } from 'antd'; +import 'bootstrap/dist/css/bootstrap.min.css'; +import Alert from 'components/Alert'; +import Btn from 'components/Button'; +import withLogic from 'components/DeleteConfirm/logic'; +import DeleteVariable from 'components/DeleteVariable'; + +import OrganizationByNameWithEnvVarsValueQuery from '../../../lib/query/organizations/OrganizationByNameWithEnvVarsValue'; +import hide from '../../../static/images/hide.svg'; +import show from '../../../static/images/show.svg'; +import AddVariable from '../../AddVariable'; +import { DeleteVariableButton } from '../../DeleteVariable/StyledDeleteVariable'; +import ViewVariableValue from '../../ViewVariableValue'; +import { StyledVariableTable, StyledVariablesDetails, VariableActions } from './StyledVariables'; + +/** + * Displays the organization variable information. + */ + +const hashValue = value => { + let hashedVal = '●'; + for (let l = 0; l < value.length; l++) { + hashedVal += '●'; + } + return hashedVal; +}; + +const OrganizationVariables = ({ organization, onVariableAdded }) => { + let displayVars = organization.envVariables; + let initValueState = new Array(displayVars.length).fill(false); + + const [valueState, setValueState] = useState(initValueState); + const [openOrgVars, setOpenOrgVars] = useState(false); + const [updateVarValue, setUpdateVarValue] = useState(''); + const [updateVarName, setUpdateVarName] = useState(''); + const [updateVarScope, setUpdateVarScope] = useState(''); + const [orgErrorAlert, setOrgErrorAlert] = useState(false); + const [action, setAction] = useState(''); + + const closeOrgError = () => { + setOrgErrorAlert(false); + }; + + const [getOrgEnvVarValues, { loading: orgLoading, error: orgError, data: orgEnvValues }] = useLazyQuery( + OrganizationByNameWithEnvVarsValueQuery, + { + variables: { name: organization.name }, + onError: () => { + setOpenOrgVars(false); + setValueState(initValueState); + setOrgErrorAlert(true); + }, + } + ); + + if (orgEnvValues) { + displayVars = orgEnvValues.organization.envVariables; + } + + if (orgError) console.error(orgError); + + const valuesShow = index => { + setValueState(valueState => valueState.map((el, i) => (i === index ? true : el))); + }; + + const valuesHide = index => { + setValueState(valueState => valueState.map((el, i) => (i === index ? false : el))); + }; + + const showVarValue = () => { + getOrgEnvVarValues(); + setOpenOrgVars(!openOrgVars); + setValueState(initValueState); + setAction('view'); + }; + + const setUpdateValue = (rowValue, rowName, rowScope) => { + setUpdateVarValue(rowValue); + setUpdateVarName(rowName); + setUpdateVarScope(rowScope); + }; + + const permissionCheck = (action, index = null) => { + getOrgEnvVarValues(); + setOpenOrgVars(false); + setAction(action); + if (action === 'delete') { + valuesShow(index); + } + }; + + const renderValue = (orgEnvVar, index) => { + if (orgEnvVar.value.length >= 0 && !valueState[index]) { + return hashValue(orgEnvVar.value).substring(0, 25); + } else if (orgEnvVar.value.length >= 100 && valueState[index]) { + return orgEnvVar.value.substring(0, 25) + '..'; + } else if (orgEnvVar.value.length === 0 && valueState[index]) { + return Empty; + } else { + return orgEnvVar.value; + } + }; + + const renderValues = (orgEnvVar, index) => { + if (orgEnvVar.value !== undefined) { + return ( + +
    +
    + {renderValue(orgEnvVar, index)} + valuesShow(index) : () => valuesHide(index)}> + + +
    + {orgEnvVar.value.length >= 100 && valueState[index] && ( + + )} +
    +
    + ); + } + }; + + return ( + + <> + {orgErrorAlert && ( + + )} +
    + +
    + + ) : ( + + )} + + + {displayVars.length > 0 && ( + + )} +
    +
    + {!displayVars.length && ( + <> +
    +
    + +
    +
    + + )} + {displayVars.length > 0 && ( +
    + +
    +
    + +
    +
    + +
    + {!orgLoading && ( + +
    + +
    +
    + )} +
    +
    + {displayVars.map((orgEnvVar, index) => { + return ( + +
    +
    {orgEnvVar.name}
    +
    {orgEnvVar.scope}
    + {renderValues(orgEnvVar, index)} +
    + + {!orgLoading && ( + +
    + + + +
    +
    + )} +
    + + + +
    +
    +
    +
    +
    + ); + })} +
    +
    +
    + )} + +
    + ); +}; + +export default withLogic(OrganizationVariables); diff --git a/src/components/ProjectNavTabs/ProjectNavTabsSkeleton.tsx b/src/components/ProjectNavTabs/ProjectNavTabsSkeleton.tsx index f69cd672..320b5fff 100644 --- a/src/components/ProjectNavTabs/ProjectNavTabsSkeleton.tsx +++ b/src/components/ProjectNavTabs/ProjectNavTabsSkeleton.tsx @@ -1,6 +1,8 @@ import React, { FC } from 'react'; import Skeleton from 'react-loading-skeleton'; +import getConfig from 'next/config'; + import { DeploymentUnitOutlined, ReadOutlined, UnorderedListOutlined } from '@ant-design/icons'; import DeployTargetsLink from 'components/link/DeployTargets'; import ProjectLink from 'components/link/Project'; @@ -8,6 +10,12 @@ import ProjectChildPageLink from 'components/link/ProjectChildPageLink'; import { StyledProjectNavTabs } from './StyledProjectNavTabs'; +/* eslint-disable-next-line + @typescript-eslint/no-unsafe-assignment, + @typescript-eslint/no-unsafe-call +*/ +const { publicRuntimeConfig } = getConfig(); + interface ProjectNavTabsSkeleton { activeTab: string; projectName: string; @@ -21,12 +29,17 @@ const ProjectNavTabsSkeleton: FC = ({ activeTab, project Project Overview
  • -
  • - - - Variables - -
  • + { + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access + publicRuntimeConfig.LAGOON_UI_VIEW_ENV_VARIABLES == null && ( +
  • + + + Variables + +
  • + ) + } {activeTab == 'deployTargets' && (
  • diff --git a/src/components/link/Organizations/Variables.tsx b/src/components/link/Organizations/Variables.tsx new file mode 100644 index 00000000..0e990715 --- /dev/null +++ b/src/components/link/Organizations/Variables.tsx @@ -0,0 +1,35 @@ +import { FC } from 'react'; + +import Link from 'next/link'; + +import { ExtendableOrgLinkProps } from './commons'; + +export const getLinkData = (organizationSlug: string) => ({ + urlObject: { + pathname: '/organizations/variables', + query: { organizationSlug }, + }, + asPath: `/organizations/${organizationSlug}/variables`, +}); + +/** + * Links to the org variables page given the organization name. + */ +const OrgVariablesLink: FC = ({ + organizationSlug, + children, + className = '', + prefetch = false, +}) => { + const linkData = getLinkData(organizationSlug); + + const linkProps = className ? { className } : {}; + + return ( + + {children} + + ); +}; + +export default OrgVariablesLink; diff --git a/src/lib/query/organizations/OrganizationByNameWithEnvVars.js b/src/lib/query/organizations/OrganizationByNameWithEnvVars.js new file mode 100644 index 00000000..1c8a1c20 --- /dev/null +++ b/src/lib/query/organizations/OrganizationByNameWithEnvVars.js @@ -0,0 +1,15 @@ +import gql from 'graphql-tag'; + +export default gql` + query getOrg($name: String!) { + organization: organizationByName(name: $name) { + id + name + envVariables { + id + name + scope + } + } + } +`; diff --git a/src/lib/query/organizations/OrganizationByNameWithEnvVarsValue.js b/src/lib/query/organizations/OrganizationByNameWithEnvVarsValue.js new file mode 100644 index 00000000..27a589c1 --- /dev/null +++ b/src/lib/query/organizations/OrganizationByNameWithEnvVarsValue.js @@ -0,0 +1,16 @@ +import gql from 'graphql-tag'; + +export default gql` + query getOrg($name: String!) { + organization: organizationByName(name: $name) { + id + name + envVariables { + id + name + value + scope + } + } + } +`; diff --git a/src/pages/organizations/variables.js b/src/pages/organizations/variables.js new file mode 100644 index 00000000..901bcd9c --- /dev/null +++ b/src/pages/organizations/variables.js @@ -0,0 +1,98 @@ +import React from 'react'; + +import Head from 'next/head'; +import { withRouter } from 'next/router'; + +import { useQuery } from '@apollo/react-hooks'; +import Breadcrumbs from 'components/Breadcrumbs'; +import OrganizationBreadcrumb from 'components/Breadcrumbs/Organizations/Organization'; +import OrgNavTabs from 'components/Organizations/NavTabs'; +import OrgNavTabsSkeleton from 'components/Organizations/NavTabs/OrgNavTabsSkeleton'; +import { OrganizationsWrapper } from 'components/Organizations/SharedStyles'; +import MainLayout from 'layouts/MainLayout'; +import OrganizationByNameWithEnvVars from 'lib/query/organizations/OrganizationByNameWithEnvVars'; + +import OrganizationVariables from '../../components/Organizations/Variables'; +import VariablesSkeleton from '../../components/Organizations/Variables/VariablesSkeleton'; +import QueryError from '../../components/errors/QueryError'; +import { VariableWrapper } from '../../styles/pageStyles'; + +/** + * Displays a list of all variables for an organization. + */ + +export const PageOrganizationVariables = ({ router }) => { + const { data, error, loading, refetch } = useQuery(OrganizationByNameWithEnvVars, { + variables: { name: router.query.organizationSlug }, + }); + + const handleRefetch = async () => await refetch({ name: data.organization.name }); + + if (loading || !data) { + return ( + <> + + + {router.query.organizationSlug ? `${router.query.organizationSlug} | Organization` : 'Organization'} + + + + + + + + + +
    +
    + A deployment is required to apply any changes to Organization variables. +
    + +
    +
    +
    +
    + + ); + } + + if (error || error) { + return ; + } + + const organization = data.organization; + + return ( + <> + + {`${organization.name} | Organization`} + + + + + + + + +
    +
    + A deployment is required to apply any changes to Organization variables. +
    + +
    +
    +
    +
    + + ); +}; + +export default withRouter(PageOrganizationVariables);