diff --git a/.gitignore b/.gitignore index d644fa07..13b3d6cb 100644 --- a/.gitignore +++ b/.gitignore @@ -41,5 +41,9 @@ yarn-error.log* spec-v1.0.yaml data.sql +# local graphql +.graphqlconfig +schema.graphql + # asdf .tool-versions \ No newline at end of file diff --git a/components/dataproducts/access/owner.tsx b/components/dataproducts/access/owner.tsx index 9bd43c48..696e565e 100644 --- a/components/dataproducts/access/owner.tsx +++ b/components/dataproducts/access/owner.tsx @@ -5,7 +5,7 @@ import LoaderSpinner from '../../lib/spinner' import * as React from 'react' import { useState } from 'react' import { Button } from '@navikt/ds-react' -import AccessList from './accessList' +import OwnerAccessList from './ownerAccessList' import ExpiredAccessList from './expiredAccessList' import { AddCircle } from '@navikt/ds-icons' import NewAccessForm from "./newAccessForm"; @@ -33,7 +33,7 @@ const Owner = ({ accessQuery }: OwnerProps) => {
Aktive tilganger - + setShowExpired(!showExpired)}>Utløpte tilganger {showExpired ? : } {showExpired && } diff --git a/components/dataproducts/access/accessList.tsx b/components/dataproducts/access/ownerAccessList.tsx similarity index 95% rename from components/dataproducts/access/accessList.tsx rename to components/dataproducts/access/ownerAccessList.tsx index 386260fd..448edc32 100644 --- a/components/dataproducts/access/accessList.tsx +++ b/components/dataproducts/access/ownerAccessList.tsx @@ -52,7 +52,7 @@ interface AccessListProps { requesters: string[], } -const AccessList = ({ id, access, requesters }: AccessListProps) => { +const OwnerAccessList = ({ id, access, requesters }: AccessListProps) => { const [revokeAccess] = useRevokeAccessMutation() const [removeRequester] = useRemoveRequesterMutation() const removeAccess = async (id: string, a: AccessEntry) => { @@ -95,7 +95,7 @@ const AccessList = ({ id, access, requesters }: AccessListProps) => { Bruker / gruppe - Kan be om tilgang + Kan gi seg selv tilgang Har tilgang Fjern tilgang @@ -118,4 +118,4 @@ const AccessList = ({ id, access, requesters }: AccessListProps) => { ) } -export default AccessList +export default OwnerAccessList diff --git a/components/dataproducts/access/user.tsx b/components/dataproducts/access/user.tsx index 7b0971e8..1baa7d98 100644 --- a/components/dataproducts/access/user.tsx +++ b/components/dataproducts/access/user.tsx @@ -1,5 +1,5 @@ import { QueryResult } from '@apollo/client' -import { DataproductAccessQuery, Exact, useRevokeAccessMutation } from '../../../lib/schema/graphql' +import { DataproductAccessQuery, Exact, useRevokeAccessMutation, } from '../../../lib/schema/graphql' import ErrorMessage from '../../lib/error' import LoaderSpinner from '../../lib/spinner' import * as React from 'react' @@ -7,8 +7,10 @@ import { useState } from 'react' import { isAfter, parseISO } from 'date-fns' import humanizeDate from '../../../lib/humanizeDate' import { Alert, Button } from '@navikt/ds-react' -import { Delete } from '@navikt/ds-icons' +import {AddCircle, Delete} from '@navikt/ds-icons' import AddAccess from './addAccess' +import SubHeader from "../../lib/subHeader"; +import UserAccessList from "./userAccessList"; interface UserProps { accessQuery: QueryResult> @@ -42,48 +44,23 @@ const User = ({ accessQuery, currentUser, groups }: UserProps) => { } // Has access - if (activeAccess) return (<> - {formError && {formError}} - Du har tilgang til dette produktet{' '} - {activeAccess.expires && <>til {humanizeDate(activeAccess.expires)}} - {' '}{group && `via gruppen ${group}`} -
- {personalAccess && - } - ) - // Can request access - if (dataproduct.requesters.length > 0) { - if (dataproduct.requesters.includes(currentUser) || groups.some(g => dataproduct.requesters.includes(g))){ - return ( - <> - Du kan gi deg selv tidsbegrenset tilgang. -
- - - )}} - return ( - <> - Du har visst ikke tilgang til disse dataene. -

- Ta kontakt med eieren av produktet (team {dataproduct.owner.group.split('@')[0]}), så finner dere en løsning. -

- {dataproduct.owner?.teamkatalogenURL && ( - - Team {dataproduct.owner.group.split('@')[0]} i Teamkatalogen - - )} - ) + <> + +
+ {false && <> + Søknader + + + } + {dataproduct.access.length != 0 && <> + Aktive tilganger + + +} + + ) } export default User \ No newline at end of file diff --git a/components/dataproducts/access/userAccessList.tsx b/components/dataproducts/access/userAccessList.tsx new file mode 100644 index 00000000..0c3273b1 --- /dev/null +++ b/components/dataproducts/access/userAccessList.tsx @@ -0,0 +1,95 @@ +import {Table, TableBody, TableCell, TableHead, TableRow} from '@mui/material' +import * as React from 'react' +import {useState} from 'react' +import {Delete, Error, Success} from '@navikt/ds-icons' +import {navGronn, navRod} from '../../../styles/constants' +import humanizeDate from '../../../lib/humanizeDate' +import {isAfter, parseISO} from 'date-fns' +import {useRemoveRequesterMutation, useRevokeAccessMutation} from '../../../lib/schema/graphql' +import {Alert} from '@navikt/ds-react' +import {Accessibility} from "@mui/icons-material"; + +interface AccessEntry { + subject: string, + access?: access, +} + +const productAccess = (access: access[]): AccessEntry[] => { + // Valid access entries are unrevoked and either eternal or expires in the future + const valid = (a: access) => !a.revoked && (!a.expires || isAfter(parseISO(a.expires), Date.now())) + return access.filter(valid).map(a => { + return { + access: a, + subject: a.subject.split(":")[1], + }; + }) +} + +interface access { + id: string; + subject: string; + granter: string; + expires?: any; + created: any; + revoked?: any; +} + +interface request { + +} + +interface AccessListProps { + id: string, + access: access[], + requests: request[] +} + +const UserAccessList = ({ id, access, requests}: AccessListProps) => { + const [revokeAccess] = useRevokeAccessMutation() + const removeAccess = async (id: string, a: AccessEntry) => { + if (a.access) { + try { + await revokeAccess({ + variables: { id: a.access.id }, + refetchQueries: ['DataproductAccess'], + }, + ) + } catch (e: any) { + setFormError(e.message) + } + } + + } + + const [formError, setFormError] = useState('') + const accesses = productAccess(access) + + return ( +
+ {formError && {formError}} + + + + Bruker / gruppe + {accesses.length != 0 && Har tilgang til} + Din søknad + Fjern tilgang + + + + {accesses.map((a, i) => + {a.subject} + {accesses.length != 0 && {a.access ? <>{a.access.expires ? humanizeDate(a.access.expires) : 'evig'} : + } + } + show + removeAccess(id, a)}/> + )} + + +
+
+ ) +} +export default UserAccessList diff --git a/lib/queries/accessRequest/accessRequestsForOwner.ts b/lib/queries/accessRequest/accessRequestsForOwner.ts new file mode 100644 index 00000000..fa918295 --- /dev/null +++ b/lib/queries/accessRequest/accessRequestsForOwner.ts @@ -0,0 +1,18 @@ +import { gql } from 'graphql-tag' + +export const ACCESS_REQUESTS_FOR_OWNER = gql` + query AccessRequestsForOwner { + accessRequestsForOwner { + id + dataproductID + subject + subjectType + polly { + id + externalID + name + url + } + } + } +` diff --git a/lib/schema/graphql.ts b/lib/schema/graphql.ts index 7f3a0784..b72111c4 100644 --- a/lib/schema/graphql.ts +++ b/lib/schema/graphql.ts @@ -35,14 +35,29 @@ export type Access = { granter: Scalars['String'] /** id for the access entry */ id: Scalars['ID'] - /** polly is the documentation for the access grant */ - polly?: Maybe /** revoked is timestamp for when access was revoked */ revoked?: Maybe /** subject to grant access */ subject: Scalars['String'] } +/** AccessRequest contains metadata on a request to access a dataproduct */ +export type AccessRequest = { + __typename?: 'AccessRequest' + /** id of dataproduct. */ + dataproductID: Scalars['ID'] + /** id of access request. */ + id: Scalars['ID'] + /** owner of the access request */ + owner?: Maybe + /** polly is the process policy attached to this grant */ + polly?: Maybe + /** subject to be granted access. */ + subject?: Maybe + /** subjectType is the type of entity which should be granted access (user, group or service account). */ + subjectType?: Maybe +} + /** BigQuery contains metadata on a BigQuery table. */ export type BigQuery = { __typename?: 'BigQuery' @@ -180,12 +195,24 @@ export type Mutation = { * Requires authentication. */ addRequesterToDataproduct: Scalars['Boolean'] + /** + * createAccessRequest creates a new access request for a dataproduct + * + * Requires authentication + */ + createAccessRequest: AccessRequest /** * createDataproduct creates a new dataproduct * * Requires authentication. */ createDataproduct: Dataproduct + /** + * deleteAccessRequest deletes a dataproduct access request. + * + * Requires authentication + */ + deleteAccessRequest: Scalars['Boolean'] /** * deleteDataproduct deletes a dataproduct. * @@ -230,6 +257,12 @@ export type Mutation = { * Requires authentication. */ revokeAccessToDataproduct: Scalars['Boolean'] + /** + * createAccessRequest creates a new access request for a dataproduct + * + * Requires authentication + */ + updateAccessRequest: AccessRequest /** * updateDataproduct updates an existing dataproduct * @@ -249,10 +282,18 @@ export type MutationAddRequesterToDataproductArgs = { subject: Scalars['String'] } +export type MutationCreateAccessRequestArgs = { + input: NewAccessRequest +} + export type MutationCreateDataproductArgs = { input: NewDataproduct } +export type MutationDeleteAccessRequestArgs = { + id: Scalars['ID'] +} + export type MutationDeleteDataproductArgs = { id: Scalars['ID'] } @@ -287,6 +328,10 @@ export type MutationRevokeAccessToDataproductArgs = { id: Scalars['ID'] } +export type MutationUpdateAccessRequestArgs = { + input: UpdateAccessRequest +} + export type MutationUpdateDataproductArgs = { id: Scalars['ID'] input: UpdateDataproduct @@ -299,6 +344,20 @@ export type MutationUpdateStoryMetadataArgs = { teamkatalogenURL?: Maybe } +/** NewAccessRequest contains metadata on a request to access a dataproduct */ +export type NewAccessRequest = { + /** id of dataproduct. */ + dataproductID: Scalars['ID'] + /** owner is the owner of the access request */ + owner?: Maybe + /** polly is the process policy attached to this grant */ + polly?: Maybe + /** subject to be granted access. */ + subject?: Maybe + /** subjectType is the type of entity which should be granted access (user, group or service account). */ + subjectType?: Maybe +} + /** NewBigQuery contains metadata for creating a new bigquery data source */ export type NewBigQuery = { /** dataset is the name of the dataset. */ @@ -331,20 +390,27 @@ export type NewDataproduct = { teamkatalogenURL?: Maybe } -/** NewGrant contains metadata on a dataproduct grant */ +/** NewGrant contains metadata on a request to access a dataproduct */ export type NewGrant = { /** id of dataproduct. */ dataproductID: Scalars['ID'] /** expires is a timestamp for when the access expires. */ expires?: Maybe - /** polly is the process policy attached to this grant */ - polly?: Maybe /** subject to be granted access. */ subject?: Maybe /** subjectType is the type of entity which should be granted access (user, group or service account). */ subjectType?: Maybe } +export type NewPolly = { + /** id from polly */ + externalID: Scalars['String'] + /** name from polly */ + name: Scalars['String'] + /** url from polly */ + url: Scalars['String'] +} + export type NewStory = { /** group is the owner group for the story. */ group: Scalars['String'] @@ -367,19 +433,12 @@ export type Owner = { teamkatalogenURL?: Maybe } -export type PollyInput = { +export type Polly = { + __typename?: 'Polly' /** id from polly */ - id: Scalars['String'] - /** name from polly */ - name: Scalars['String'] - /** url from polly */ - url: Scalars['String'] -} - -export type PollyResult = { - __typename?: 'PollyResult' - /** id from polly */ - id: Scalars['String'] + externalID: Scalars['String'] + /** database id */ + id: Scalars['ID'] /** name from polly */ name: Scalars['String'] /** url from polly */ @@ -388,6 +447,12 @@ export type PollyResult = { export type Query = { __typename?: 'Query' + /** accessRequest returns one specific access request */ + accessRequest: AccessRequest + /** accessRequests returns all access requests for a dataproduct */ + accessRequestsForDataproduct: Array + /** accessRequests returns all access requests for an owner */ + accessRequestsForOwner: Array /** dataproduct returns the given dataproduct. */ dataproduct: Dataproduct /** dataproducts returns a list of dataproducts. Pagination done using the arguments. */ @@ -409,7 +474,8 @@ export type Query = { /** Keywords returns all keywords, with an optional filter */ keywords: Array /** searches polly for process purposes matching query input */ - polly: Array + + polly: Array /** search through existing dataproducts. */ search: Array /** stories returns all either draft or published stories depending on the draft boolean. */ @@ -432,6 +498,14 @@ export type Query = { version: Scalars['String'] } +export type QueryAccessRequestArgs = { + id: Scalars['ID'] +} + +export type QueryAccessRequestsForDataproductArgs = { + dataproductID: Scalars['ID'] +} + export type QueryDataproductArgs = { id: Scalars['ID'] } @@ -491,6 +565,16 @@ export type QueryTeamkatalogenArgs = { q: Scalars['String'] } +export type QueryPolly = { + __typename?: 'QueryPolly' + /** id from polly */ + externalID: Scalars['String'] + /** name from polly */ + name: Scalars['String'] + /** url from polly */ + url: Scalars['String'] +} + export type SearchOptions = { /** groups filters results on the group. */ groups?: Maybe> @@ -655,6 +739,18 @@ export type TeamkatalogenResult = { url: Scalars['String'] } +/** UpdateAccessRequest contains metadata on a request to access a dataproduct */ +export type UpdateAccessRequest = { + /** id of access request. */ + id: Scalars['ID'] + /** newPolly is the new polly documentation for this access request. */ + newPolly?: Maybe + /** owner is the owner of the access request. */ + owner: Scalars['String'] + /** pollyID is the id of the existing polly documentation. */ + pollyID?: Maybe +} + /** UpdateDataproduct contains metadata for updating a dataproduct */ export type UpdateDataproduct = { /** description of the dataproduct */ diff --git a/pages/dataproduct/[id]/[[...page]].tsx b/pages/dataproduct/[id]/[[...page]].tsx index 5959a303..a658396a 100644 --- a/pages/dataproduct/[id]/[[...page]].tsx +++ b/pages/dataproduct/[id]/[[...page]].tsx @@ -125,19 +125,36 @@ const Dataproduct = (props: DataproductProps) => { component: ( ), - }, - { + } + ]; + + if (userInfo && accessType.type == "owner") { + menuItems.push({ title: 'tilganger', slug: 'access', - component: !userInfo ? <>Du må logge inn for å gjøre noe her : userInfo && isOwner ? - : g.email)}/>, - }, - { + component: , + }) + } + + if (userInfo && accessType.type == "user") { + menuItems.push({ + title: 'dine tilganger', + slug: 'your-accesses', + component: g.email)}/>, + }) + } + + if (userInfo && ["user", "owner"].includes(accessType.type)) { + menuItems.push({ title: 'utforsk', slug: 'explore', component: , - }, - ] + }) + } + + if (userInfo && accessType.type != "none") { + + } const currentPage = menuItems .map((e) => e.slug) @@ -195,7 +212,7 @@ const Dataproduct = (props: DataproductProps) => { error={deleteError} /> - + )