diff --git a/schema.graphql b/schema.graphql index 443d4ed..5184ce9 100644 --- a/schema.graphql +++ b/schema.graphql @@ -38,6 +38,7 @@ type Environment { packages: [Package!]! state: State tags: [String!]! + hidden: Boolean! requested: DateTime buildStart: DateTime buildDone: DateTime @@ -72,6 +73,12 @@ type Group { name: String! } +union HiddenResponse = HiddenSuccess | InvalidInputError | EnvironmentNotFoundError + +type HiddenSuccess implements Success { + message: String! +} + type InvalidInputError implements Error { message: String! } @@ -95,6 +102,7 @@ type SchemaMutation { createEnvironment(env: EnvironmentInput!): CreateResponse! deleteEnvironment(name: String!, path: String!): DeleteResponse! addTag(name: String!, path: String!, tag: String!): AddTagResponse! + setHidden(name: String!, path: String!, hidden: Boolean!): HiddenResponse! createFromModule(file: Upload!, modulePath: String!, environmentPath: String!): CreateResponse! updateFromModule(file: Upload!, modulePath: String!, environmentPath: String!): UpdateResponse! } diff --git a/softpack_core/artifacts.py b/softpack_core/artifacts.py index ab2558c..96fae0d 100644 --- a/softpack_core/artifacts.py +++ b/softpack_core/artifacts.py @@ -156,12 +156,27 @@ def spec(self) -> Box: map(lambda p: Package.from_name(p), info.packages) ) + metadata = self.metadata() + + info["tags"] = getattr(metadata, "tags", []) + info["hidden"] = getattr(metadata, "hidden", False) + info["force_hidden"] = getattr(metadata, "force_hidden", False) + + return info + + def metadata(self) -> Box: + """Returns the metadata for an Environment. + + Should contain keys: + - tags: list[string] + - hidden: boolean + - force_hidden: boolean + """ meta = Box() if Artifacts.meta_file in self.obj: meta = Box.from_yaml(self.obj[Artifacts.meta_file].data) - info["tags"] = getattr(meta, "tags", []) - return info + return meta def __iter__(self) -> Iterator["Artifacts.Object"]: """A generator for returning items under an artifacts. diff --git a/softpack_core/schemas/environment.py b/softpack_core/schemas/environment.py index 4421083..c25697e 100644 --- a/softpack_core/schemas/environment.py +++ b/softpack_core/schemas/environment.py @@ -17,6 +17,7 @@ import starlette.datastructures import strawberry import yaml +from box import Box from fastapi import UploadFile from strawberry.file_uploads import Upload @@ -57,6 +58,11 @@ class AddTagSuccess(Success): """Successfully added tag to environment.""" +@strawberry.type +class HiddenSuccess(Success): + """Successfully set hidden status on environment.""" + + @strawberry.type class DeleteEnvironmentSuccess(Success): """Environment successfully deleted.""" @@ -124,6 +130,15 @@ class BuilderError(Error): ], ) +HiddenResponse = strawberry.union( + "HiddenResponse", + [ + HiddenSuccess, + InvalidInputError, + EnvironmentNotFoundError, + ], +) + DeleteResponse = strawberry.union( "DeleteResponse", [ @@ -306,6 +321,7 @@ class Environment: packages: list[Package] state: Optional[State] tags: list[str] + hidden: bool requested: Optional[datetime.datetime] = None build_start: Optional[datetime.datetime] = None @@ -364,6 +380,9 @@ def from_artifact(cls, obj: Artifacts.Object) -> Optional["Environment"]: """ try: spec = obj.spec() + if spec.force_hidden: + return None + return Environment( id=obj.oid, name=obj.name, @@ -374,6 +393,7 @@ def from_artifact(cls, obj: Artifacts.Object) -> Optional["Environment"]: readme=spec.get("readme", ""), type=spec.get("type", ""), tags=spec.tags, + hidden=spec.hidden, ) except KeyError: return None @@ -586,12 +606,63 @@ def add_tag( return AddTagSuccess(message="Tag already present") tags.add(tag) - metadata = yaml.dump({"tags": sorted(tags)}) + metadata = cls.read_metadata(path, name) + metadata.tags = sorted(tags) + + cls.store_metadata(environment_path, metadata) + + return AddTagSuccess(message="Tag successfully added") + + @classmethod + def read_metadata(cls, path: str, name: str) -> Box: + """Read an environments metadata. + + This method returns the metadata for an environment with the given + path and name. + """ + arts = artifacts.get(Path(path), name) + + if arts is not None: + return arts.metadata() + + return Box() + + @classmethod + def store_metadata(cls, environment_path: Path, metadata: Box) -> None: + """Store an environments metadata. + + This method writes the given metadata to the repo for the + environment path given. + """ tree_oid = artifacts.create_file( - environment_path, artifacts.meta_file, metadata, overwrite=True + environment_path, + artifacts.meta_file, + metadata.to_yaml(), + overwrite=True, ) - artifacts.commit_and_push(tree_oid, "create environment folder") - return AddTagSuccess(message="Tag successfully added") + + artifacts.commit_and_push(tree_oid, "update metadata") + + @classmethod + def set_hidden( + cls, name: str, path: str, hidden: bool + ) -> HiddenResponse: # type: ignore + """This method sets the hidden status for the given environment.""" + environment_path = Path(path, name) + response: Optional[Error] = cls.check_env_exists(environment_path) + if response is not None: + return response + + metadata = cls.read_metadata(path, name) + + if metadata.get("hidden") == hidden: + return HiddenSuccess(message="Hidden metadata already set") + + metadata.hidden = hidden + + cls.store_metadata(environment_path, metadata) + + return HiddenSuccess(message="Hidden metadata set") @classmethod def delete(cls, name: str, path: str) -> DeleteResponse: # type: ignore @@ -847,6 +918,7 @@ class Mutation: createEnvironment: CreateResponse = Environment.create # type: ignore deleteEnvironment: DeleteResponse = Environment.delete # type: ignore addTag: AddTagResponse = Environment.add_tag # type: ignore + setHidden: HiddenResponse = Environment.set_hidden # type: ignore # writeArtifact: WriteArtifactResponse = ( # type: ignore # Environment.write_artifact # ) diff --git a/tests/integration/test_environment.py b/tests/integration/test_environment.py index ca00fc3..949ff4f 100644 --- a/tests/integration/test_environment.py +++ b/tests/integration/test_environment.py @@ -24,6 +24,7 @@ EnvironmentAlreadyExistsError, EnvironmentInput, EnvironmentNotFoundError, + HiddenSuccess, InvalidInputError, Package, State, @@ -553,3 +554,52 @@ def test_tagging(httpx_post, testable_env_input: EnvironmentInput) -> None: example_env = Environment.iter()[0] assert example_env.tags == ["second test", "test"] + + +def test_hidden(httpx_post, testable_env_input: EnvironmentInput) -> None: + example_env = Environment.iter()[0] + assert not example_env.hidden + name, path = example_env.name, example_env.path + + result = Environment.set_hidden(name, path, True) + assert isinstance(result, HiddenSuccess) + assert result.message == "Hidden metadata set" + example_env = Environment.iter()[0] + assert example_env.hidden + + result = Environment.set_hidden(name, path, True) + assert isinstance(result, HiddenSuccess) + assert result.message == "Hidden metadata already set" + example_env = Environment.iter()[0] + assert example_env.hidden + + result = Environment.set_hidden(name, path, False) + assert isinstance(result, HiddenSuccess) + assert result.message == "Hidden metadata set" + example_env = Environment.iter()[0] + assert not example_env.hidden + + result = Environment.set_hidden(name, path, False) + assert isinstance(result, HiddenSuccess) + assert result.message == "Hidden metadata already set" + example_env = Environment.iter()[0] + assert not example_env.hidden + + result = Environment.set_hidden(name, path, True) + assert isinstance(result, HiddenSuccess) + assert result.message == "Hidden metadata set" + example_env = Environment.iter()[0] + assert example_env.hidden + + +def test_force_hidden( + httpx_post, testable_env_input: EnvironmentInput +) -> None: + first_env = Environment.iter()[0] + metadata = Environment.read_metadata(first_env.path, first_env.name) + metadata.force_hidden = True + Environment.store_metadata(Path(first_env.path, first_env.name), metadata) + + new_first = Environment.iter()[0] + + assert first_env.path != new_first.path or first_env.name != new_first.name