{"id":26711662,"url":"https://github.com/swisslife-oss/hotchocolate.extensions","last_synced_at":"2025-04-13T19:51:04.619Z","repository":{"id":63769351,"uuid":"316999439","full_name":"SwissLife-OSS/HotChocolate.Extensions","owner":"SwissLife-OSS","description":"A collection of extensions for HotChocolate GraphQL. Includes a Translation feature and a simple Tracking Feature that can be used for KPIs","archived":false,"fork":false,"pushed_at":"2025-02-24T15:45:45.000Z","size":187,"stargazers_count":5,"open_issues_count":12,"forks_count":1,"subscribers_count":3,"default_branch":"main","last_synced_at":"2025-03-24T01:17:50.571Z","etag":null,"topics":["graphql","hotchocolate","kpi","tracking"],"latest_commit_sha":null,"homepage":"","language":"C#","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/SwissLife-OSS.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2020-11-29T16:57:53.000Z","updated_at":"2024-10-17T09:18:28.000Z","dependencies_parsed_at":"2024-06-28T06:48:48.758Z","dependency_job_id":"a459e9cc-f878-461b-981f-88f499249238","html_url":"https://github.com/SwissLife-OSS/HotChocolate.Extensions","commit_stats":null,"previous_names":[],"tags_count":21,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/SwissLife-OSS%2FHotChocolate.Extensions","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/SwissLife-OSS%2FHotChocolate.Extensions/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/SwissLife-OSS%2FHotChocolate.Extensions/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/SwissLife-OSS%2FHotChocolate.Extensions/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/SwissLife-OSS","download_url":"https://codeload.github.com/SwissLife-OSS/HotChocolate.Extensions/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248773753,"owners_count":21159517,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["graphql","hotchocolate","kpi","tracking"],"created_at":"2025-03-27T10:30:08.881Z","updated_at":"2025-04-13T19:51:04.595Z","avatar_url":"https://github.com/SwissLife-OSS.png","language":"C#","funding_links":[],"categories":[],"sub_categories":[],"readme":"\n[![Reliability Rating](https://sonarcloud.io/api/project_badges/measure?project=SwissLife-OSS_HotChocolate_Extensions\u0026metric=reliability_rating)](https://sonarcloud.io/summary/new_code?id=SwissLife-OSS_HotChocolate_Extensions) [![Coverage](https://sonarcloud.io/api/project_badges/measure?project=SwissLife-OSS_HotChocolate_Extensions\u0026metric=coverage)](https://sonarcloud.io/summary/new_code?id=SwissLife-OSS_HotChocolate_Extensions) [![Lines of Code](https://sonarcloud.io/api/project_badges/measure?project=SwissLife-OSS_HotChocolate_Extensions\u0026metric=ncloc)](https://sonarcloud.io/summary/new_code?id=SwissLife-OSS_HotChocolate_Extensions) [![Security Rating](https://sonarcloud.io/api/project_badges/measure?project=SwissLife-OSS_HotChocolate_Extensions\u0026metric=security_rating)](https://sonarcloud.io/summary/new_code?id=SwissLife-OSS_HotChocolate_Extensions) \n\nThis repository contains features that can be added to your HotChocolate Server to enhance your HotChocolate experience.\n\n- [HotChocolate.Extensions.Translations](#hotchocolateextensionstranslations)\n  * [Install](#install)\n  * [Translating fields](#translating-fields)\n    + [Translation on demand](#translation-on-demand)\n    + [Translation to a { key label } type](#translation-to-a---key-label---type)\n    + [Translate arrays to { key label } items](#translate-arrays-to---key-label---items)\n  * [FAQ](#faq)\n    + [What language is used by default by this extension?](#what-language-is-used-by-default-by-this-extension-)\n    + [What is the performance impact of translations?](#what-is-the-performance-impact-of-translations-)\n- [HotChocolate.Extensions.Tracking](#hotchocolateextensionstracking)\n  * [Setting up the depencies and schema](#setting-up-the-depencies-and-schema)\n  * [Basic tracking by Tags](#basic-tracking-by-tags)\n  * [Custom tracking](#custom-tracking)\n- [Community](#community)\n\n\n## HotChocolate.Extensions.Translations\n\nThis package uses HotChocolate directives and middlewares to add translations to your HotChocolate server. \nEasily extend your fields to transform their code-value (for instance \"CH\") into a more presentable value for your consumer (\"Switzerland\"/\"Schweiz\"/\"Suisse\"...). \n\nThe resource strings containing the translations can be provided from any source you like: external microservices, from memory, resx files...\n\n### Install\n\nYou will need to include the following package on your HotChocolate Server:\n\n```bash\ndotnet add package HotChocolate.Extensions.Translation\n```\n\nThe next step is to register some directives to your GraphQL Schema:\n```csharp\nnew ServiceCollection()\n  .AddGraphQLServer()\n  .SetSchema\u003cMySchema\u003e()\n  .AddTranslation(\n    /* add all translatable types explicitely, except String, which is already added implicitely. */\n    c =\u003e c.AddTranslatableType\u003cCountry\u003e()\n          .AddTranslatableType\u003cMyEnum2\u003e()\n  );\n```\n\nThe last step is to implement and register an IResourcesProvider. This provider will be used to retrieve the string resources in the target language. \n```csharp\nservices.AddSingleton\u003cIResourcesProvider, MyResourcesProvider\u003e();\n```\n\nThe alternative way is to implement IStringLocalizer interfaces.\nAdditionally, we can use resource type marker classes.\nThis approach overrides all logic related to the usage of the IResourcesProvider interface.\n```csharp\nnamespace Namespace.Namespace1.Namespace2;\n\n[ResourceTypeAlias(\"SomePath\")]\npublic class CustomResource\n{\n}\n\nnew ServiceCollection()\n  .AddGraphQLServer()\n  .SetSchema\u003cMySchema\u003e()\n  .AddTranslation(    \n      /* add all translatable types explicitely, except String, which is already added implicitely. */\n      c =\u003e c.AddTranslatableType\u003cCountry\u003e()\n            .AddTranslatableType\u003cMyEnum2\u003e()\n  )\n  .AddStringLocalizer\u003cCustomLocalizer\u003e(ServiceLifetime.Singleton, typeof(CustomResource))\n  .AddStringLocalizer\u003cOtherCustomLocalizer\u003e(ServiceLifetime.Scoped, [ typeof(OtherCustomResource) /* additional resources can share the same localizer logic */ ])\n  .AddStringLocalizer(typeof(OpenGenericLocalizer\u003c\u003e), [ typeof(AnyResource), ......]);\n```\n\nIf the resource type is not decorated with the ResourceTypeAlias attribute,\nthe default alias is generated from the namespace and name of the resource class.\nFor the case described above, it would generate the alias \"Namespace::Namespace1::Namespace2::CustomResource\".\n\n\nWith this we have registered the necessary objects to support translation on our fields. Now we can start adding translation support to our fields.\n\n### Translating fields\n\nYou can translate field values in several ways, listed below.\n\n#### Translation to a { key label } type\n\nWe can also rewrite our string/enum/other field to make it a `{ key label }` field.\nThis can be useful if we also use Array Translations, which use the same typing (see next chapter).\n\n```csharp\n[ResourceTypeAlias(\"Ref/Aex/Countries\"))]\npublic class CountryResource\n{\n}\n\npublic class AddressType : ObjectType\u003cQuery\u003e\n{\n    protected override void Configure(IObjectTypeDescriptor\u003cQuery\u003e descriptor)\n    {\n        descriptor\n            .Field(c =\u003e c.Country) // The Country property is a Country enum\n            .Translate(\"Ref/Aex/Countries\");\n       \n       // OR\n       // descriptor\n       //     .Field(c =\u003e c.Country)\n       //     .Translate(typeof(CountryResource));\n    }\n}\n```\n\nAlternatively it is possible to translate the field via an attribute directly on the property:\n```csharp\n[ResourceTypeAlias(\"Ref/Aex/Countries\"))]\npublic class CountryResource\n{\n}\n\npublic class Address\n{\n    [Translate(\"Ref/Aex/Countries\")]\n    public Country Country { get; }\n    \n    \n    // OR\n    // [Translate(typeof(CountryResource)]\n    // public Country Country { get; }\n}\n```\n\nIn both cases, this will generate the following Schema:\n```graphql\ntype Query {\n  country: TranslatedResourceOfCountry!\n}\n\ntype TranslatedResourceOfCountry {\n  key: Country!\n  label: String!\n}\n```\n\nQuerying this field will produce the following results:\n```graphql\n{\n  country {\n    key // -\u003e \"CH\",\n    label // -\u003e \"Switzerland\" if your Thread language is english\n  }\n}\n```\n\n#### Translate arrays to { key label } items\n\nWe can translate string/enum/other arrays to `{ key label }` arrays.\n\n```csharp\npublic class AddressType : ObjectType\u003cQuery\u003e\n{\n    protected override void Configure(IObjectTypeDescriptor\u003cQuery\u003e descriptor)\n    {\n        descriptor.Field(c =\u003e c.Countries)\n            .TranslateArray\u003cCountry\u003e(\"Ref/Aex/Countries\");\n    }\n}\n```\nAlternatively it is possible to translate the field via an attribute directly on the property:\n```csharp\npublic class Address\n{\n    [TranslateArray\u003cCountry\u003e(\"Ref/Aex/Countries\")]\n    public Country[] Countries { get; }\n}\n```\n\nThis will generate the following Schema:\n```graphql\ntype Query {\n  countries: [TranslatedResourceOfCountry!]!\n}\n\ntype TranslatedResourceOfCountry {\n  key: Country!\n  label: String!\n}\n```\n\nQuerying this field will produce the following results:\n```graphql\n{\n  countries {\n    key\n    label \n  }\n}\n```\n\n```json\n{\n  countries: [\n    {\n      key: \"FR\",\n      label: \"France\"\n    },\n    {\n      key: \"CH\",\n      label: \"Switzerland\"\n    }\n}\n```\n\n\n### FAQ\n\n#### What language is used by default by this extension?\n\nThe language of the thread, which you can for instance initialize via the [ASP.NET Core Localization Middleware](https://docs.microsoft.com/en-us/aspnet/core/fundamentals/localization?view=aspnetcore-5.0#localization-middleware-2).\n\n#### What is the performance impact of translations?\n\nThis will depend in large part on your implementation of ```IResourcesProvider```. This provider is registered in the service container and can therefore use any registered service of your applicaton. \n\nIn our samples, we usually build our resource strings into our assemblies so that we can retrieve them very quickly from the memory.\n\nIf you are retrieving your resource strings from another microservice, you could consider injecting [Greendonut DataLoaders](https://github.com/ChilliCream/greendonut) or [IMemoryCache](https://docs.microsoft.com/en-us/aspnet/core/performance/caching/memory?view=aspnetcore-5.0) to reduce calls to the external microservice. \n\n\n## HotChocolate.Extensions.Tracking\n***\n\nThis project enables tracking via middlewares on HotChocolate Query and mutation fields.\nIn order to save the tracking items, a custom repository needs to be injected. \nAs for now, the only provided repository is the MassTransitRepository, which essentially sends the tracking item to an azure ServiceBus topic.\n\nNote: In order to not slow down the resolver pipelines of Tracked HotChocolate Fields, the context data of the tracked Field is passed into a System.Threading.Channels.Channel and then persisted asynchonously.\nAs a consequence, some tracking items could be lost if the server were to shut down unexpectedly.\n\n### Setting up the depencies and schema\n\nRegister the tracking pipeline in the GraphQL Schema.\n```\n  Schema schema = await services\n            .AddGraphQLServer()\n                .AddTrackingPipeline(builder =\u003e builder\n                    .AddExporter\u003cNotifyOnFirstEntryExporter\u003e()\n                        .AddSupportedType\u003cMyTrace\u003e()\n                        .AddSupportedType\u003cMyOtherTrace\u003e());\n```\n\n### Basic tracking by Tags\n\nThe basic field invocation tracking produces a message with the following data:\n- a tag\n- a timestamp\n\nYou can add tracking on a field in the following way:\n```csharp\nfieldDescriptor.Field(\"myField\").Track(tag:\"myFieldWasCalled\");\n```\n\nAlternatively, you can also add tracking on the field via an attribute on the field/resolver:\n```csharp\npublic class Query\n{\n    [Track(\"FooInvoked\")]\n    public string Foo =\u003e \"bar\";\n}\n```\n\n\n### Custom tracking\n\nIf you want to save custom data fields, you have the possibility to write your own TrackingEntryFactory. \n\n```\n  public sealed class MyTrackingEntryFactory : ITrackingEntryFactory\n  {\n    public ITrackingEntry CreateTrackingEntry(\n        IHttpContextAccessor httpContextAccessor,\n        IResolverContext context)\n    {\n      // Get data from httpContextAccessor or the resolver context and create and return a custom ITrackingEntry from it.\n    }\n```\n\nYou can then add custom tracking to your fields:\n```fieldDescriptor.Field(\"myField\").Track(new MyTrackingEntryFactory());```\n\nOr alternatively via an attribute:\n```csharp\npublic class Query\n{\n    [Track\u003cMyTrackingEntryFactory\u003e]\n    public string Foo =\u003e \"bar\";\n}\n```\n\n\n## Community\n\nThis project has adopted the code of conduct defined by the [Contributor Covenant](https://contributor-covenant.org/)\nto clarify expected behavior in our community. For more information, see the [Swiss Life OSS Code of Conduct](https://swisslife-oss.github.io/coc).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fswisslife-oss%2Fhotchocolate.extensions","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fswisslife-oss%2Fhotchocolate.extensions","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fswisslife-oss%2Fhotchocolate.extensions/lists"}