├── .github
├── ISSUE_TEMPLATE
│ ├── bug_report.md
│ └── feature_request.md
└── workflows
│ └── php.yml
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── LICENSE.txt
├── README.md
├── apigee_api_catalog.info.yml
├── apigee_api_catalog.install
├── apigee_api_catalog.libraries.yml
├── apigee_api_catalog.links.action.yml
├── apigee_api_catalog.links.menu.yml
├── apigee_api_catalog.links.task.yml
├── apigee_api_catalog.module
├── apigee_api_catalog.routing.yml
├── apigee_api_catalog.services.yml
├── codecov.yml
├── composer.json
├── config
├── install
│ ├── core.base_field_override.node.apidoc.title.yml
│ ├── core.entity_form_display.node.apidoc.default.yml
│ ├── core.entity_view_display.node.apidoc.default.yml
│ ├── field.field.node.apidoc.body.yml
│ ├── field.field.node.apidoc.field_api_product.yml
│ ├── field.field.node.apidoc.field_apidoc_fetched_timestamp.yml
│ ├── field.field.node.apidoc.field_apidoc_file_link.yml
│ ├── field.field.node.apidoc.field_apidoc_spec.yml
│ ├── field.field.node.apidoc.field_apidoc_spec_file_source.yml
│ ├── field.field.node.apidoc.field_apidoc_spec_md5.yml
│ ├── field.storage.node.field_api_product.yml
│ ├── field.storage.node.field_apidoc_fetched_timestamp.yml
│ ├── field.storage.node.field_apidoc_file_link.yml
│ ├── field.storage.node.field_apidoc_spec.yml
│ ├── field.storage.node.field_apidoc_spec_file_source.yml
│ ├── field.storage.node.field_apidoc_spec_md5.yml
│ └── node.type.apidoc.yml
└── optional
│ ├── views.view.api_catalog_admin.yml
│ └── views.view.apigee_api_catalog.yml
├── js
└── smartdocs_integration.js
├── modules
├── apigee_asyncapi_doc
│ ├── apigee_asyncapi_doc.info.yml
│ ├── apigee_asyncapi_doc.install
│ ├── apigee_asyncapi_doc.libraries.yml
│ ├── apigee_asyncapi_doc.links.action.yml
│ ├── apigee_asyncapi_doc.module
│ ├── apigee_asyncapi_doc.routing.yml
│ ├── apigee_asyncapi_doc.services.yml
│ ├── config
│ │ ├── install
│ │ │ ├── field.field.node.asyncapi_doc.body.yml
│ │ │ ├── field.field.node.asyncapi_doc.field_api_product.yml
│ │ │ ├── field.field.node.asyncapi_doc.field_asyncapi_spec.yml
│ │ │ ├── field.field.node.asyncapi_doc.field_asyncapi_spec_file_link.yml
│ │ │ ├── field.field.node.asyncapi_doc.field_asyncapi_spec_source_type.yml
│ │ │ ├── field.storage.node.field_asyncapi_spec.yml
│ │ │ ├── field.storage.node.field_asyncapi_spec_file_link.yml
│ │ │ ├── field.storage.node.field_asyncapi_spec_source_type.yml
│ │ │ └── node.type.asyncapi_doc.yml
│ │ └── optional
│ │ │ ├── core.entity_form_display.node.asyncapi_doc.default.yml
│ │ │ └── core.entity_view_display.node.asyncapi_doc.default.yml
│ ├── css
│ │ └── apigee-asyncapi-doc.css
│ ├── src
│ │ ├── ApigeeAsyncapiDocBreadcrumbBuilder.php
│ │ ├── ApigeeAsyncapiDocUninstallValidator.php
│ │ ├── EventSubscriber
│ │ │ └── ApigeeAsyncapiDocSubscriber.php
│ │ ├── Form
│ │ │ └── ConfirmRemoveViewsReferencesForm.php
│ │ └── Plugin
│ │ │ └── Field
│ │ │ └── FieldFormatter
│ │ │ └── AsyncapiFormatter.php
│ └── templates
│ │ └── apigee-asyncapi-doc-file-link-field-item.html.twig
├── apigee_freeform_doc
│ ├── apigee_freeform_doc.info.yml
│ ├── apigee_freeform_doc.install
│ ├── apigee_freeform_doc.links.action.yml
│ ├── apigee_freeform_doc.routing.yml
│ ├── apigee_freeform_doc.services.yml
│ ├── config
│ │ ├── install
│ │ │ ├── field.field.node.freeform_doc.body.yml
│ │ │ ├── field.field.node.freeform_doc.field_api_product.yml
│ │ │ ├── field.field.node.freeform_doc.field_freeform_spec_doc.yml
│ │ │ ├── field.storage.node.field_freeform_spec_doc.yml
│ │ │ └── node.type.freeform_doc.yml
│ │ └── optional
│ │ │ ├── core.entity_form_display.node.freeform_doc.default.yml
│ │ │ └── core.entity_view_display.node.freeform_doc.default.yml
│ └── src
│ │ ├── ApigeeFreeformDocBreadcrumbBuilder.php
│ │ ├── ApigeeFreeformDocUninstallValidator.php
│ │ ├── EventSubscriber
│ │ └── ApigeeFreeformDocSubscriber.php
│ │ └── Form
│ │ └── ConfirmRemoveViewsReferencesForm.php
└── apigee_graphql_doc
│ ├── apigee_graphql_doc.info.yml
│ ├── apigee_graphql_doc.install
│ ├── apigee_graphql_doc.libraries.yml
│ ├── apigee_graphql_doc.links.action.yml
│ ├── apigee_graphql_doc.module
│ ├── apigee_graphql_doc.routing.yml
│ ├── apigee_graphql_doc.services.yml
│ ├── config
│ ├── install
│ │ ├── field.field.node.graphql_doc.body.yml
│ │ ├── field.field.node.graphql_doc.field_api_product.yml
│ │ ├── field.field.node.graphql_doc.field_graphql_spec.yml
│ │ ├── field.field.node.graphql_doc.field_graphql_spec_file_link.yml
│ │ ├── field.field.node.graphql_doc.field_graphql_spec_source_type.yml
│ │ ├── field.storage.node.field_graphql_spec.yml
│ │ ├── field.storage.node.field_graphql_spec_file_link.yml
│ │ ├── field.storage.node.field_graphql_spec_source_type.yml
│ │ └── node.type.graphql_doc.yml
│ └── optional
│ │ ├── core.entity_form_display.node.graphql_doc.default.yml
│ │ └── core.entity_view_display.node.graphql_doc.default.yml
│ ├── css
│ └── apigee-graphql-doc.css
│ ├── js
│ └── apigee-graphql-doc.js
│ ├── src
│ ├── ApigeeGraphqlDocBreadcrumbBuilder.php
│ ├── ApigeeGraphqlDocUninstallValidator.php
│ ├── Controller
│ │ └── ApigeeGraphqlServerController.php
│ ├── EventSubscriber
│ │ └── ApigeeGraphqlDocSubscriber.php
│ ├── Form
│ │ └── ConfirmRemoveViewsReferencesForm.php
│ └── Plugin
│ │ └── Field
│ │ └── FieldFormatter
│ │ └── GraphiqlFormatter.php
│ └── templates
│ └── apigee-graphql-doc-file-link-field-item.html.twig
├── phpcs.xml.dist
├── phpunit.core.xml.dist
├── src
├── ApigeeApiCatalogBreadcrumbBuilder.php
├── Entity
│ └── Form
│ │ └── ApiDocReimportSpecForm.php
├── EventSubscriber
│ └── PageNotFoundEventSubscriber.php
├── Plugin
│ ├── Field
│ │ └── FieldFormatter
│ │ │ └── SmartDocsFormatter.php
│ └── Validation
│ │ └── Constraint
│ │ ├── ApiDocFileLinkConstraint.php
│ │ └── ApiDocFileLinkConstraintValidator.php
├── SpecFetcher.php
├── SpecFetcherInterface.php
└── UpdateService.php
└── tests
└── src
├── Functional
├── ApiDocsAdminTest.php
├── ApiDocsBreadcrumbTest.php
├── ApiDocsJsonApi.php
└── SmartdocRoutingTest.php
└── Kernel
└── ApidocEntityTest.php
/.github/ISSUE_TEMPLATE/bug_report.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Bug report
3 | about: Create a report to help us improve
4 | title: ''
5 | labels: bug
6 | assignees: ''
7 |
8 | ---
9 | ## Description
10 | A clear and concise description of what the bug is.
11 |
12 | ## Steps to Reproduce
13 | Steps to reproduce the behavior:
14 | 1. Go to '...'
15 | 2. Click on '....'
16 | 3. Scroll down to '....'
17 |
18 | ## Actual Behavior
19 | What happened after the steps to reproduce.
20 |
21 | ## Expected Behavior
22 | A clear and concise description of what you expected to happen.
23 |
24 | ## Screenshots
25 | If applicable, add screenshots to help explain your problem.
26 |
27 | ## Notes
28 | Add any other context about the problem here.
29 |
30 | ## Version Info
31 | This can be the version you can see on admin/modules in Drupal or
32 | the output of this command: `composer show`. Add Drupal core and
33 | other version information if needed.
34 |
35 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature_request.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Feature request
3 | about: Suggest an idea for this project
4 | title: ''
5 | labels: enhancement
6 | assignees: ''
7 |
8 | ---
9 |
10 | ## Is your feature request related to a problem? Please describe.
11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
12 |
13 | ## Describe the solution you would like
14 | A clear and concise description of what you want to happen.
15 |
16 | ## Describe alternatives you've considered
17 | A clear and concise description of any alternative solutions or features you've considered.
18 |
19 | ## Additional context
20 | Add any other context or screenshots about the feature request here.
21 |
22 |
--------------------------------------------------------------------------------
/.github/workflows/php.yml:
--------------------------------------------------------------------------------
1 | name: test_and_lint
2 | env:
3 | SIMPLETEST_DB: "sqlite://localhost//tmp/test.sqlite"
4 | SIMPLETEST_BASE_URL: "http://127.0.0.1:8000"
5 | APIGEE_EDGE_AUTH_TYPE: ${{ secrets.APIGEE_EDGE_AUTH_TYPE }}
6 | APIGEE_EDGE_ORGANIZATION: ${{ secrets.APIGEE_EDGE_ORGANIZATION }}
7 | APIGEE_EDGE_USERNAME: ${{ secrets.APIGEE_EDGE_USERNAME }}
8 | APIGEE_EDGE_PASSWORD: ${{ secrets.APIGEE_EDGE_PASSWORD }}
9 | APIGEE_EDGE_INSTANCE_TYPE: ${{ secrets.APIGEE_EDGE_INSTANCE_TYPE }}
10 | APIGEE_INTEGRATION_ENABLE: ${{ secrets.APIGEE_INTEGRATION_ENABLE }}
11 | APIGEE_EDGE_ENDPOINT: ${{ secrets.APIGEE_EDGE_ENDPOINT }}
12 | BROWSERTEST_OUTPUT_DIRECTORY: "sites/simpletest/browser_output"
13 | BROWSERTEST_OUTPUT_BASE_URL: ""
14 | MINK_DRIVER_ARGS_WEBDRIVER: '["chrome", { "chromeOptions": { "w3c": false } }, "http://127.0.0.1:9515/wd/hub"]'
15 |
16 | on:
17 | push:
18 | pull_request_target:
19 | schedule:
20 | - cron: '30 01 * * *'
21 |
22 | jobs:
23 | build:
24 |
25 | runs-on: ubuntu-24.04
26 |
27 | name: "PHP ${{ matrix.php-version }} | Drupal ${{ matrix.drupal-core }}"
28 | strategy:
29 | fail-fast: false
30 | matrix:
31 | php-version:
32 | - "8.1"
33 | - "8.2"
34 | - "8.3"
35 | drupal-core:
36 | # Should update the following as the minimum supported version from Drupal.org
37 | - "10.3.x"
38 | - "11.1.x"
39 | exclude:
40 | - php-version: "8.1"
41 | drupal-core: "11.1.x"
42 | - php-version: "8.2"
43 | drupal-core: "11.1.x"
44 |
45 | steps:
46 |
47 | - name: Disable deprecations check for PR
48 | if: github.event_name != 'schedule'
49 | run: |
50 | echo "Disable deprecations check"
51 | echo "SYMFONY_DEPRECATIONS_HELPER=disabled" >> $GITHUB_ENV
52 |
53 | - name: "Install PHP"
54 | uses: "shivammathur/setup-php@v2"
55 | with:
56 | coverage: "xdebug"
57 | php-version: "${{ matrix.php-version }}"
58 | tools: composer:v2
59 | extensions: date, dom, filter, hash, json, curl, libxml, mbstring, zip, pdo, sqlite3, mysqli, mysql, pdo_mysql, bcmath, gd, exif, iconv, opcache, imagick, openssl
60 |
61 | - name: Checkout Drupal core
62 | run: |
63 | git clone --depth 1 --branch ${{ matrix.drupal-core }} https://github.com/drupal/drupal.git drupal
64 | mkdir -p drupal/modules/contrib/apigee_api_catalog
65 | mkdir -p drupal/sites/simpletest/browser_output
66 |
67 | - name: Checkout apigee_api_catalog module
68 | uses: actions/checkout@v4
69 | with:
70 | path: drupal/modules/contrib/apigee_api_catalog
71 |
72 | - name: "Allow plugins and dev dependencies"
73 | run: |
74 | cd drupal
75 | composer config --no-plugins allow-plugins.composer/installers true
76 | composer config --no-plugins allow-plugins.drupal/core-composer-scaffold true
77 | composer config --no-plugins allow-plugins.drupal/core-project-message true
78 | composer config --no-plugins allow-plugins.dealerdirect/phpcodesniffer-composer-installer true
79 | composer config --no-plugins allow-plugins.wikimedia/composer-merge-plugin true
80 | composer config --no-plugins allow-plugins.composer/package-versions-deprecated true
81 | composer config --no-plugins allow-plugins.cweagans/composer-patches true
82 | composer config --no-plugins allow-plugins.php-http/discovery true
83 | composer config minimum-stability dev
84 | composer require wikimedia/composer-merge-plugin
85 | composer config --json extra.merge-plugin.require '["modules/contrib/apigee_api_catalog/composer.json"]'
86 | composer config platform.php ${{ matrix.php-version }}
87 | composer config --json extra.patches."drupal/core" '{ "Support entities that are neither content nor config entities": "https://www.drupal.org/files/issues/2020-12-02/3042467-50.patch"}'
88 | composer update --with-all-dependencies
89 | composer require --dev phpspec/prophecy-phpunit:^2
90 |
91 | # Install drupal using minimal installation profile and enable the module.
92 | - name: Install Drupal
93 | run: |
94 | cd drupal
95 | php -d sendmail_path=$(which true); vendor/bin/drush --yes -v site-install minimal --db-url="$SIMPLETEST_DB"
96 | vendor/bin/drush rs 8000 &
97 |
98 | - name: "PHPCS"
99 | run: |
100 | cd drupal
101 | cp modules/contrib/apigee_api_catalog/phpcs.xml.dist .
102 | vendor/bin/phpcs --standard=./phpcs.xml.dist modules/contrib/apigee_api_catalog -p -s -n --colors
103 | composer show > composer.show.txt
104 |
105 | - name: Artifacts
106 | uses: actions/upload-artifact@v4
107 | with:
108 | name: composer-${{ matrix.php-version }}-${{ matrix.drupal-core }}-artifact
109 | path: drupal/composer.*
110 |
111 | - uses: nanasess/setup-chromedriver@v2
112 |
113 | - run: |
114 | export DISPLAY=:99
115 | chromedriver --url-base=/wd/hub &
116 | sudo Xvfb -ac :99 -screen 0 1280x1024x24 > /dev/null 2>&1 & # optional
117 |
118 | - name: "Run PHPUnit Tests"
119 | if: ${{ matrix.drupal-core != '10.3.x' || matrix.php-version != '8.2' }}
120 | run: |
121 | cd drupal
122 | vendor/bin/phpunit -c core --color --group apigee_api_catalog --testsuite unit,kernel,functional,functional-javascript modules/contrib/apigee_api_catalog
123 |
124 | - name: "Run PHPUnit Tests with Code Coverage"
125 | if: ${{ matrix.drupal-core == '10.3.x' && matrix.php-version == '8.2' }}
126 | run: |
127 | cd drupal
128 | cp modules/contrib/apigee_api_catalog/phpunit.core.xml.dist core/phpunit.xml
129 | vendor/bin/phpunit -c core --color --group apigee_api_catalog --testsuite unit,kernel,functional,functional-javascript --coverage-clover /tmp/coverage.xml modules/contrib/apigee_api_catalog
130 |
131 | - name: Artifacts
132 | if: failure()
133 | uses: actions/upload-artifact@v4
134 | with:
135 | name: browser-output-${{ matrix.php-version }}-${{ matrix.drupal-core }}-artifact
136 | path: drupal/sites/simpletest/browser_output/*
137 |
138 | - name: Upload Coverage to Codecov
139 | if: ${{ matrix.drupal-core == '10.3.x' && matrix.php-version == '8.2' }}
140 | uses: codecov/codecov-action@v4
141 | with:
142 | token: ${{ secrets.CODECOV_TOKEN }}
143 | files: /tmp/coverage.xml
144 | name: codecov-umbrella
145 | fail_ci_if_error: true
146 | verbose: true
147 |
--------------------------------------------------------------------------------
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | # Code of Conduct
2 |
3 | ## Our Pledge
4 |
5 | In the interest of fostering an open and welcoming environment, we as
6 | contributors and maintainers pledge to making participation in our project and
7 | our community a harassment-free experience for everyone, regardless of age, body
8 | size, disability, ethnicity, gender identity and expression, level of
9 | experience, education, socio-economic status, nationality, personal appearance,
10 | race, religion, or sexual identity and orientation.
11 |
12 | ## Our Standards
13 |
14 | Examples of behavior that contributes to creating a positive environment
15 | include:
16 |
17 | * Using welcoming and inclusive language
18 | * Being respectful of differing viewpoints and experiences
19 | * Gracefully accepting constructive criticism
20 | * Focusing on what is best for the community
21 | * Showing empathy towards other community members
22 |
23 | Examples of unacceptable behavior by participants include:
24 |
25 | * The use of sexualized language or imagery and unwelcome sexual attention or
26 | advances
27 | * Trolling, insulting/derogatory comments, and personal or political attacks
28 | * Public or private harassment
29 | * Publishing others' private information, such as a physical or electronic
30 | address, without explicit permission
31 | * Other conduct which could reasonably be considered inappropriate in a
32 | professional setting
33 |
34 | ## Our Responsibilities
35 |
36 | Project maintainers are responsible for clarifying the standards of acceptable
37 | behavior and are expected to take appropriate and fair corrective action in
38 | response to any instances of unacceptable behavior.
39 |
40 | Project maintainers have the right and responsibility to remove, edit, or reject
41 | comments, commits, code, wiki edits, issues, and other contributions that are
42 | not aligned to this Code of Conduct, or to ban temporarily or permanently any
43 | contributor for other behaviors that they deem inappropriate, threatening,
44 | offensive, or harmful.
45 |
46 | ## Scope
47 |
48 | This Code of Conduct applies both within project spaces and in public spaces
49 | when an individual is representing the project or its community. Examples of
50 | representing a project or community include using an official project e-mail
51 | address, posting via an official social media account, or acting as an appointed
52 | representative at an online or offline event. Representation of a project may be
53 | further defined and clarified by project maintainers.
54 |
55 | This Code of Conduct also applies outside the project spaces when the Project
56 | Steward has a reasonable belief that an individual's behavior may have a
57 | negative impact on the project or its community.
58 |
59 | ## Conflict Resolution
60 |
61 | We do not believe that all conflict is bad; healthy debate and disagreement
62 | often yield positive results. However, it is never okay to be disrespectful or
63 | to engage in behavior that violates the project’s code of conduct.
64 |
65 | If you see someone violating the code of conduct, you are encouraged to address
66 | the behavior directly with those involved. Many issues can be resolved quickly
67 | and easily, and this gives people more control over the outcome of their
68 | dispute. If you are unable to resolve the matter for any reason, or if the
69 | behavior is threatening or harassing, report it. We are dedicated to providing
70 | an environment where participants feel welcome and safe.
71 |
72 | Reports should be directed to Chris Novak [chrisnovak@google.com], the
73 | Project Steward for Apigee API Catalog module for Drupal. It is the Project Steward’s duty to
74 | receive and address reported violations of the code of conduct. They will then
75 | work with a committee consisting of representatives from the Open Source
76 | Programs Office and the Google Open Source Strategy team. If for any reason you
77 | are uncomfortable reaching out the Project Steward, please email
78 | opensource@google.com.
79 |
80 | We will investigate every complaint, but you may not receive a direct response.
81 | We will use our discretion in determining when and how to follow up on reported
82 | incidents, which may range from not taking action to permanent expulsion from
83 | the project and project-sponsored spaces. We will notify the accused of the
84 | report and provide them an opportunity to discuss it before any action is taken.
85 | The identity of the reporter will be omitted from the details of the report
86 | supplied to the accused. In potentially harmful situations, such as ongoing
87 | harassment or threats to anyone's safety, we may take action without notice.
88 |
89 | ## Attribution
90 |
91 | This Code of Conduct is adapted from the Contributor Covenant, version 1.4,
92 | available at
93 | https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
94 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # How to Contribute
2 |
3 | We'd love to accept your patches and contributions to this project. There are
4 | just a few small guidelines you need to follow.
5 |
6 | ## Contributor License Agreement
7 |
8 | Contributions to this project must be accompanied by a Contributor License
9 | Agreement. You (or your employer) retain the copyright to your contribution,
10 | this simply gives us permission to use and redistribute your contributions as
11 | part of the project. Head over to to see
12 | your current agreements on file or to sign a new one.
13 |
14 | You generally only need to submit a CLA once, so if you've already submitted one
15 | (even if it was for a different project), you probably don't need to do it
16 | again.
17 |
18 | ## Code reviews
19 |
20 | All submissions, including submissions by project members, require review. We
21 | use GitHub pull requests for this purpose. Consult
22 | [GitHub Help](https://help.github.com/articles/about-pull-requests/) for more
23 | information on using pull requests.
24 |
25 | ## Community Guidelines
26 |
27 | This project follows [Google's Open Source Community Guidelines](https://opensource.google.com/conduct/).
28 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Apigee API Catalog for Drupal
2 |
3 | Render OpenAPI specs as documentation to your API developers.
4 |
5 | ## Overview
6 |
7 | When you enable this module, it creates a new content type named
8 | "API Doc". You can add new API docs under __Content > API catalog__ in the admin menu.
9 |
10 | Once added, the API name and description for each API Doc will be displayed in the
11 | "APIs" menu item on the site using a Drupal view.
12 |
13 | The OpenAPI spec by default is rendered using Apigee's SmartDocs custom field formatter.
14 |
15 | The OpenAPI spec can be directly uploaded as a file, or associated to a source location
16 | such as Apigee Edge or a URL. A "Re-import OpenAPI spec" operation is available per
17 | API Doc to re-import the spec file when source location changes.
18 |
19 | The OpenAPI spec by default is shown on the API Doc detail page by default.
20 | To render the OpenAPI spec using Swagger UI:
21 |
22 | 1. Install and enable the [Swagger UI Field Formatter](https://www.drupal.org/project/swagger_ui_formatter) module.
23 | 2. Install the Swagger UI JS library as documented [on the module page](https://www.drupal.org/project/swagger_ui_formatter).
24 | 3. Go to __Configuration > API docs > Manage display__ in the admin menu.
25 | 4. Change "OpenAPI specification" field format to use the Swagger UI field formatter.
26 |
27 | The API Doc is an node type, you can configure it at __Structure > Content types > API Doc__ in the admin
28 | menu.
29 |
30 | The "/apis" page ("APIs" menu link) is a view. You can modify it by editing the "API Catalog" view
31 | under __Structure > Views__ in the admin menu.
32 |
33 | ## API Docs RBAC (Role based access control)
34 |
35 | _API Docs_ are a node type, so any node access control module from contrib will work to restrict access and play well
36 | with views.
37 |
38 | To set up RBAC, we recommend ["Permissions by term"](https://www.drupal.org/project/permissions_by_term), which can
39 | cover the most frequent scenarios. Refer to the online documentation (to be updated as part of
40 | [#81](https://github.com/apigee/apigee-api-catalog-drupal/issues/81)) for a "How to" guide on setting up the RBAC:
41 | https://www.drupal.org/node/3058344
42 |
43 | ## Planned Features
44 |
45 | - Integration with Apigee API Products.
46 | - Add visual notifications when source URL specs have changed on the API Doc admin screen
47 |
48 | ### Known issues
49 |
50 | - The Apigee SmartDocs formatter can only render one OpenAPI spec per page and the URL pattern
51 | must match `/api/{entityId}`. This means that the formatter will probably not work correctly if
52 | you modify the default API Docs view or try to use this file Formatter on other node types or entities.
53 |
54 | ## Installing
55 |
56 | This module must be installed on a Drupal site that is managed by Composer. Drupal.org has documentation on how to
57 | [use Composer to manage Drupal site dependencies](https://www.drupal.org/docs/develop/using-composer/using-composer-to-manage-drupal-site-dependencies)
58 | to get you started quickly.
59 |
60 | 1. Install the module using [Composer](https://getcomposer.org/).
61 | Composer will download the this module and all its dependencies.
62 | **Note**: Composer must be executed at the root of your Drupal installation.
63 | For example:
64 | ```
65 | cd /path/to/drupal/root
66 | composer require drupal/apigee_api_catalog
67 | ```
68 | For more information about installing contributed modules using Composer, read
69 | [how to download contributed modules and themes using Composer](https://www.drupal.org/docs/develop/using-composer/using-composer-to-manage-drupal-site-dependencies#managing-contributed).
70 | 2. Choose **Extend** in the Drupal administration menu.
71 | 3. Select the **Apigee API catalog** module.
72 | 4. Choose **Install**.
73 |
74 | ## Development
75 |
76 | Development is happening in our [GitHub repository](https://github.com/apigee/apigee-api-catalog-drupal). The Drupal.org issue
77 | queue is disabled; we use the [Github issue queue](https://github.com/apigee/apigee-api-catalog-drupal) to coordinate
78 | development. See [CONTRIBUTING.md] for more information on contributing through development.
79 |
80 | ## Support
81 |
82 | This project, which integrates Drupal 8 with Apigee Edge, is supported by Google.
83 |
--------------------------------------------------------------------------------
/apigee_api_catalog.info.yml:
--------------------------------------------------------------------------------
1 | name: 'Apigee API Catalog'
2 | type: module
3 | description: 'Display OpenAPI documentation of your APIs to your developers.'
4 | core_version_requirement: ^10 || ^11
5 | package: 'Apigee'
6 | dependencies:
7 | - drupal:text
8 | - drupal:entity
9 | - drupal:file
10 | - drupal:user
11 | - drupal:node
12 | - drupal:path
13 | - drupal:options
14 | - drupal:file_link
15 | - drupal:apigee_edge
16 |
--------------------------------------------------------------------------------
/apigee_api_catalog.install:
--------------------------------------------------------------------------------
1 | moduleExists('apigee_edge_apidocs')) {
32 | return [
33 | 'apigee_api_catalog_module_conflict' => [
34 | 'title' => t('Apigee API Catalog'),
35 | 'description' => t('The "Apigee Edge API Docs" module is currently installed. It is deprecated and should be uninstalled before installing the Apigee API Catalog module.'),
36 | 'severity' => REQUIREMENT_ERROR,
37 | ],
38 | ];
39 | }
40 |
41 | return [];
42 | }
43 |
44 | /**
45 | * Update the length of the name field.
46 | */
47 | function apigee_api_catalog_update_8701() {
48 | $entity_type_id = 'apidoc';
49 | $field_name = 'name';
50 | $field_length = 255;
51 | $definition_update_manager = \Drupal::entityDefinitionUpdateManager();
52 | $last_installed_schema_repository = \Drupal::service('entity.last_installed_schema.repository');
53 | $entity_type = $definition_update_manager->getEntityType($entity_type_id);
54 |
55 | // Update the field storage definition.
56 | /** @var \Drupal\Core\Field\FieldStorageDefinitionInterface[] $field_storage_definitions */
57 | $field_storage_definitions = $last_installed_schema_repository->getLastInstalledFieldStorageDefinitions($entity_type_id);
58 | $field_storage_definitions[$field_name]->setSetting('max_length', $field_length);
59 | $definition_update_manager->updateFieldableEntityType($entity_type, $field_storage_definitions, $sandbox);
60 |
61 | $definition = \Drupal::entityTypeManager()->getDefinition($entity_type_id);
62 | $data_table = $definition->getDataTable();
63 | $revision_data_table = $definition->getRevisionDataTable();
64 |
65 | $entity_storage_schema_sql = \Drupal::keyValue('entity.storage_schema.sql');
66 | $schema_key = "$entity_type_id.field_schema_data.$field_name";
67 | if ($field_schema_data = $entity_storage_schema_sql->get($schema_key)) {
68 | // Update storage schema for data table.
69 | $field_schema_data[$data_table]['fields'][$field_name]['length'] = $field_length;
70 |
71 | // Update storage schema for the revision table.
72 | if ($revision_data_table) {
73 | $field_schema_data[$revision_data_table]['fields'][$field_name]['length'] = $field_length;
74 | }
75 |
76 | $entity_storage_schema_sql->set($schema_key, $field_schema_data);
77 |
78 | // Update the base database field.
79 | $schema = \Drupal::database()->schema();
80 | $schema->changeField($data_table, $field_name, $field_name, $field_schema_data[$data_table]['fields'][$field_name]);
81 |
82 | // Update schema for the revision table.
83 | if ($revision_data_table) {
84 | $schema->changeField($revision_data_table, $field_name, $field_name, $field_schema_data[$revision_data_table]['fields'][$field_name]);
85 | }
86 | }
87 | }
88 |
89 | /**
90 | * Set module schema to 8801.
91 | */
92 | function apigee_api_catalog_update_8801() {
93 | // Empty - only needed to set the 2.x module schema number.
94 | }
95 |
96 | /**
97 | * Create API Doc node type and fields if updating from 1.x.
98 | */
99 | function apigee_api_catalog_update_8802() {
100 | return \Drupal::service('apigee_api_catalog.updates')->update8802();
101 | }
102 |
103 | /**
104 | * Recreate other fields added to the API Doc entity onto the API Doc node type if updating from 1.x.
105 | */
106 | function apigee_api_catalog_update_8803() {
107 | return \Drupal::service('apigee_api_catalog.updates')->update8803();
108 | }
109 |
110 | /**
111 | * Convert API Doc entities to nodes (migrating data) if updating from 1.x.
112 | */
113 | function apigee_api_catalog_update_8804(&$sandbox) {
114 | return \Drupal::service('apigee_api_catalog.updates')->update8804($sandbox);
115 | }
116 |
117 | /**
118 | * Deprecated step.
119 | */
120 | function apigee_api_catalog_update_8805() {
121 | // return \Drupal::service('apigee_api_catalog.updates')->update8805();
122 | }
123 |
124 | /**
125 | * Re-install if needed deprecated API Doc entity definition.
126 | */
127 | function apigee_api_catalog_update_8806() {
128 | return \Drupal::service('apigee_api_catalog.updates')->update8806();
129 | }
130 |
131 | /**
132 | * Removed required atttribute for field_apidoc_spec_file_source.
133 | */
134 | function apigee_api_catalog_update_8807() {
135 | return \Drupal::service('apigee_api_catalog.updates')->update8807();
136 | }
137 |
138 | /**
139 | * Add API Product field.
140 | */
141 | function apigee_api_catalog_update_8808() {
142 | return \Drupal::service('apigee_api_catalog.updates')->update8808();
143 | }
144 |
145 | /**
146 | * Rename API Doc content type as OpenAPI Doc.
147 | */
148 | function apigee_api_catalog_update_8809() {
149 | return \Drupal::service('apigee_api_catalog.updates')->update8809();
150 | }
151 |
152 | /**
153 | * Removed yml extension from field_apidoc_file_link and field_apidoc_spec allowed values.
154 | */
155 | function apigee_api_catalog_update_8810() {
156 | return \Drupal::service('apigee_api_catalog.updates')->update8810();
157 | }
158 |
--------------------------------------------------------------------------------
/apigee_api_catalog.libraries.yml:
--------------------------------------------------------------------------------
1 | # SmartDocs Drupal Angular application.
2 | smartdocs:
3 | version: 1.0.0
4 | license:
5 | name: Proprietary Google Code
6 | gpl-compatible: false
7 | css:
8 | component:
9 | https://fonts.googleapis.com/icon?family=Material+Icons: { type: external }
10 | https://www.gstatic.com/smartdocs/1.1.0/styles.css: { type: external }
11 | js:
12 | https://www.gstatic.com/smartdocs/1.1.0/es2015-polyfills.js: { type: external, minified: true }
13 | https://www.gstatic.com/smartdocs/1.1.0/main.js: { type: external, minified: true }
14 | https://www.gstatic.com/smartdocs/1.1.0/polyfills.js: { type: external, minified: true }
15 | https://www.gstatic.com/smartdocs/1.1.0/runtime.js: { type: external, minified: true }
16 | https://www.gstatic.com/smartdocs/1.1.0/vendor.js: { type: external, minified: true }
17 |
18 | # Integration of SmartDocs Angular application with Drupal,
19 | # passes the OpenAPI spec URL to the SmartDocs ng app.
20 | smartdocs_integration:
21 | version: VERSION
22 | js:
23 | js/smartdocs_integration.js: {}
24 | dependencies:
25 | - core/jquery
26 | - core/drupal
27 |
28 | # Library to parse OpenAPI YAML files to pass to SmartDocs Angular app.
29 | js_yaml:
30 | version: 3.13.1
31 | license:
32 | name: MIT
33 | url: https://github.com/nodeca/js-yaml/blob/master/LICENSE
34 | gpl-compatible: true
35 | js:
36 | https://cdn.jsdelivr.net/npm/js-yaml@3.13.1/dist/js-yaml.min.js: { type: external, minified: true }
37 |
--------------------------------------------------------------------------------
/apigee_api_catalog.links.action.yml:
--------------------------------------------------------------------------------
1 | node.apidoc.add_form:
2 | route_name: node.add
3 | title: 'OpenAPI'
4 | route_parameters:
5 | node_type: 'apidoc'
6 | appears_on:
7 | - view.api_catalog_admin.page_1
8 |
--------------------------------------------------------------------------------
/apigee_api_catalog.links.menu.yml:
--------------------------------------------------------------------------------
1 | apigee_api_catalog.apidoc.collection:
2 | title: 'API catalog'
3 | route_name: view.api_catalog_admin.page_1
4 | description: 'List of all API documentation.'
5 | parent: system.admin_content
6 | weight: -1
7 |
--------------------------------------------------------------------------------
/apigee_api_catalog.links.task.yml:
--------------------------------------------------------------------------------
1 | apidoc.admin:
2 | title: API catalog
3 | route_name: view.api_catalog_admin.page_1
4 | base_route: system.admin_content
5 | weight: -10
6 |
7 | entity.node.apidoc.reimport_spec_form:
8 | route_name: entity.node.reimport_spec_form
9 | base_route: entity.node.canonical
10 | title: 'Re-import OpenAPI spec'
11 | weight: 30
12 |
--------------------------------------------------------------------------------
/apigee_api_catalog.module:
--------------------------------------------------------------------------------
1 | ' . t('About') . '';
41 | $output .= '
' . t('Display documentation of your APIs to your developers using OpenAPI documentation.') . '
';
42 | return $output;
43 |
44 | default:
45 | }
46 | }
47 |
48 | /**
49 | * Implements hook_entity_type_build().
50 | */
51 | function apigee_api_catalog_entity_type_build(array &$entity_types) {
52 | if (isset($entity_types['node'])) {
53 | // Add the reimport form class and link template. The route access check
54 | // makes sure it does not show up for other node types.
55 | $entity_types['node']->setFormClass('reimport_spec', '\Drupal\apigee_api_catalog\Entity\Form\ApiDocReimportSpecForm');
56 | $entity_types['node']->setLinkTemplate('reimport-spec-form', '/node/{node}/reimport');
57 | }
58 | }
59 |
60 | /**
61 | * Implements hook_entity_bundle_field_info_alter().
62 | */
63 | function apigee_api_catalog_entity_bundle_field_info_alter(&$fields, EntityTypeInterface $entity_type, $bundle) {
64 | if ($entity_type->id() == 'node' && $bundle == 'apidoc' && !empty($fields['field_apidoc_file_link'])) {
65 | $fields['field_apidoc_file_link']->addConstraint('ApiDocFileLink', []);
66 | }
67 | }
68 |
69 | /**
70 | * Implements hook_ENTITY_TYPE_presave().
71 | */
72 | function apigee_api_catalog_node_presave(EntityInterface $entity) {
73 | /* @var \Drupal\node\NodeInterface $entity */
74 |
75 | if ($entity->bundle() != 'apidoc') {
76 | return;
77 | }
78 |
79 | // Fetch spec file if using URL.
80 | if ($entity->get('field_apidoc_spec_file_source')->value === SpecFetcherInterface::SPEC_AS_URL) {
81 | \Drupal::service('apigee_api_catalog.spec_fetcher')
82 | ->fetchSpec($entity);
83 | }
84 |
85 | // API docs that use the "file" source will still need their md5 updated.
86 | if ($entity->get('field_apidoc_spec_file_source')->value === SpecFetcherInterface::SPEC_AS_FILE) {
87 | $spec_value = $entity->get('field_apidoc_spec')->isEmpty() ? [] : $entity->get('field_apidoc_spec')->getValue()[0];
88 | if (!empty($spec_value['target_id'])) {
89 | /* @var \Drupal\file\Entity\File $file */
90 | $file = \Drupal::entityTypeManager()
91 | ->getStorage('file')
92 | ->load($spec_value['target_id']);
93 |
94 | if ($file) {
95 | $entity->set('field_apidoc_spec_md5', md5_file($file->getFileUri()));
96 | }
97 | }
98 | }
99 | }
100 |
101 | /**
102 | * Implements hook_entity_operation().
103 | */
104 | function apigee_api_catalog_entity_operation(EntityInterface $entity) {
105 | $operations = [];
106 |
107 | if ($entity->bundle() == 'apidoc' && $entity->access('update')) {
108 | if ($entity->hasLinkTemplate('reimport-spec-form')) {
109 | $operations['reimport_spec'] = [
110 | 'title' => t('Re-import OpenAPI spec'),
111 | 'weight' => 100,
112 | 'url' => $entity->toUrl('reimport-spec-form')->setOption('query', \Drupal::destination()->getAsArray()),
113 | ];
114 | }
115 | }
116 |
117 | return $operations;
118 | }
119 |
120 | /**
121 | * Implements hook_ENTITY_TYPE_insert().
122 | */
123 | function apigee_api_catalog_node_insert(EntityInterface $entity) {
124 | apigee_api_catalog_node_update($entity);
125 | }
126 |
127 | /**
128 | * Implements hook_ENTITY_TYPE_update().
129 | */
130 | function apigee_api_catalog_node_update(EntityInterface $entity) {
131 | if ($entity->bundle() == 'apidoc') {
132 | $systemPath = '/node/' . $entity->id();
133 | $langCode = $entity->language()->getId();
134 | $exists = \Drupal::service('path_alias.manager')->getAliasByPath($systemPath, $langCode);
135 | if ($exists == $systemPath) {
136 | \Drupal::entityTypeManager()->getStorage('path_alias')->create([
137 | 'path' => $systemPath,
138 | 'alias' => '/api/' . $entity->id(),
139 | 'langcode' => $langCode,
140 | ])
141 | ->save();
142 | }
143 | }
144 | }
145 |
146 | /**
147 | * Implements hook_form_FORM_ID_alter().
148 | */
149 | function apigee_api_catalog_form_node_form_alter(&$form, FormStateInterface $form_state, $form_id) {
150 | $node = $form_state->getFormObject()->getEntity();
151 | if ($node->bundle() != 'apidoc') {
152 | return;
153 | }
154 |
155 | $form['field_apidoc_spec']['#states'] = [
156 | 'visible' => [
157 | ':input[name="field_apidoc_spec_file_source"]' => ['value' => SpecFetcherInterface::SPEC_AS_FILE],
158 | ],
159 | ];
160 |
161 | // @todo: #states does not work on managed files.
162 | // @see: https://www.drupal.org/project/drupal/issues/2847425
163 | $form['field_apidoc_spec']['widget'][0]['#states'] = [
164 | 'required' => [
165 | ':input[name="field_apidoc_spec_file_source"]' => ['value' => SpecFetcherInterface::SPEC_AS_FILE],
166 | ],
167 | ];
168 |
169 | $form['field_apidoc_file_link']['#states'] = [
170 | 'visible' => [
171 | [':input[name="field_apidoc_spec_file_source"]' => ['value' => SpecFetcherInterface::SPEC_AS_URL]],
172 | ],
173 | ];
174 |
175 | $form['field_apidoc_file_link']['widget'][0]['uri']['#states'] = [
176 | 'required' => [
177 | [':input[name="field_apidoc_spec_file_source"]' => ['value' => SpecFetcherInterface::SPEC_AS_URL]],
178 | ],
179 | ];
180 |
181 | $form['#validate'][] = '_apigee_api_catalog_form_node_form_validate';
182 | }
183 |
184 | /**
185 | * Form validator for the "apidoc" node bundle.
186 | *
187 | * @param array $form
188 | * The form.
189 | * @param \Drupal\Core\Form\FormStateInterface $form_state
190 | * The form state.
191 | */
192 | function _apigee_api_catalog_form_node_form_validate(&$form, FormStateInterface $form_state) {
193 | $values = $form_state->getValues();
194 |
195 | if (empty($values['field_apidoc_spec_file_source'])) {
196 | return;
197 | }
198 |
199 | // Make sure the field_apidoc_spec (file) or field_apidoc_file_link (link)
200 | // is not empty, according to what was selected as the file source.
201 | $source = $values['field_apidoc_spec_file_source'][0]['value'] ?: NULL;
202 | if ($source == SpecFetcherInterface::SPEC_AS_FILE && empty($values['field_apidoc_spec'][0]['fids'][0])) {
203 | $form_state->setErrorByName('field_apidoc_spec', t('Provide an OpenAPI specification file.'));
204 | }
205 | elseif ($source == SpecFetcherInterface::SPEC_AS_URL && empty($values['field_apidoc_file_link'][0]['uri'])) {
206 | $form_state->setErrorByName('field_apidoc_file_link', t('Provide the URL to an OpenAPI specification file.'));
207 | }
208 | }
209 |
--------------------------------------------------------------------------------
/apigee_api_catalog.routing.yml:
--------------------------------------------------------------------------------
1 | entity.node.reimport_spec_form:
2 | path: '/node/{node}/reimport'
3 | defaults:
4 | _title: 'Re-import API Doc OpenAPI specification'
5 | _entity_form: 'node.reimport_spec'
6 | requirements:
7 | _custom_access: '\Drupal\apigee_api_catalog\Entity\Form\ApiDocReimportSpecForm::checkAccess'
8 | options:
9 | _node_operation_route: TRUE
10 |
--------------------------------------------------------------------------------
/apigee_api_catalog.services.yml:
--------------------------------------------------------------------------------
1 | services:
2 |
3 | logger.channel.apigee_api_catalog:
4 | parent: logger.channel_base
5 | arguments: ['apigee_api_catalog']
6 |
7 | apigee_api_catalog.spec_fetcher:
8 | class: Drupal\apigee_api_catalog\SpecFetcher
9 | arguments: ['@file_system', '@http_client', '@entity_type.manager', '@string_translation', '@messenger', '@logger.channel.apigee_api_catalog']
10 |
11 | apigee_api_catalog.page_not_found_subscriber:
12 | class: Drupal\apigee_api_catalog\EventSubscriber\PageNotFoundEventSubscriber
13 | arguments: ['@path.matcher', '@path.validator']
14 | tags:
15 | - { name: event_subscriber }
16 |
17 | apigee_api_catalog.updates:
18 | class: Drupal\apigee_api_catalog\UpdateService
19 | arguments: ['@uuid', '@config.factory', '@module_handler', '@entity_type.manager', '@entity_field.manager', '@entity.last_installed_schema.repository']
20 |
21 | apigee_api_catalog.breadcrumb:
22 | class: Drupal\apigee_api_catalog\ApigeeApiCatalogBreadcrumbBuilder
23 | tags:
24 | - { name: breadcrumb_builder, priority: 1000 }
25 |
--------------------------------------------------------------------------------
/codecov.yml:
--------------------------------------------------------------------------------
1 | codecov:
2 | require_ci_to_pass: yes
3 |
4 | coverage:
5 | precision: 2
6 | round: down
7 | range: "70...100"
8 | status:
9 | project: no
10 | patch: no
11 | changes: no
12 | default_rules:
13 | flag_coverage_not_uploaded_behavior: include
14 |
15 | github_checks:
16 | annotations: yes
17 | parsers:
18 | gcov:
19 | branch_detection:
20 | conditional: yes
21 | loop: yes
22 | method: no
23 | macro: no
24 |
25 | comment:
26 | layout: "reach,diff,flags,tree"
27 | behavior: default
28 | require_changes: no
29 | show_carryforward_flags: no
30 |
31 | fixes:
32 | - "drupal/modules/contrib/apigee_api_catalog::/"
33 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "drupal/apigee_api_catalog",
3 | "license": "GPL-2.0",
4 | "type": "drupal-module",
5 | "description": "Apigee API Catalog for Drupal",
6 | "require": {
7 | "php": "~8.1.0 || ~8.2.0 || ~8.3.0",
8 | "drupal/entity": "^1.6",
9 | "drupal/file_link": "^2.2",
10 | "drupal/apigee_edge": "^3.0.8 || ^4.0.0",
11 | "webonyx/graphql-php": "^15.19"
12 | },
13 | "require-dev": {
14 | "cweagans/composer-patches": "^1.7",
15 | "drupal/core-dev": "^10.3 || ^11.1",
16 | "drush/drush": "^12.4.3 || ^13.3",
17 | "mglaman/drupal-check": "^1.5",
18 | "phpmd/phpmd": "^2.15.0",
19 | "phpmetrics/phpmetrics": "^2.8",
20 | "phpstan/phpstan": "^1.12"
21 | },
22 | "config": {
23 | "sort-packages": true
24 | },
25 | "minimum-stability": "dev",
26 | "prefer-stable": true
27 | }
28 |
--------------------------------------------------------------------------------
/config/install/core.base_field_override.node.apidoc.title.yml:
--------------------------------------------------------------------------------
1 | langcode: en
2 | status: true
3 | dependencies:
4 | config:
5 | - node.type.apidoc
6 | id: node.apidoc.title
7 | field_name: title
8 | entity_type: node
9 | bundle: apidoc
10 | label: Name
11 | description: ''
12 | required: true
13 | translatable: true
14 | default_value: { }
15 | default_value_callback: ''
16 | settings: { }
17 | field_type: string
18 |
--------------------------------------------------------------------------------
/config/install/core.entity_form_display.node.apidoc.default.yml:
--------------------------------------------------------------------------------
1 | langcode: en
2 | status: true
3 | dependencies:
4 | config:
5 | - field.field.node.apidoc.body
6 | - field.field.node.apidoc.field_api_product
7 | - field.field.node.apidoc.field_apidoc_fetched_timestamp
8 | - field.field.node.apidoc.field_apidoc_file_link
9 | - field.field.node.apidoc.field_apidoc_spec
10 | - field.field.node.apidoc.field_apidoc_spec_file_source
11 | - field.field.node.apidoc.field_apidoc_spec_md5
12 | - node.type.apidoc
13 | module:
14 | - file
15 | - file_link
16 | - path
17 | - text
18 | id: node.apidoc.default
19 | targetEntityType: node
20 | bundle: apidoc
21 | mode: default
22 | content:
23 | body:
24 | type: text_textarea_with_summary
25 | weight: 1
26 | region: content
27 | settings:
28 | rows: 9
29 | summary_rows: 3
30 | placeholder: ''
31 | show_summary: false
32 | third_party_settings: { }
33 | created:
34 | type: datetime_timestamp
35 | weight: 6
36 | region: content
37 | settings: { }
38 | third_party_settings: { }
39 | field_apidoc_file_link:
40 | type: file_link_default
41 | weight: 4
42 | region: content
43 | settings: { }
44 | third_party_settings: { }
45 | field_apidoc_spec:
46 | weight: 3
47 | settings:
48 | progress_indicator: throbber
49 | third_party_settings: { }
50 | type: file_generic
51 | region: content
52 | field_apidoc_spec_file_source:
53 | weight: 2
54 | settings: { }
55 | third_party_settings: { }
56 | type: options_select
57 | region: content
58 | field_api_product:
59 | weight: 7
60 | settings:
61 | match_operator: CONTAINS
62 | match_limit: 10
63 | size: 60
64 | placeholder: ''
65 | third_party_settings: { }
66 | type: entity_reference_autocomplete
67 | region: content
68 | path:
69 | type: path
70 | weight: 9
71 | region: content
72 | settings: { }
73 | third_party_settings: { }
74 | promote:
75 | type: boolean_checkbox
76 | settings:
77 | display_label: true
78 | weight: 7
79 | region: content
80 | third_party_settings: { }
81 | status:
82 | type: boolean_checkbox
83 | settings:
84 | display_label: true
85 | weight: 10
86 | region: content
87 | third_party_settings: { }
88 | sticky:
89 | type: boolean_checkbox
90 | settings:
91 | display_label: true
92 | weight: 8
93 | region: content
94 | third_party_settings: { }
95 | title:
96 | type: string_textfield
97 | weight: 0
98 | region: content
99 | settings:
100 | size: 60
101 | placeholder: ''
102 | third_party_settings: { }
103 | uid:
104 | type: entity_reference_autocomplete
105 | weight: 5
106 | settings:
107 | match_operator: CONTAINS
108 | size: 60
109 | placeholder: ''
110 | match_limit: 10
111 | region: content
112 | third_party_settings: { }
113 | hidden:
114 | field_apidoc_fetched_timestamp: true
115 | field_apidoc_spec_md5: true
116 |
--------------------------------------------------------------------------------
/config/install/core.entity_view_display.node.apidoc.default.yml:
--------------------------------------------------------------------------------
1 | langcode: en
2 | status: true
3 | dependencies:
4 | config:
5 | - field.field.node.apidoc.body
6 | - field.field.node.apidoc.field_api_product
7 | - field.field.node.apidoc.field_apidoc_fetched_timestamp
8 | - field.field.node.apidoc.field_apidoc_file_link
9 | - field.field.node.apidoc.field_apidoc_spec
10 | - field.field.node.apidoc.field_apidoc_spec_file_source
11 | - field.field.node.apidoc.field_apidoc_spec_md5
12 | - node.type.apidoc
13 | module:
14 | - apigee_api_catalog
15 | - text
16 | - user
17 | id: node.apidoc.default
18 | targetEntityType: node
19 | bundle: apidoc
20 | mode: default
21 | content:
22 | body:
23 | type: text_default
24 | weight: 0
25 | region: content
26 | label: hidden
27 | settings: { }
28 | third_party_settings: { }
29 | field_apidoc_spec:
30 | weight: 1
31 | label: hidden
32 | settings: { }
33 | third_party_settings: { }
34 | type: apigee_api_catalog_smartdocs
35 | region: content
36 | links:
37 | weight: 2
38 | region: content
39 | settings: { }
40 | third_party_settings: { }
41 | hidden:
42 | field_apidoc_fetched_timestamp: true
43 | field_apidoc_file_link: true
44 | field_apidoc_spec_file_source: true
45 | field_apidoc_spec_md5: true
46 | field_api_product: true
47 |
--------------------------------------------------------------------------------
/config/install/field.field.node.apidoc.body.yml:
--------------------------------------------------------------------------------
1 | langcode: en
2 | status: true
3 | dependencies:
4 | config:
5 | - field.storage.node.body
6 | - node.type.apidoc
7 | module:
8 | - text
9 | id: node.apidoc.body
10 | field_name: body
11 | entity_type: node
12 | bundle: apidoc
13 | label: Description
14 | description: 'Description of the API.'
15 | required: false
16 | translatable: true
17 | default_value: { }
18 | default_value_callback: ''
19 | settings:
20 | display_summary: true
21 | required_summary: false
22 | field_type: text_with_summary
23 |
--------------------------------------------------------------------------------
/config/install/field.field.node.apidoc.field_api_product.yml:
--------------------------------------------------------------------------------
1 | langcode: en
2 | status: true
3 | dependencies:
4 | config:
5 | - field.storage.node.field_api_product
6 | - node.type.apidoc
7 | id: node.apidoc.field_api_product
8 | field_name: field_api_product
9 | entity_type: node
10 | bundle: apidoc
11 | label: 'API Product'
12 | description: 'Apigee API Product'
13 | required: false
14 | translatable: false
15 | default_value: { }
16 | default_value_callback: ''
17 | settings:
18 | handler: 'default:api_product'
19 | handler_settings:
20 | target_bundles: null
21 | auto_create: false
22 | field_type: entity_reference
23 |
--------------------------------------------------------------------------------
/config/install/field.field.node.apidoc.field_apidoc_fetched_timestamp.yml:
--------------------------------------------------------------------------------
1 | langcode: en
2 | status: true
3 | dependencies:
4 | config:
5 | - field.storage.node.field_apidoc_fetched_timestamp
6 | - node.type.apidoc
7 | id: node.apidoc.field_apidoc_fetched_timestamp
8 | field_name: field_apidoc_fetched_timestamp
9 | entity_type: node
10 | bundle: apidoc
11 | label: 'Spec fetched from URL timestamp'
12 | description: 'When the OpenAPI spec file was last fetched from URL as a Unix timestamp.'
13 | required: false
14 | translatable: false
15 | default_value:
16 | -
17 | value: 1587191891
18 | default_value_callback: ''
19 | settings: { }
20 | field_type: timestamp
21 |
--------------------------------------------------------------------------------
/config/install/field.field.node.apidoc.field_apidoc_file_link.yml:
--------------------------------------------------------------------------------
1 | langcode: en
2 | status: true
3 | dependencies:
4 | config:
5 | - field.storage.node.field_apidoc_file_link
6 | - node.type.apidoc
7 | module:
8 | - file_link
9 | id: node.apidoc.field_apidoc_file_link
10 | field_name: field_apidoc_file_link
11 | entity_type: node
12 | bundle: apidoc
13 | label: 'URL to OpenAPI specification file'
14 | description: 'The URL to an OpenAPI file spec.'
15 | required: false
16 | translatable: false
17 | default_value: { }
18 | default_value_callback: ''
19 | settings:
20 | link_type: 17
21 | title: 0
22 | file_extensions: 'yaml json'
23 | no_extension: false
24 | field_type: file_link
25 |
--------------------------------------------------------------------------------
/config/install/field.field.node.apidoc.field_apidoc_spec.yml:
--------------------------------------------------------------------------------
1 | langcode: en
2 | status: true
3 | dependencies:
4 | config:
5 | - field.storage.node.field_apidoc_spec
6 | - node.type.apidoc
7 | module:
8 | - file
9 | id: node.apidoc.field_apidoc_spec
10 | field_name: field_apidoc_spec
11 | entity_type: node
12 | bundle: apidoc
13 | label: 'OpenAPI specification'
14 | description: 'The spec snapshot.'
15 | required: false
16 | translatable: false
17 | default_value: { }
18 | default_value_callback: ''
19 | settings:
20 | file_directory: apidoc_specs
21 | file_extensions: 'yaml json'
22 | max_filesize: ''
23 | description_field: false
24 | handler: 'default:file'
25 | handler_settings: { }
26 | field_type: file
27 |
--------------------------------------------------------------------------------
/config/install/field.field.node.apidoc.field_apidoc_spec_file_source.yml:
--------------------------------------------------------------------------------
1 | langcode: en
2 | status: true
3 | dependencies:
4 | config:
5 | - field.storage.node.field_apidoc_spec_file_source
6 | - node.type.apidoc
7 | module:
8 | - options
9 | id: node.apidoc.field_apidoc_spec_file_source
10 | field_name: field_apidoc_spec_file_source
11 | entity_type: node
12 | bundle: apidoc
13 | label: 'Specification source type'
14 | description: 'Indicate if the OpenAPI spec will be provided as a file for upload or a URL.'
15 | required: false
16 | translatable: false
17 | default_value: { }
18 | default_value_callback: ''
19 | settings: { }
20 | field_type: list_string
21 |
--------------------------------------------------------------------------------
/config/install/field.field.node.apidoc.field_apidoc_spec_md5.yml:
--------------------------------------------------------------------------------
1 | langcode: en
2 | status: true
3 | dependencies:
4 | config:
5 | - field.storage.node.field_apidoc_spec_md5
6 | - node.type.apidoc
7 | id: node.apidoc.field_apidoc_spec_md5
8 | field_name: field_apidoc_spec_md5
9 | entity_type: node
10 | bundle: apidoc
11 | label: 'OpenAPI specification file MD5'
12 | description: 'OpenAPI specification file MD5.'
13 | required: false
14 | translatable: false
15 | default_value: { }
16 | default_value_callback: ''
17 | settings: { }
18 | field_type: string
19 |
--------------------------------------------------------------------------------
/config/install/field.storage.node.field_api_product.yml:
--------------------------------------------------------------------------------
1 | langcode: en
2 | status: true
3 | dependencies:
4 | enforced:
5 | module:
6 | - apigee_api_catalog
7 | module:
8 | - apigee_edge
9 | - node
10 | id: node.field_api_product
11 | field_name: field_api_product
12 | entity_type: node
13 | type: entity_reference
14 | settings:
15 | target_type: api_product
16 | module: apigee_api_catalog
17 | locked: false
18 | cardinality: -1
19 | translatable: false
20 | indexes: { }
21 | persist_with_no_fields: false
22 | custom_storage: false
23 |
--------------------------------------------------------------------------------
/config/install/field.storage.node.field_apidoc_fetched_timestamp.yml:
--------------------------------------------------------------------------------
1 | langcode: en
2 | status: true
3 | dependencies:
4 | enforced:
5 | module:
6 | - apigee_api_catalog
7 | module:
8 | - node
9 | id: node.field_apidoc_fetched_timestamp
10 | field_name: field_apidoc_fetched_timestamp
11 | entity_type: node
12 | type: timestamp
13 | settings: { }
14 | module: core
15 | locked: false
16 | cardinality: 1
17 | translatable: true
18 | indexes: { }
19 | persist_with_no_fields: false
20 | custom_storage: false
21 |
--------------------------------------------------------------------------------
/config/install/field.storage.node.field_apidoc_file_link.yml:
--------------------------------------------------------------------------------
1 | langcode: en
2 | status: true
3 | dependencies:
4 | enforced:
5 | module:
6 | - apigee_api_catalog
7 | module:
8 | - file_link
9 | - node
10 | id: node.field_apidoc_file_link
11 | field_name: field_apidoc_file_link
12 | entity_type: node
13 | type: file_link
14 | settings: { }
15 | module: file_link
16 | locked: false
17 | cardinality: 1
18 | translatable: true
19 | indexes: { }
20 | persist_with_no_fields: false
21 | custom_storage: false
22 |
--------------------------------------------------------------------------------
/config/install/field.storage.node.field_apidoc_spec.yml:
--------------------------------------------------------------------------------
1 | langcode: en
2 | status: true
3 | dependencies:
4 | enforced:
5 | module:
6 | - apigee_api_catalog
7 | module:
8 | - file
9 | - node
10 | id: node.field_apidoc_spec
11 | field_name: field_apidoc_spec
12 | entity_type: node
13 | type: file
14 | settings:
15 | display_field: false
16 | display_default: false
17 | uri_scheme: public
18 | target_type: file
19 | module: file
20 | locked: false
21 | cardinality: 1
22 | translatable: true
23 | indexes: { }
24 | persist_with_no_fields: false
25 | custom_storage: false
26 |
--------------------------------------------------------------------------------
/config/install/field.storage.node.field_apidoc_spec_file_source.yml:
--------------------------------------------------------------------------------
1 | langcode: en
2 | status: true
3 | dependencies:
4 | enforced:
5 | module:
6 | - apigee_api_catalog
7 | module:
8 | - node
9 | - options
10 | id: node.field_apidoc_spec_file_source
11 | field_name: field_apidoc_spec_file_source
12 | entity_type: node
13 | type: list_string
14 | settings:
15 | allowed_values:
16 | -
17 | value: file
18 | label: File
19 | -
20 | value: url
21 | label: URL
22 | allowed_values_function: ''
23 | module: options
24 | locked: false
25 | cardinality: 1
26 | translatable: true
27 | indexes: { }
28 | persist_with_no_fields: false
29 | custom_storage: false
30 |
--------------------------------------------------------------------------------
/config/install/field.storage.node.field_apidoc_spec_md5.yml:
--------------------------------------------------------------------------------
1 | langcode: en
2 | status: true
3 | dependencies:
4 | enforced:
5 | module:
6 | - apigee_api_catalog
7 | module:
8 | - node
9 | id: node.field_apidoc_spec_md5
10 | field_name: field_apidoc_spec_md5
11 | entity_type: node
12 | type: string
13 | settings:
14 | max_length: 255
15 | is_ascii: false
16 | case_sensitive: false
17 | module: core
18 | locked: false
19 | cardinality: 1
20 | translatable: true
21 | indexes: { }
22 | persist_with_no_fields: false
23 | custom_storage: false
24 |
--------------------------------------------------------------------------------
/config/install/node.type.apidoc.yml:
--------------------------------------------------------------------------------
1 | langcode: en
2 | status: true
3 | dependencies:
4 | enforced:
5 | module:
6 | - apigee_api_catalog
7 | name: 'OpenAPI Doc'
8 | type: apidoc
9 | description: 'Use OpenAPI Docs to document OpenAPIs'
10 | help: ''
11 | new_revision: true
12 | preview_mode: 1
13 | display_submitted: false
14 |
--------------------------------------------------------------------------------
/js/smartdocs_integration.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @file
3 | * Integration of SmartDocs file field formatter.
4 | *
5 | * Take the OpenAPI spec and put into session storage for the SmartDocs
6 | * Angular app to use.
7 | */
8 |
9 | (function ($, window, Drupal) {
10 |
11 | Drupal.behaviors.smartdocsFieldFormatter = {
12 | attach: function (context) {
13 | let specMap = {};
14 | for (let fieldName in drupalSettings.smartdocsFieldFormatter) {
15 | if (drupalSettings.smartdocsFieldFormatter.hasOwnProperty(fieldName)) {
16 | const field = drupalSettings.smartdocsFieldFormatter[fieldName];
17 | for (let fieldDelta = 0; fieldDelta < field.openApiFiles.length; fieldDelta++) {
18 | // Each openApiFile var has the spec url and file extension.
19 | const fileUrl = field.openApiFiles[fieldDelta].fileUrl;
20 | const fileExtention = field.openApiFiles[fieldDelta].fileExtension;
21 | // Get the OpenAPI spec file from the server.
22 | $.get(fileUrl, function(data, status) {
23 | if (fileExtention === 'json') {
24 | // JSON files do not need any conversion.
25 | specMap[field.entityId] = data;
26 | }
27 | else {
28 | // YAML files need to be parsed into javascript object.
29 | specMap[field.entityId] = jsyaml.load(data);
30 | }
31 | // Store the spec into session storage.
32 | sessionStorage.setItem('specs', JSON.stringify(specMap));
33 | });
34 | }
35 | }
36 | }
37 | }
38 | };
39 |
40 | }(jQuery, window, Drupal));
41 |
--------------------------------------------------------------------------------
/modules/apigee_asyncapi_doc/apigee_asyncapi_doc.info.yml:
--------------------------------------------------------------------------------
1 | name: AsyncAPI for Apigee
2 | type: module
3 | description: AsyncAPI for Apigee
4 | package: Apigee (Experimental)
5 | core_version_requirement: ^10 || ^11
6 | dependencies:
7 | - apigee_api_catalog
8 | - file_link
9 | - text
10 |
--------------------------------------------------------------------------------
/modules/apigee_asyncapi_doc/apigee_asyncapi_doc.install:
--------------------------------------------------------------------------------
1 | get('node.type.locked');
32 | $locked['asyncapi_doc'] = 'asyncapi_doc';
33 | \Drupal::state()->set('node.type.locked', $locked);
34 | }
35 |
36 | /**
37 | * Implements hook_uninstall().
38 | */
39 | function apigee_asyncapi_doc_uninstall() {
40 | // Allow to delete a asyncapi_doc's node type.
41 | $locked = \Drupal::state()->get('node.type.locked');
42 | unset($locked['asyncapi_doc']);
43 | \Drupal::state()->set('node.type.locked', $locked);
44 | }
45 |
--------------------------------------------------------------------------------
/modules/apigee_asyncapi_doc/apigee_asyncapi_doc.libraries.yml:
--------------------------------------------------------------------------------
1 | # Third-party library (CDN).
2 | reactjs:
3 | remote: https://reactjs.org
4 | version: VERSION
5 | license:
6 | name: MIT
7 | url: https://github.com/facebook/react/blob/main/LICENSE
8 | gpl-compatible: true
9 | js:
10 | https://unpkg.com/react/umd/react.production.min.js: {type: external, minified: true}
11 | https://unpkg.com/react-dom/umd/react-dom.production.min.js: {type: external, minified: true}
12 |
13 | # Third-party library (CDN).
14 | # When updating the version, apigee-async-doc.css should be updated as well.
15 | asyncapi:
16 | remote: https://www.asyncapi.com/
17 | version: VERSION
18 | license:
19 | name: Apache License 2.0
20 | url: https://github.com/asyncapi/asyncapi-react/blob/master/LICENSE
21 | gpl-compatible: true
22 | js:
23 | https://unpkg.com/@webcomponents/webcomponentsjs/webcomponents-bundle.js: {type: external, minified: true}
24 | https://unpkg.com/@asyncapi/web-component/lib/asyncapi-web-component.js: {type: external, minified: true}
25 |
--------------------------------------------------------------------------------
/modules/apigee_asyncapi_doc/apigee_asyncapi_doc.links.action.yml:
--------------------------------------------------------------------------------
1 | node.asyncapi_doc.add_form:
2 | route_name: node.add
3 | title: 'AsyncAPI'
4 | route_parameters:
5 | node_type: 'asyncapi_doc'
6 | appears_on:
7 | - view.api_catalog_admin.page_1
8 |
--------------------------------------------------------------------------------
/modules/apigee_asyncapi_doc/apigee_asyncapi_doc.module:
--------------------------------------------------------------------------------
1 | [
35 | 'variables' => [
36 | 'field_name' => NULL,
37 | 'delta' => NULL,
38 | 'link' => NULL,
39 | ],
40 | ],
41 | ];
42 | }
43 |
44 | /**
45 | * Implements hook_ENTITY_TYPE_presave().
46 | */
47 | function apigee_asyncapi_doc_node_presave(EntityInterface $entity) {
48 | /* @var \Drupal\node\NodeInterface $entity */
49 |
50 | if ($entity->bundle() != 'asyncapi_doc') {
51 | return;
52 | }
53 |
54 | if ($entity->get('field_asyncapi_spec_source_type')->value === 'file') {
55 | $spec_value = $entity->get('field_asyncapi_spec')->isEmpty() ? [] : $entity->get('field_asyncapi_spec')->getValue()[0];
56 | if (!empty($spec_value['target_id'])) {
57 | /* @var \Drupal\file\Entity\File $file */
58 | $file = \Drupal::entityTypeManager()
59 | ->getStorage('file')
60 | ->load($spec_value['target_id']);
61 |
62 | if ($file) {
63 | $entity->set('field_asyncapi_spec_file_link', ['uri' => $file->createFileUrl(FALSE)]);
64 | }
65 | }
66 | }
67 | }
68 |
69 | /**
70 | * Implements hook_form_FORM_ID_alter().
71 | */
72 | function apigee_asyncapi_doc_form_node_form_alter(&$form, FormStateInterface $form_state, $form_id) {
73 | $node = $form_state->getFormObject()->getEntity();
74 | if ($node->bundle() != 'asyncapi_doc') {
75 | return;
76 | }
77 |
78 | $form['field_asyncapi_spec']['#states'] = [
79 | 'visible' => [
80 | ':input[name="field_asyncapi_spec_source_type"]' => ['value' => 'file'],
81 | ],
82 | ];
83 |
84 | // @todo: #states does not work on managed files.
85 | // @see: https://www.drupal.org/project/drupal/issues/2847425
86 | $form['field_asyncapi_spec']['widget'][0]['#states'] = [
87 | 'required' => [
88 | ':input[name="field_asyncapi_spec_source_type"]' => ['value' => 'file'],
89 | ],
90 | ];
91 |
92 | $form['field_asyncapi_spec_file_link']['#states'] = [
93 | 'visible' => [
94 | [':input[name="field_asyncapi_spec_source_type"]' => ['value' => 'url']],
95 | ],
96 | ];
97 |
98 | $form['field_asyncapi_spec_file_link']['widget'][0]['uri']['#states'] = [
99 | 'required' => [
100 | [':input[name="field_asyncapi_spec_source_type"]' => ['value' => 'url']],
101 | ],
102 | ];
103 |
104 | $form['#validate'][] = '_apigee_asyncapi_form_node_form_validate';
105 | }
106 |
107 | /**
108 | * Form validator for the "asyncapi_doc" node bundle.
109 | *
110 | * @param array $form
111 | * The form.
112 | * @param \Drupal\Core\Form\FormStateInterface $form_state
113 | * The form state.
114 | */
115 | function _apigee_asyncapi_form_node_form_validate(&$form, FormStateInterface $form_state) {
116 | $values = $form_state->getValues();
117 |
118 | if (empty($values['field_asyncapi_spec_source_type'])) {
119 | return;
120 | }
121 |
122 | // Make sure the field_asyncapi_spec (file) or field_asyncapi_spec_file_link (link)
123 | // is not empty, according to what was selected as the file source.
124 | $source = $values['field_asyncapi_spec_source_type'][0]['value'] ?: NULL;
125 | if ($source == 'file' && empty($values['field_asyncapi_spec'][0]['fids'][0])) {
126 | $form_state->setErrorByName('field_asyncapi_spec', t('Provide an AsyncAPI specification file.'));
127 | }
128 | elseif ($source == 'url' && empty($values['field_asyncapi_spec_file_link'][0]['uri'])) {
129 | $form_state->setErrorByName('field_asyncapi_spec_file_link', t('Provide the URL to an AsyncAPI specification file.'));
130 | }
131 | }
132 |
--------------------------------------------------------------------------------
/modules/apigee_asyncapi_doc/apigee_asyncapi_doc.routing.yml:
--------------------------------------------------------------------------------
1 | apigee_asyncapi_doc.confirm_remove_views_references:
2 | path: '/admin/modules/uninstall/apigee-async-doc/confirm-remove-views-references'
3 | defaults:
4 | _title: 'Confirm remove views references'
5 | _form: 'Drupal\apigee_asyncapi_doc\Form\ConfirmRemoveViewsReferencesForm'
6 | requirements:
7 | _permission: 'administer modules'
8 |
--------------------------------------------------------------------------------
/modules/apigee_asyncapi_doc/apigee_asyncapi_doc.services.yml:
--------------------------------------------------------------------------------
1 | services:
2 | apigee_asyncapi_doc.event_subscriber:
3 | class: Drupal\apigee_asyncapi_doc\EventSubscriber\ApigeeAsyncapiDocSubscriber
4 | arguments: ['@messenger']
5 | tags:
6 | - { name: event_subscriber }
7 |
8 | apigee_asyncapi_doc.uninstall_validator:
9 | class: Drupal\apigee_asyncapi_doc\ApigeeAsyncapiDocUninstallValidator
10 | tags:
11 | - { name: module_install.uninstall_validator }
12 | arguments: ['@entity_type.manager', '@string_translation']
13 |
14 | apigee_asyncapi_doc.breadcrumb:
15 | class: Drupal\apigee_asyncapi_doc\ApigeeAsyncapiDocBreadcrumbBuilder
16 | tags:
17 | - { name: breadcrumb_builder, priority: 1000 }
18 |
--------------------------------------------------------------------------------
/modules/apigee_asyncapi_doc/config/install/field.field.node.asyncapi_doc.body.yml:
--------------------------------------------------------------------------------
1 | langcode: en
2 | status: true
3 | dependencies:
4 | config:
5 | - field.storage.node.body
6 | - node.type.asyncapi_doc
7 | module:
8 | - text
9 | id: node.asyncapi_doc.body
10 | field_name: body
11 | entity_type: node
12 | bundle: asyncapi_doc
13 | label: Description
14 | description: ''
15 | required: false
16 | translatable: true
17 | default_value: { }
18 | default_value_callback: ''
19 | settings:
20 | display_summary: true
21 | required_summary: false
22 | field_type: text_with_summary
23 |
--------------------------------------------------------------------------------
/modules/apigee_asyncapi_doc/config/install/field.field.node.asyncapi_doc.field_api_product.yml:
--------------------------------------------------------------------------------
1 | langcode: en
2 | status: true
3 | dependencies:
4 | config:
5 | - field.storage.node.field_api_product
6 | - node.type.asyncapi_doc
7 | id: node.asyncapi_doc.field_api_product
8 | field_name: field_api_product
9 | entity_type: node
10 | bundle: asyncapi_doc
11 | label: 'API Product'
12 | description: 'Apigee API Product'
13 | required: false
14 | translatable: false
15 | default_value: { }
16 | default_value_callback: ''
17 | settings:
18 | handler: 'default:api_product'
19 | handler_settings:
20 | target_bundles: null
21 | auto_create: false
22 | field_type: entity_reference
23 |
--------------------------------------------------------------------------------
/modules/apigee_asyncapi_doc/config/install/field.field.node.asyncapi_doc.field_asyncapi_spec.yml:
--------------------------------------------------------------------------------
1 | langcode: en
2 | status: true
3 | dependencies:
4 | config:
5 | - field.storage.node.field_asyncapi_spec
6 | - node.type.asyncapi_doc
7 | module:
8 | - file
9 | id: node.asyncapi_doc.field_asyncapi_spec
10 | field_name: field_asyncapi_spec
11 | entity_type: node
12 | bundle: asyncapi_doc
13 | label: 'AsyncAPI specification'
14 | description: 'The spec snapshot.'
15 | required: false
16 | translatable: false
17 | default_value: { }
18 | default_value_callback: ''
19 | settings:
20 | handler: 'default:file'
21 | handler_settings: { }
22 | file_directory: asyncapi_specs
23 | file_extensions: 'yaml json'
24 | max_filesize: ''
25 | description_field: false
26 | field_type: file
--------------------------------------------------------------------------------
/modules/apigee_asyncapi_doc/config/install/field.field.node.asyncapi_doc.field_asyncapi_spec_file_link.yml:
--------------------------------------------------------------------------------
1 | langcode: en
2 | status: true
3 | dependencies:
4 | config:
5 | - field.storage.node.field_asyncapi_spec_file_link
6 | - node.type.asyncapi_doc
7 | module:
8 | - file_link
9 | id: node.asyncapi_doc.field_asyncapi_spec_file_link
10 | field_name: field_asyncapi_spec_file_link
11 | entity_type: node
12 | bundle: asyncapi_doc
13 | label: 'AsyncAPI endpoint'
14 | description: ''
15 | required: false
16 | translatable: false
17 | default_value: { }
18 | default_value_callback: ''
19 | settings:
20 | title: 0
21 | link_type: 16
22 | file_extensions: ''
23 | no_extension: true
24 | deferred_request: false
25 | field_type: file_link
26 |
--------------------------------------------------------------------------------
/modules/apigee_asyncapi_doc/config/install/field.field.node.asyncapi_doc.field_asyncapi_spec_source_type.yml:
--------------------------------------------------------------------------------
1 | langcode: en
2 | status: true
3 | dependencies:
4 | config:
5 | - field.storage.node.field_asyncapi_spec_source_type
6 | - node.type.asyncapi_doc
7 | module:
8 | - options
9 | id: node.asyncapi_doc.field_asyncapi_spec_source_type
10 | field_name: field_asyncapi_spec_source_type
11 | entity_type: node
12 | bundle: asyncapi_doc
13 | label: 'Specification source type'
14 | description: 'Indicate if the AsyncAPI spec will be provided as a file for upload or a URL.'
15 | required: true
16 | translatable: false
17 | default_value: { }
18 | default_value_callback: ''
19 | settings: { }
20 | field_type: list_string
21 |
--------------------------------------------------------------------------------
/modules/apigee_asyncapi_doc/config/install/field.storage.node.field_asyncapi_spec.yml:
--------------------------------------------------------------------------------
1 | langcode: en
2 | status: true
3 | dependencies:
4 | enforced:
5 | module:
6 | - apigee_asyncapi_doc
7 | module:
8 | - file
9 | - node
10 | id: node.field_asyncapi_spec
11 | field_name: field_asyncapi_spec
12 | entity_type: node
13 | type: file
14 | settings:
15 | target_type: file
16 | display_field: false
17 | display_default: false
18 | uri_scheme: public
19 | module: file
20 | locked: false
21 | cardinality: 1
22 | translatable: false
23 | indexes: { }
24 | persist_with_no_fields: false
25 | custom_storage: false
26 |
--------------------------------------------------------------------------------
/modules/apigee_asyncapi_doc/config/install/field.storage.node.field_asyncapi_spec_file_link.yml:
--------------------------------------------------------------------------------
1 | langcode: en
2 | status: true
3 | dependencies:
4 | enforced:
5 | module:
6 | - apigee_asyncapi_doc
7 | module:
8 | - file_link
9 | - node
10 | id: node.field_asyncapi_spec_file_link
11 | field_name: field_asyncapi_spec_file_link
12 | entity_type: node
13 | type: file_link
14 | settings: { }
15 | module: file_link
16 | locked: false
17 | cardinality: 1
18 | translatable: false
19 | indexes: { }
20 | persist_with_no_fields: false
21 | custom_storage: false
22 |
--------------------------------------------------------------------------------
/modules/apigee_asyncapi_doc/config/install/field.storage.node.field_asyncapi_spec_source_type.yml:
--------------------------------------------------------------------------------
1 | langcode: en
2 | status: true
3 | dependencies:
4 | enforced:
5 | module:
6 | - apigee_asyncapi_doc
7 | module:
8 | - node
9 | - options
10 | id: node.field_asyncapi_spec_source_type
11 | field_name: field_asyncapi_spec_source_type
12 | entity_type: node
13 | type: list_string
14 | settings:
15 | allowed_values:
16 | -
17 | value: file
18 | label: File
19 | -
20 | value: url
21 | label: URL
22 | allowed_values_function: ''
23 | module: options
24 | locked: false
25 | cardinality: 1
26 | translatable: false
27 | indexes: { }
28 | persist_with_no_fields: false
29 | custom_storage: false
--------------------------------------------------------------------------------
/modules/apigee_asyncapi_doc/config/install/node.type.asyncapi_doc.yml:
--------------------------------------------------------------------------------
1 | langcode: en
2 | dependencies:
3 | module:
4 | - apigee_api_catalog
5 | - apigee_asyncapi_doc
6 | enforced:
7 | module:
8 | - apigee_asyncapi_doc
9 | type: asyncapi_doc
10 | name: 'AsyncAPI Doc'
11 | description: 'Use AsyncAPI Doc to document AsyncAPI.'
12 | help: ''
13 | new_revision: false
14 | display_submitted: false
15 | preview_mode: 0
16 | status: true
17 |
--------------------------------------------------------------------------------
/modules/apigee_asyncapi_doc/config/optional/core.entity_form_display.node.asyncapi_doc.default.yml:
--------------------------------------------------------------------------------
1 | langcode: en
2 | status: true
3 | dependencies:
4 | config:
5 | - field.field.node.asyncapi_doc.body
6 | - field.field.node.asyncapi_doc.field_asyncapi_spec
7 | - field.field.node.asyncapi_doc.field_asyncapi_spec_file_link
8 | - field.field.node.asyncapi_doc.field_asyncapi_spec_source_type
9 | - field.field.node.asyncapi_doc.field_api_product
10 | - node.type.asyncapi_doc
11 | module:
12 | - file_link
13 | - path
14 | - text
15 | id: node.asyncapi_doc.default
16 | targetEntityType: node
17 | bundle: asyncapi_doc
18 | mode: default
19 | content:
20 | title:
21 | type: string_textfield
22 | weight: 0
23 | region: content
24 | settings:
25 | size: 60
26 | placeholder: ''
27 | third_party_settings: { }
28 | body:
29 | type: text_textarea_with_summary
30 | weight: 1
31 | settings:
32 | rows: 9
33 | summary_rows: 3
34 | placeholder: ''
35 | show_summary: false
36 | third_party_settings: { }
37 | region: content
38 | field_asyncapi_spec_source_type:
39 | type: options_select
40 | weight: 4
41 | region: content
42 | settings: { }
43 | third_party_settings: { }
44 | field_asyncapi_spec:
45 | type: file_generic
46 | weight: 5
47 | region: content
48 | settings:
49 | progress_indicator: throbber
50 | third_party_settings: { }
51 | field_asyncapi_spec_file_link:
52 | type: file_link_default
53 | weight: 6
54 | region: content
55 | settings:
56 | placeholder_url: ''
57 | placeholder_title: ''
58 | third_party_settings: { }
59 | field_api_product:
60 | weight: 7
61 | settings:
62 | match_operator: CONTAINS
63 | match_limit: 10
64 | size: 60
65 | placeholder: ''
66 | third_party_settings: { }
67 | type: entity_reference_autocomplete
68 | region: content
69 | created:
70 | type: datetime_timestamp
71 | weight: 8
72 | region: content
73 | settings: { }
74 | third_party_settings: { }
75 | promote:
76 | type: boolean_checkbox
77 | settings:
78 | display_label: true
79 | weight: 9
80 | region: content
81 | third_party_settings: { }
82 | uid:
83 | type: entity_reference_autocomplete
84 | weight: 10
85 | settings:
86 | match_operator: CONTAINS
87 | size: 60
88 | placeholder: ''
89 | match_limit: 10
90 | region: content
91 | third_party_settings: { }
92 | sticky:
93 | type: boolean_checkbox
94 | settings:
95 | display_label: true
96 | weight: 11
97 | region: content
98 | third_party_settings: { }
99 | path:
100 | type: path
101 | weight: 12
102 | region: content
103 | settings: { }
104 | third_party_settings: { }
105 | status:
106 | type: boolean_checkbox
107 | settings:
108 | display_label: true
109 | weight: 13
110 | region: content
111 | third_party_settings: { }
112 | hidden: { }
--------------------------------------------------------------------------------
/modules/apigee_asyncapi_doc/config/optional/core.entity_view_display.node.asyncapi_doc.default.yml:
--------------------------------------------------------------------------------
1 | langcode: en
2 | status: true
3 | dependencies:
4 | config:
5 | - field.field.node.asyncapi_doc.body
6 | - field.field.node.asyncapi_doc.field_asyncapi_spec
7 | - field.field.node.asyncapi_doc.field_asyncapi_spec_file_link
8 | - field.field.node.asyncapi_doc.field_asyncapi_spec_source_type
9 | - field.field.node.asyncapi_doc.field_api_product
10 | - node.type.asyncapi_doc
11 | module:
12 | - file_link
13 | - text
14 | - user
15 | id: node.asyncapi_doc.default
16 | targetEntityType: node
17 | bundle: asyncapi_doc
18 | mode: default
19 | content:
20 | body:
21 | label: hidden
22 | type: text_default
23 | weight: 1
24 | settings: { }
25 | third_party_settings: { }
26 | region: content
27 | field_asyncapi_spec_file_link:
28 | weight: 3
29 | label: hidden
30 | settings:
31 | trim_length: 80
32 | url_only: true
33 | url_plain: false
34 | rel: ''
35 | target: ''
36 | format_size: true
37 | third_party_settings: { }
38 | type: apigee_asyncapi_doc_async
39 | region: content
40 | links:
41 | weight: 0
42 | region: content
43 | settings: { }
44 | third_party_settings: { }
45 | hidden:
46 | field_api_product: true
47 | field_asyncapi_spec: true
48 | field_asyncapi_spec_source_type: true
49 |
--------------------------------------------------------------------------------
/modules/apigee_asyncapi_doc/css/apigee-asyncapi-doc.css:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2022 Google Inc.
3 | *
4 | * This program is free software; you can redistribute it and/or modify it under
5 | * the terms of the GNU General Public License version 2 as published by the
6 | * Free Software Foundation.
7 | *
8 | * This program is distributed in the hope that it will be useful, but WITHOUT
9 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
10 | * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
11 | * License for more details.
12 | *
13 | * You should have received a copy of the GNU General Public License along
14 | * with this program; if not, write to the Free Software Foundation, Inc., 51
15 | * Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 | */
17 |
18 | @import url(https://unpkg.com/@asyncapi/react-component/styles/default.min.css);
19 |
20 | /* Customization as per Apigee */
21 | .asyncapi,
22 | .asyncapi__tag,
23 | .asyncapi__server-expand-icon:before,
24 | .asyncapi__error {
25 | font-family: Roboto,sans-serif;
26 | }
27 | .asyncapi__code-body {
28 | font-family: courier;
29 | }
30 | h1, h2, h3 {
31 | font-weight: 300;
32 | }
33 |
--------------------------------------------------------------------------------
/modules/apigee_asyncapi_doc/src/ApigeeAsyncapiDocBreadcrumbBuilder.php:
--------------------------------------------------------------------------------
1 | getParameter('node');
42 | return $node instanceof NodeInterface && $node->getType() === 'asyncapi_doc';
43 | }
44 |
45 | /**
46 | * {@inheritdoc}
47 | */
48 | public function build(RouteMatchInterface $route_match) {
49 | $breadcrumb = new Breadcrumb();
50 |
51 | $links[] = Link::createFromRoute($this->t('Home'), '');
52 |
53 | // API Catalog page is a view.
54 | $links[] = Link::createFromRoute($this->t('API Catalog'), 'view.apigee_api_catalog.page_1');
55 |
56 | $breadcrumb->setLinks($links);
57 | $breadcrumb->addCacheContexts(['route']);
58 |
59 | return $breadcrumb;
60 | }
61 |
62 | }
63 |
--------------------------------------------------------------------------------
/modules/apigee_asyncapi_doc/src/ApigeeAsyncapiDocUninstallValidator.php:
--------------------------------------------------------------------------------
1 | entityTypeManager = $entity_manager;
54 | $this->stringTranslation = $string_translation;
55 | }
56 |
57 | /**
58 | * {@inheritdoc}
59 | */
60 | public function validate($module) {
61 | $reasons = [];
62 | if ($module == 'apigee_asyncapi_doc') {
63 | if ($this->hasAsyncApiDocNodes()) {
64 | $reasons[] = $this->t('To uninstall AsyncAPI for Apigee, first delete all AsyncAPI Doc content');
65 | }
66 |
67 | // Restrict uninstall, if reference of asyncapi_doc is there in apigee_api_catalog view.
68 | $is_views_references = FALSE;
69 | $view = Views::getView('apigee_api_catalog');
70 | if (is_object($view)) {
71 | $display = $view->getDisplay();
72 |
73 | $filters = $view->display_handler->getOption('filters');
74 | if (isset($filters['type']['value']['asyncapi_doc'])) {
75 | $is_views_references = TRUE;
76 | }
77 | }
78 |
79 | // Restrict uninstall, if reference of asyncapi_doc is there in api_catalog_admin view.
80 | $view = Views::getView('api_catalog_admin');
81 | if (is_object($view)) {
82 | $display = $view->getDisplay();
83 |
84 | $filters = $view->display_handler->getOption('filters');
85 | if (isset($filters['type']['value']['asyncapi_doc'])) {
86 | $is_views_references = TRUE;
87 | }
88 | }
89 | if ($is_views_references) {
90 | $message_arguments = [
91 | ':url' => Url::fromRoute('apigee_asyncapi_doc.confirm_remove_views_references')->toString(),
92 | ];
93 | $reasons[] = $this->t('Click to remove AsyncAPI Doc references from both "apigee_api_catalog" and "api_catalog_admin" views', $message_arguments);
94 | }
95 | }
96 |
97 | return $reasons;
98 | }
99 |
100 | /**
101 | * Determines if there are any asyncapi_doc nodes or not.
102 | *
103 | * @return bool
104 | * TRUE if there are forum nodes, FALSE otherwise.
105 | */
106 | protected function hasAsyncApiDocNodes() {
107 | $nodes = $this->entityTypeManager->getStorage('node')->getQuery()
108 | ->condition('type', 'asyncapi_doc')
109 | ->accessCheck(FALSE)
110 | ->range(0, 1)
111 | ->execute();
112 | return !empty($nodes);
113 | }
114 |
115 | }
116 |
--------------------------------------------------------------------------------
/modules/apigee_asyncapi_doc/src/EventSubscriber/ApigeeAsyncapiDocSubscriber.php:
--------------------------------------------------------------------------------
1 | messenger = $messenger;
49 | }
50 |
51 | /**
52 | * Update views on installation.
53 | *
54 | * @param \Drupal\Core\Config\ConfigCrudEvent $event
55 | * Config crud event.
56 | */
57 | public function configSave(ConfigCrudEvent $event) {
58 | $config = $event->getConfig();
59 | if ($config->getName() == 'core.entity_view_display.node.asyncapi_doc.default') {
60 |
61 | $view = Views::getView('apigee_api_catalog');
62 | if (is_object($view)) {
63 | $display = $view->getDisplay();
64 |
65 | $filters = $view->display_handler->getOption('filters');
66 | if ($filters['type']) {
67 | $filters['type']['value']['asyncapi_doc'] = 'asyncapi_doc';
68 | }
69 | if (isset($filters['type_1'])) {
70 | $filters['type_1']['value']['asyncapi_doc'] = 'asyncapi_doc';
71 | }
72 | $view->display_handler->overrideOption('filters', $filters);
73 |
74 | $view->save();
75 | \Drupal::messenger()->addStatus('Updating Views - API Catalog view');
76 | }
77 |
78 | $view = Views::getView('api_catalog_admin');
79 | if (is_object($view)) {
80 | $display = $view->getDisplay();
81 |
82 | $filters = $view->display_handler->getOption('filters');
83 | if ($filters['type']) {
84 | $filters['type']['value']['asyncapi_doc'] = 'asyncapi_doc';
85 | }
86 | if (isset($filters['type_1'])) {
87 | $filters['type_1']['value']['asyncapi_doc'] = 'asyncapi_doc';
88 | }
89 | $view->display_handler->overrideOption('filters', $filters);
90 |
91 | $view->save();
92 | \Drupal::messenger()->addStatus('Updating Views - API Catalog Admin');
93 | }
94 | }
95 | }
96 |
97 | /**
98 | * {@inheritdoc}
99 | */
100 | public static function getSubscribedEvents() {
101 | return [
102 | ConfigEvents::SAVE => 'configSave',
103 | ];
104 | }
105 |
106 | }
107 |
--------------------------------------------------------------------------------
/modules/apigee_asyncapi_doc/src/Form/ConfirmRemoveViewsReferencesForm.php:
--------------------------------------------------------------------------------
1 | t('Are you sure you want to remove references of AsyncAPI for Apigee in views before uninstallation of the module?');
45 | }
46 |
47 | /**
48 | * {@inheritdoc}
49 | */
50 | public function getCancelUrl() {
51 | return new Url('system.modules_uninstall');
52 | }
53 |
54 | /**
55 | * {@inheritdoc}
56 | */
57 | public function submitForm(array &$form, FormStateInterface $form_state) {
58 | $view = Views::getView('apigee_api_catalog');
59 | if (is_object($view)) {
60 | $display = $view->getDisplay();
61 |
62 | $filters = $view->display_handler->getOption('filters');
63 | if ($filters['type']['value']['asyncapi_doc']) {
64 | unset($filters['type']['value']['asyncapi_doc']);
65 | }
66 | if (isset($filters['type_1']) && $filters['type_1']['value']['asyncapi_doc']) {
67 | unset($filters['type_1']['value']['asyncapi_doc']);
68 | }
69 | $view->display_handler->overrideOption('filters', $filters);
70 |
71 | $view->save();
72 |
73 | $this->messenger()->addStatus($this->t('Updating Views - API Catalog'));
74 | }
75 |
76 | $view = Views::getView('api_catalog_admin');
77 | if (is_object($view)) {
78 | $display = $view->getDisplay();
79 |
80 | $filters = $view->display_handler->getOption('filters');
81 | if ($filters['type']['value']['asyncapi_doc']) {
82 | unset($filters['type']['value']['asyncapi_doc']);
83 | }
84 | if (isset($filters['type_1']) && $filters['type_1']['value']['asyncapi_doc']) {
85 | unset($filters['type_1']['value']['asyncapi_doc']);
86 | }
87 | $view->display_handler->overrideOption('filters', $filters);
88 |
89 | $view->save();
90 |
91 | $this->messenger()->addStatus($this->t('Updating Views - API Catalog Admin'));
92 | }
93 |
94 | $form_state->setRedirectUrl($this->getCancelUrl());
95 | }
96 |
97 | }
98 |
--------------------------------------------------------------------------------
/modules/apigee_asyncapi_doc/src/Plugin/Field/FieldFormatter/AsyncapiFormatter.php:
--------------------------------------------------------------------------------
1 | $item) {
46 | $element[$delta] = [
47 | '#theme' => 'apigee_asyncapi_doc_file_link_field_item',
48 | '#field_name' => $this->fieldDefinition->getName(),
49 | '#delta' => $delta,
50 | '#link' => $item->getUrl()->toString(),
51 | ];
52 | }
53 |
54 | return $element;
55 | }
56 |
57 | /**
58 | * {@inheritdoc}
59 | */
60 | public function view(FieldItemListInterface $items, $langcode = NULL) {
61 | $element = parent::view($items, $langcode);
62 |
63 | $async_urls = [];
64 | foreach ($items as $delta => $item) {
65 | /** @var \Drupal\link\Plugin\Field\FieldType\LinkItem $item */
66 | // Not validating URLs or paths.
67 | $async_urls[] = $item->getUrl()->toString();
68 | }
69 |
70 | return $this->attachLibraries($element, $async_urls);
71 | }
72 |
73 | /**
74 | * Helper function to attach library definitions and pass JavaScript settings.
75 | *
76 | * @param array $element
77 | * A renderable array of the field element.
78 | * @param array $async_urls
79 | * An array of async Urls.
80 | *
81 | * @return array
82 | * A renderable array of the field element with attached libraries.
83 | */
84 | private function attachLibraries(array $element, array $async_urls) {
85 | if (!empty($async_urls)) {
86 | $element['#attached'] = [
87 | 'library' => [
88 | 'apigee_asyncapi_doc/reactjs',
89 | 'apigee_asyncapi_doc/asyncapi',
90 | ],
91 | ];
92 | }
93 | return $element;
94 | }
95 |
96 | }
97 |
--------------------------------------------------------------------------------
/modules/apigee_asyncapi_doc/templates/apigee-asyncapi-doc-file-link-field-item.html.twig:
--------------------------------------------------------------------------------
1 | {#
2 | /**
3 | * @file apigee-async-doc-file-link-field-item.html.twig
4 | * Copyright 2022 Google Inc.
5 | *
6 | * This program is free software; you can redistribute it and/or modify it under
7 | * the terms of the GNU General Public License version 2 as published by the
8 | * Free Software Foundation.
9 | *
10 | * This program is distributed in the hope that it will be useful, but WITHOUT
11 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
12 | * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
13 | * License for more details.
14 | *
15 | * You should have received a copy of the GNU General Public License along
16 | * with this program; if not, write to the Free Software Foundation, Inc., 51
17 | * Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18 | */
19 |
20 | /**
21 | * Default theme implementation for Apigee AsyncAPI Doc field items.
22 | *
23 | * Available variables:
24 | * - field_name: The name of the field.
25 | * - delta: The delta of the field item.
26 | * - link: A link to the file.
27 | *
28 | * @ingroup themeable
29 | */
30 | #}
31 |
32 | {% set module_path = _self|split('/templates') %}
33 |
36 |
37 |
38 |
--------------------------------------------------------------------------------
/modules/apigee_freeform_doc/apigee_freeform_doc.info.yml:
--------------------------------------------------------------------------------
1 | name: Free-form documentation for Apigee
2 | type: module
3 | description: Free-form documentation for Apigee
4 | package: Apigee (Experimental)
5 | core_version_requirement: ^10 || ^11
6 | dependencies:
7 | - apigee_api_catalog
8 | - file
9 | - text
10 |
--------------------------------------------------------------------------------
/modules/apigee_freeform_doc/apigee_freeform_doc.install:
--------------------------------------------------------------------------------
1 | get('node.type.locked');
32 | $locked['freeform_doc'] = 'freeform_doc';
33 | \Drupal::state()->set('node.type.locked', $locked);
34 | }
35 |
36 | /**
37 | * Implements hook_uninstall().
38 | */
39 | function apigee_freeform_doc_uninstall() {
40 | // Allow to delete a freeform_doc's node type.
41 | $locked = \Drupal::state()->get('node.type.locked');
42 | unset($locked['freeform_doc']);
43 | \Drupal::state()->set('node.type.locked', $locked);
44 | }
45 |
--------------------------------------------------------------------------------
/modules/apigee_freeform_doc/apigee_freeform_doc.links.action.yml:
--------------------------------------------------------------------------------
1 | node.freeform_doc.add_form:
2 | route_name: node.add
3 | title: 'Free-form Doc'
4 | route_parameters:
5 | node_type: 'freeform_doc'
6 | appears_on:
7 | - view.api_catalog_admin.page_1
8 |
--------------------------------------------------------------------------------
/modules/apigee_freeform_doc/apigee_freeform_doc.routing.yml:
--------------------------------------------------------------------------------
1 | apigee_freeform_doc.confirm_remove_views_references:
2 | path: '/admin/modules/uninstall/apigee-freeform-doc/confirm-remove-views-references'
3 | defaults:
4 | _title: 'Confirm remove views references'
5 | _form: 'Drupal\apigee_freeform_doc\Form\ConfirmRemoveViewsReferencesForm'
6 | requirements:
7 | _permission: 'administer modules'
8 |
--------------------------------------------------------------------------------
/modules/apigee_freeform_doc/apigee_freeform_doc.services.yml:
--------------------------------------------------------------------------------
1 | services:
2 | apigee_freeform_doc.event_subscriber:
3 | class: Drupal\apigee_freeform_doc\EventSubscriber\ApigeeFreeformDocSubscriber
4 | arguments: ['@messenger']
5 | tags:
6 | - { name: event_subscriber }
7 |
8 | apigee_freeform_doc.uninstall_validator:
9 | class: Drupal\apigee_freeform_doc\ApigeeFreeformDocUninstallValidator
10 | tags:
11 | - { name: module_install.uninstall_validator }
12 | arguments: ['@entity_type.manager', '@string_translation']
13 |
14 | apigee_freeform_doc.breadcrumb:
15 | class: Drupal\apigee_freeform_doc\ApigeeFreeformDocBreadcrumbBuilder
16 | tags:
17 | - { name: breadcrumb_builder, priority: 1000 }
18 |
--------------------------------------------------------------------------------
/modules/apigee_freeform_doc/config/install/field.field.node.freeform_doc.body.yml:
--------------------------------------------------------------------------------
1 | langcode: en
2 | status: true
3 | dependencies:
4 | config:
5 | - field.storage.node.body
6 | - node.type.freeform_doc
7 | module:
8 | - text
9 | id: node.freeform_doc.body
10 | field_name: body
11 | entity_type: node
12 | bundle: freeform_doc
13 | label: 'API Description'
14 | description: 'If the summary is not set, the trimmed API Description will end at the last full sentence before 200 character limit.'
15 | required: false
16 | translatable: true
17 | default_value: { }
18 | default_value_callback: ''
19 | settings:
20 | display_summary: true
21 | required_summary: false
22 | field_type: text_with_summary
23 |
--------------------------------------------------------------------------------
/modules/apigee_freeform_doc/config/install/field.field.node.freeform_doc.field_api_product.yml:
--------------------------------------------------------------------------------
1 | langcode: en
2 | status: true
3 | dependencies:
4 | config:
5 | - field.storage.node.field_api_product
6 | - node.type.freeform_doc
7 | id: node.freeform_doc.field_api_product
8 | field_name: field_api_product
9 | entity_type: node
10 | bundle: freeform_doc
11 | label: 'API Product'
12 | description: 'Apigee API Product'
13 | required: false
14 | translatable: false
15 | default_value: { }
16 | default_value_callback: ''
17 | settings:
18 | handler: 'default:api_product'
19 | handler_settings:
20 | target_bundles: null
21 | auto_create: false
22 | field_type: entity_reference
23 |
--------------------------------------------------------------------------------
/modules/apigee_freeform_doc/config/install/field.field.node.freeform_doc.field_freeform_spec_doc.yml:
--------------------------------------------------------------------------------
1 | langcode: en
2 | status: true
3 | dependencies:
4 | config:
5 | - field.storage.node.field_freeform_spec_doc
6 | - node.type.freeform_doc
7 | module:
8 | - file
9 | id: node.freeform_doc.field_freeform_spec_doc
10 | field_name: field_freeform_spec_doc
11 | entity_type: node
12 | bundle: freeform_doc
13 | label: 'Supporting docs'
14 | description: 'Upload the supporting doc/attachment here.'
15 | required: false
16 | translatable: false
17 | default_value: { }
18 | default_value_callback: ''
19 | settings:
20 | handler: 'default:file'
21 | handler_settings: { }
22 | file_directory: freeform_specs_doc
23 | file_extensions: 'zip jpg jpeg gif png txt html doc xls pdf ppt pps yaml'
24 | max_filesize: ''
25 | description_field: false
26 | field_type: file
27 |
--------------------------------------------------------------------------------
/modules/apigee_freeform_doc/config/install/field.storage.node.field_freeform_spec_doc.yml:
--------------------------------------------------------------------------------
1 | langcode: en
2 | status: true
3 | dependencies:
4 | enforced:
5 | module:
6 | - apigee_freeform_doc
7 | module:
8 | - file
9 | - node
10 | id: node.field_freeform_spec_doc
11 | field_name: field_freeform_spec_doc
12 | entity_type: node
13 | type: file
14 | settings:
15 | target_type: file
16 | display_field: false
17 | display_default: false
18 | uri_scheme: public
19 | module: file
20 | locked: false
21 | cardinality: -1
22 | translatable: false
23 | indexes: { }
24 | persist_with_no_fields: false
25 | custom_storage: false
26 |
--------------------------------------------------------------------------------
/modules/apigee_freeform_doc/config/install/node.type.freeform_doc.yml:
--------------------------------------------------------------------------------
1 | langcode: en
2 | status: true
3 | dependencies:
4 | module:
5 | - apigee_api_catalog
6 | - apigee_freeform_doc
7 | enforced:
8 | module:
9 | - apigee_freeform_doc
10 | type: freeform_doc
11 | name: 'Free-form Doc'
12 | description: 'Use Free-form Doc content to get Free-form Documentation.'
13 | help: ''
14 | new_revision: false
15 | display_submitted: false
16 | preview_mode: 1
17 |
--------------------------------------------------------------------------------
/modules/apigee_freeform_doc/config/optional/core.entity_form_display.node.freeform_doc.default.yml:
--------------------------------------------------------------------------------
1 | langcode: en
2 | status: true
3 | dependencies:
4 | config:
5 | - field.field.node.freeform_doc.body
6 | - field.field.node.freeform_doc.field_api_product
7 | - field.field.node.freeform_doc.field_freeform_spec_doc
8 | - node.type.freeform_doc
9 | module:
10 | - file
11 | - path
12 | - text
13 | id: node.freeform_doc.default
14 | targetEntityType: node
15 | bundle: freeform_doc
16 | mode: default
17 | content:
18 | title:
19 | type: string_textfield
20 | weight: 0
21 | region: content
22 | settings:
23 | size: 60
24 | placeholder: ''
25 | third_party_settings: { }
26 | body:
27 | type: text_textarea_with_summary
28 | weight: 2
29 | settings:
30 | rows: 3
31 | summary_rows: 2
32 | placeholder: ''
33 | show_summary: false
34 | third_party_settings: { }
35 | region: content
36 | field_freeform_spec_doc:
37 | type: file_generic
38 | weight: 3
39 | region: content
40 | settings:
41 | progress_indicator: throbber
42 | third_party_settings: { }
43 | field_api_product:
44 | weight: 4
45 | settings:
46 | match_operator: CONTAINS
47 | match_limit: 10
48 | size: 60
49 | placeholder: ''
50 | third_party_settings: { }
51 | type: entity_reference_autocomplete
52 | region: content
53 | created:
54 | type: datetime_timestamp
55 | weight: 5
56 | region: content
57 | settings: { }
58 | third_party_settings: { }
59 | promote:
60 | type: boolean_checkbox
61 | settings:
62 | display_label: true
63 | weight: 6
64 | region: content
65 | third_party_settings: { }
66 | uid:
67 | type: entity_reference_autocomplete
68 | weight: 7
69 | settings:
70 | match_operator: CONTAINS
71 | size: 60
72 | placeholder: ''
73 | match_limit: 10
74 | region: content
75 | third_party_settings: { }
76 | sticky:
77 | type: boolean_checkbox
78 | settings:
79 | display_label: true
80 | weight: 8
81 | region: content
82 | third_party_settings: { }
83 | path:
84 | type: path
85 | weight: 9
86 | region: content
87 | settings: { }
88 | third_party_settings: { }
89 | status:
90 | type: boolean_checkbox
91 | settings:
92 | display_label: true
93 | weight: 10
94 | region: content
95 | third_party_settings: { }
96 | hidden: { }
97 |
--------------------------------------------------------------------------------
/modules/apigee_freeform_doc/config/optional/core.entity_view_display.node.freeform_doc.default.yml:
--------------------------------------------------------------------------------
1 | langcode: en
2 | status: true
3 | dependencies:
4 | config:
5 | - field.field.node.freeform_doc.body
6 | - field.field.node.freeform_doc.field_api_product
7 | - field.field.node.freeform_doc.field_freeform_spec_doc
8 | - node.type.freeform_doc
9 | module:
10 | - file
11 | - text
12 | - user
13 | id: node.freeform_doc.default
14 | targetEntityType: node
15 | bundle: freeform_doc
16 | mode: default
17 | content:
18 | body:
19 | type: text_default
20 | label: hidden
21 | settings: { }
22 | third_party_settings: { }
23 | weight: 1
24 | region: content
25 | field_freeform_spec_doc:
26 | type: file_default
27 | label: hidden
28 | settings:
29 | use_description_as_link_text: true
30 | third_party_settings: { }
31 | weight: 2
32 | region: content
33 | links:
34 | settings: { }
35 | third_party_settings: { }
36 | weight: 0
37 | region: content
38 | hidden:
39 | field_api_product: true
40 | field_image: true
41 |
--------------------------------------------------------------------------------
/modules/apigee_freeform_doc/src/ApigeeFreeformDocBreadcrumbBuilder.php:
--------------------------------------------------------------------------------
1 | getParameter('node');
42 | return $node instanceof NodeInterface && $node->getType() === 'freeform_doc';
43 | }
44 |
45 | /**
46 | * {@inheritdoc}
47 | */
48 | public function build(RouteMatchInterface $route_match) {
49 | $breadcrumb = new Breadcrumb();
50 |
51 | $links[] = Link::createFromRoute($this->t('Home'), '');
52 |
53 | // API Catalog page is a view.
54 | $links[] = Link::createFromRoute($this->t('API Catalog'), 'view.apigee_api_catalog.page_1');
55 |
56 | $breadcrumb->setLinks($links);
57 | $breadcrumb->addCacheContexts(['route']);
58 |
59 | return $breadcrumb;
60 | }
61 |
62 | }
63 |
--------------------------------------------------------------------------------
/modules/apigee_freeform_doc/src/ApigeeFreeformDocUninstallValidator.php:
--------------------------------------------------------------------------------
1 | entityTypeManager = $entity_manager;
54 | $this->stringTranslation = $string_translation;
55 | }
56 |
57 | /**
58 | * {@inheritdoc}
59 | */
60 | public function validate($module) {
61 | $reasons = [];
62 | if ($module == 'apigee_freeform_doc') {
63 | if ($this->hasFreeformDocNodes()) {
64 | $reasons[] = $this->t('To uninstall Apigee Free-form Doc, first delete all Free-form Doc content');
65 | }
66 |
67 | // Restrict uninstall, if reference of freeform_doc is there in apigee_api_catalog view.
68 | $is_views_references = FALSE;
69 | $view = Views::getView('apigee_api_catalog');
70 | if (is_object($view)) {
71 | $display = $view->getDisplay();
72 |
73 | $filters = $view->display_handler->getOption('filters');
74 | if (isset($filters['type']['value']['freeform_doc'])) {
75 | $is_views_references = TRUE;
76 | }
77 | }
78 |
79 | // Restrict uninstall, if reference of freeform_doc is there in api_catalog_admin view.
80 | $view = Views::getView('api_catalog_admin');
81 | if (is_object($view)) {
82 | $display = $view->getDisplay();
83 |
84 | $filters = $view->display_handler->getOption('filters');
85 | if (isset($filters['type']['value']['freeform_doc'])) {
86 | $is_views_references = TRUE;
87 | }
88 | }
89 | if ($is_views_references) {
90 | $message_arguments = [
91 | ':url' => Url::fromRoute('apigee_freeform_doc.confirm_remove_views_references')->toString(),
92 | ];
93 | $reasons[] = $this->t('Click to remove Free-form Doc references from both "apigee_api_catalog" and "api_catalog_admin" views', $message_arguments);
94 | }
95 | }
96 |
97 | return $reasons;
98 | }
99 |
100 | /**
101 | * Determines if there are any freeform_doc nodes or not.
102 | *
103 | * @return bool
104 | * TRUE if there are forum nodes, FALSE otherwise.
105 | */
106 | protected function hasFreeformDocNodes() {
107 | $nodes = $this->entityTypeManager->getStorage('node')->getQuery()
108 | ->condition('type', 'freeform_doc')
109 | ->accessCheck(FALSE)
110 | ->range(0, 1)
111 | ->execute();
112 | return !empty($nodes);
113 | }
114 |
115 | }
116 |
--------------------------------------------------------------------------------
/modules/apigee_freeform_doc/src/EventSubscriber/ApigeeFreeformDocSubscriber.php:
--------------------------------------------------------------------------------
1 | messenger = $messenger;
49 | }
50 |
51 | /**
52 | * Update views on installation.
53 | *
54 | * @param \Drupal\Core\Config\ConfigCrudEvent $event
55 | * Config crud event.
56 | */
57 | public function configSave(ConfigCrudEvent $event) {
58 | $config = $event->getConfig();
59 | if ($config->getName() == 'core.entity_view_display.node.freeform_doc.default') {
60 |
61 | $view = Views::getView('apigee_api_catalog');
62 | if (is_object($view)) {
63 | $display = $view->getDisplay();
64 |
65 | $filters = $view->display_handler->getOption('filters');
66 | if ($filters['type']) {
67 | $filters['type']['value']['freeform_doc'] = 'freeform_doc';
68 | }
69 | if (isset($filters['type_1'])) {
70 | $filters['type_1']['value']['freeform_doc'] = 'freeform_doc';
71 | }
72 | $view->display_handler->overrideOption('filters', $filters);
73 |
74 | $view->save();
75 | \Drupal::messenger()->addStatus('Updating Views - API Catalog view');
76 | }
77 |
78 | $view = Views::getView('api_catalog_admin');
79 | if (is_object($view)) {
80 | $display = $view->getDisplay();
81 |
82 | $filters = $view->display_handler->getOption('filters');
83 | if ($filters['type']) {
84 | $filters['type']['value']['freeform_doc'] = 'freeform_doc';
85 | }
86 | if (isset($filters['type_1'])) {
87 | $filters['type_1']['value']['freeform_doc'] = 'freeform_doc';
88 | }
89 | $view->display_handler->overrideOption('filters', $filters);
90 |
91 | $view->save();
92 | \Drupal::messenger()->addStatus('Updating Views - API Catalog Admin');
93 | }
94 | }
95 | }
96 |
97 | /**
98 | * {@inheritdoc}
99 | */
100 | public static function getSubscribedEvents() {
101 | return [
102 | ConfigEvents::SAVE => 'configSave',
103 | ];
104 | }
105 |
106 | }
107 |
--------------------------------------------------------------------------------
/modules/apigee_freeform_doc/src/Form/ConfirmRemoveViewsReferencesForm.php:
--------------------------------------------------------------------------------
1 | t('Are you sure you want to remove references of Free-form in views before uninstallation of the module?');
45 | }
46 |
47 | /**
48 | * {@inheritdoc}
49 | */
50 | public function getCancelUrl() {
51 | return new Url('system.modules_uninstall');
52 | }
53 |
54 | /**
55 | * {@inheritdoc}
56 | */
57 | public function submitForm(array &$form, FormStateInterface $form_state) {
58 | $view = Views::getView('apigee_api_catalog');
59 | if (is_object($view)) {
60 | $display = $view->getDisplay();
61 |
62 | $filters = $view->display_handler->getOption('filters');
63 | if ($filters['type']['value']['freeform_doc']) {
64 | unset($filters['type']['value']['freeform_doc']);
65 | }
66 | if (isset($filters['type_1']) && $filters['type_1']['value']['freeform_doc']) {
67 | unset($filters['type_1']['value']['freeform_doc']);
68 | }
69 | $view->display_handler->overrideOption('filters', $filters);
70 |
71 | $view->save();
72 |
73 | $this->messenger()->addStatus($this->t('Updating Views - API Catalog'));
74 | }
75 |
76 | $view = Views::getView('api_catalog_admin');
77 | if (is_object($view)) {
78 | $display = $view->getDisplay();
79 |
80 | $filters = $view->display_handler->getOption('filters');
81 | if ($filters['type']['value']['freeform_doc']) {
82 | unset($filters['type']['value']['freeform_doc']);
83 | }
84 | if (isset($filters['type_1']) && $filters['type_1']['value']['freeform_doc']) {
85 | unset($filters['type_1']['value']['freeform_doc']);
86 | }
87 | $view->display_handler->overrideOption('filters', $filters);
88 |
89 | $view->save();
90 |
91 | $this->messenger()->addStatus($this->t('Updating Views - API Catalog Admin'));
92 | }
93 |
94 | $form_state->setRedirectUrl($this->getCancelUrl());
95 | }
96 |
97 | }
98 |
--------------------------------------------------------------------------------
/modules/apigee_graphql_doc/apigee_graphql_doc.info.yml:
--------------------------------------------------------------------------------
1 | name: GraphQL for Apigee
2 | type: module
3 | description: GraphQL for Apigee
4 | package: Apigee (Experimental)
5 | core_version_requirement: ^10 || ^11
6 | dependencies:
7 | - apigee_api_catalog
8 | - file_link
9 | - text
10 |
--------------------------------------------------------------------------------
/modules/apigee_graphql_doc/apigee_graphql_doc.install:
--------------------------------------------------------------------------------
1 | get('node.type.locked');
32 | $locked['graphql_doc'] = 'graphql_doc';
33 | \Drupal::state()->set('node.type.locked', $locked);
34 | }
35 |
36 | /**
37 | * Implements hook_uninstall().
38 | */
39 | function apigee_graphql_doc_uninstall() {
40 | // Allow to delete a graphql_doc's node type.
41 | $locked = \Drupal::state()->get('node.type.locked');
42 | unset($locked['graphql_doc']);
43 | \Drupal::state()->set('node.type.locked', $locked);
44 | }
45 |
--------------------------------------------------------------------------------
/modules/apigee_graphql_doc/apigee_graphql_doc.libraries.yml:
--------------------------------------------------------------------------------
1 | # Integration module library for GraphQL plugin.
2 | apigee_graphql_doc_integration:
3 | version: 1.0
4 | js:
5 | js/apigee-graphql-doc.js: {}
6 | css:
7 | component:
8 | css/apigee-graphql-doc.css: {}
9 | dependencies:
10 | - core/jquery
11 | - core/drupalSettings
12 | - apigee_graphql_doc/reactjs
13 | - apigee_graphql_doc/graphiql
14 |
15 | # Third-party library (CDN).
16 | reactjs:
17 | remote: https://reactjs.org
18 | version: VERSION
19 | license:
20 | name: MIT
21 | url: https://github.com/facebook/react/blob/main/LICENSE
22 | gpl-compatible: true
23 | js:
24 | https://unpkg.com/react/umd/react.production.min.js: {type: external, minified: true}
25 | https://unpkg.com/react-dom/umd/react-dom.production.min.js: {type: external, minified: true}
26 |
27 | # Third-party library (CDN).
28 | graphiql:
29 | remote: https://graphql.org
30 | version: VERSION
31 | license:
32 | name: MIT
33 | url: https://github.com/graphql/graphiql/blob/main/LICENSE
34 | gpl-compatible: true
35 | js:
36 | https://unpkg.com/graphiql/graphiql.min.js: {type: external, minified: true}
37 | css:
38 | component:
39 | https://unpkg.com/graphiql/graphiql.min.css: {type: external, minified: true}
40 |
--------------------------------------------------------------------------------
/modules/apigee_graphql_doc/apigee_graphql_doc.links.action.yml:
--------------------------------------------------------------------------------
1 | node.graphql_doc.add_form:
2 | route_name: node.add
3 | title: 'GraphQL Doc'
4 | route_parameters:
5 | node_type: 'graphql_doc'
6 | appears_on:
7 | - view.api_catalog_admin.page_1
8 |
--------------------------------------------------------------------------------
/modules/apigee_graphql_doc/apigee_graphql_doc.module:
--------------------------------------------------------------------------------
1 | [
36 | 'variables' => [
37 | 'field_name' => NULL,
38 | 'delta' => NULL,
39 | ],
40 | ],
41 | ];
42 | }
43 |
44 | /**
45 | * Implements hook_ENTITY_TYPE_load().
46 | */
47 | function apigee_graphql_doc_node_load($entities) {
48 | foreach ($entities as $entity) {
49 | if ($entity->bundle() != 'graphql_doc') {
50 | return;
51 | }
52 | if ($entity->get('field_graphql_spec_source_type')->value === 'file') {
53 | $spec_value = $entity->get('field_graphql_spec')->isEmpty() ? [] : $entity->get('field_graphql_spec')->getValue()[0];
54 | if (!empty($spec_value['target_id'])) {
55 | /* @var \Drupal\file\Entity\File $file */
56 | $file = \Drupal::entityTypeManager()
57 | ->getStorage('file')
58 | ->load($spec_value['target_id']);
59 |
60 | if ($file) {
61 | $path = '/node/' . $entity->id() . '/graphql';
62 | $entity->set('field_graphql_spec_file_link', ['uri' => Url::fromUri('base:' . $path , ['absolute' => TRUE])->toString()]);
63 | }
64 | }
65 | }
66 | }
67 | }
68 |
69 | /**
70 | * Implements hook_form_FORM_ID_alter().
71 | */
72 | function apigee_graphql_doc_form_node_form_alter(&$form, FormStateInterface $form_state, $form_id) {
73 | $node = $form_state->getFormObject()->getEntity();
74 | if ($node->bundle() != 'graphql_doc') {
75 | return;
76 | }
77 |
78 | $form['field_graphql_spec']['#states'] = [
79 | 'visible' => [
80 | ':input[name="field_graphql_spec_source_type"]' => ['value' => 'file'],
81 | ],
82 | ];
83 |
84 | // @todo: #states does not work on managed files.
85 | // @see: https://www.drupal.org/project/drupal/issues/2847425
86 | $form['field_graphql_spec']['widget'][0]['#states'] = [
87 | 'required' => [
88 | ':input[name="field_graphql_spec_source_type"]' => ['value' => 'file'],
89 | ],
90 | ];
91 |
92 | $form['field_graphql_spec_file_link']['#states'] = [
93 | 'visible' => [
94 | [':input[name="field_graphql_spec_source_type"]' => ['value' => 'url']],
95 | ],
96 | ];
97 |
98 | $form['field_graphql_spec_file_link']['widget'][0]['uri']['#states'] = [
99 | 'required' => [
100 | [':input[name="field_graphql_spec_source_type"]' => ['value' => 'url']],
101 | ],
102 | ];
103 |
104 | $form['#validate'][] = '_apigee_graphql_form_node_form_validate';
105 | }
106 |
107 | /**
108 | * Form validator for the "graphql_doc" node bundle.
109 | *
110 | * @param array $form
111 | * The form.
112 | * @param \Drupal\Core\Form\FormStateInterface $form_state
113 | * The form state.
114 | */
115 | function _apigee_graphql_form_node_form_validate(&$form, FormStateInterface $form_state) {
116 | $values = $form_state->getValues();
117 |
118 | if (empty($values['field_graphql_spec_source_type'])) {
119 | return;
120 | }
121 |
122 | // Make sure the field_graphql_spec (file) or field_graphql_spec_file_link (link)
123 | // is not empty, according to what was selected as the file source.
124 | $source = $values['field_graphql_spec_source_type'][0]['value'] ?: NULL;
125 | if ($source == 'file' && empty($values['field_graphql_spec'][0]['fids'][0])) {
126 | $form_state->setErrorByName('field_graphql_spec', t('Provide an GraphQL specification file.'));
127 | }
128 | elseif ($source == 'url' && empty($values['field_graphql_spec_file_link'][0]['uri'])) {
129 | $form_state->setErrorByName('field_graphql_spec_file_link', t('Provide the URL to an GraphQL specification file.'));
130 | }
131 | }
132 |
--------------------------------------------------------------------------------
/modules/apigee_graphql_doc/apigee_graphql_doc.routing.yml:
--------------------------------------------------------------------------------
1 | apigee_graphql_doc.confirm_remove_views_references:
2 | path: '/admin/modules/uninstall/apigee-graphql-doc/confirm-remove-views-references'
3 | defaults:
4 | _title: 'Confirm remove views references'
5 | _form: 'Drupal\apigee_graphql_doc\Form\ConfirmRemoveViewsReferencesForm'
6 | requirements:
7 | _permission: 'administer modules'
8 |
9 | entity.node.graphqlserver:
10 | path: '/node/{node}/graphql'
11 | defaults:
12 | _title: 'Mock graphql server'
13 | _controller: '\Drupal\apigee_graphql_doc\Controller\ApigeeGraphqlServerController::build'
14 | methods: [POST]
15 | requirements:
16 | _permission: 'access content'
17 | node: \d+
18 | options:
19 | node:
20 | type: entity:node
21 |
--------------------------------------------------------------------------------
/modules/apigee_graphql_doc/apigee_graphql_doc.services.yml:
--------------------------------------------------------------------------------
1 | services:
2 | apigee_graphql_doc.event_subscriber:
3 | class: Drupal\apigee_graphql_doc\EventSubscriber\ApigeeGraphqlDocSubscriber
4 | arguments: ['@messenger']
5 | tags:
6 | - { name: event_subscriber }
7 |
8 | apigee_graphql_doc.uninstall_validator:
9 | class: Drupal\apigee_graphql_doc\ApigeeGraphqlDocUninstallValidator
10 | tags:
11 | - { name: module_install.uninstall_validator }
12 | arguments: ['@entity_type.manager', '@string_translation']
13 |
14 | apigee_graphql_doc.breadcrumb:
15 | class: Drupal\apigee_graphql_doc\ApigeeGraphqlDocBreadcrumbBuilder
16 | tags:
17 | - { name: breadcrumb_builder, priority: 1000 }
18 |
--------------------------------------------------------------------------------
/modules/apigee_graphql_doc/config/install/field.field.node.graphql_doc.body.yml:
--------------------------------------------------------------------------------
1 | langcode: en
2 | status: true
3 | dependencies:
4 | config:
5 | - field.storage.node.body
6 | - node.type.graphql_doc
7 | module:
8 | - text
9 | id: node.graphql_doc.body
10 | field_name: body
11 | entity_type: node
12 | bundle: graphql_doc
13 | label: Description
14 | description: ''
15 | required: false
16 | translatable: true
17 | default_value: { }
18 | default_value_callback: ''
19 | settings:
20 | display_summary: true
21 | required_summary: false
22 | field_type: text_with_summary
23 |
--------------------------------------------------------------------------------
/modules/apigee_graphql_doc/config/install/field.field.node.graphql_doc.field_api_product.yml:
--------------------------------------------------------------------------------
1 | langcode: en
2 | status: true
3 | dependencies:
4 | config:
5 | - field.storage.node.field_api_product
6 | - node.type.graphql_doc
7 | id: node.graphql_doc.field_api_product
8 | field_name: field_api_product
9 | entity_type: node
10 | bundle: graphql_doc
11 | label: 'API Product'
12 | description: 'Apigee API Product'
13 | required: false
14 | translatable: false
15 | default_value: { }
16 | default_value_callback: ''
17 | settings:
18 | handler: 'default:api_product'
19 | handler_settings:
20 | target_bundles: null
21 | auto_create: false
22 | field_type: entity_reference
23 |
--------------------------------------------------------------------------------
/modules/apigee_graphql_doc/config/install/field.field.node.graphql_doc.field_graphql_spec.yml:
--------------------------------------------------------------------------------
1 | langcode: en
2 | status: true
3 | dependencies:
4 | config:
5 | - field.storage.node.field_graphql_spec
6 | - node.type.graphql_doc
7 | module:
8 | - file
9 | id: node.graphql_doc.field_graphql_spec
10 | field_name: field_graphql_spec
11 | entity_type: node
12 | bundle: graphql_doc
13 | label: 'GraphQL specification'
14 | description: 'The spec snapshot. Please note that it will be rendered by mock server and only schema will be accessible.'
15 | required: false
16 | translatable: false
17 | default_value: { }
18 | default_value_callback: ''
19 | settings:
20 | handler: 'default:file'
21 | handler_settings: { }
22 | file_directory: graphql_specs
23 | file_extensions: 'graphql'
24 | max_filesize: ''
25 | description_field: false
26 | field_type: file
--------------------------------------------------------------------------------
/modules/apigee_graphql_doc/config/install/field.field.node.graphql_doc.field_graphql_spec_file_link.yml:
--------------------------------------------------------------------------------
1 | langcode: en
2 | status: true
3 | dependencies:
4 | config:
5 | - field.storage.node.field_graphql_spec_file_link
6 | - node.type.graphql_doc
7 | module:
8 | - file_link
9 | id: node.graphql_doc.field_graphql_spec_file_link
10 | field_name: field_graphql_spec_file_link
11 | entity_type: node
12 | bundle: graphql_doc
13 | label: 'GraphQL Endpoint'
14 | description: 'Enter the the GraphQL endpoint'
15 | required: false
16 | translatable: false
17 | default_value: { }
18 | default_value_callback: ''
19 | settings:
20 | link_type: 17
21 | title: 0
22 | file_extensions: ''
23 | no_extension: true
24 | deferred_request: false
25 | field_type: file_link
26 |
--------------------------------------------------------------------------------
/modules/apigee_graphql_doc/config/install/field.field.node.graphql_doc.field_graphql_spec_source_type.yml:
--------------------------------------------------------------------------------
1 | langcode: en
2 | status: true
3 | dependencies:
4 | config:
5 | - field.storage.node.field_graphql_spec_source_type
6 | - node.type.graphql_doc
7 | module:
8 | - options
9 | id: node.graphql_doc.field_graphql_spec_source_type
10 | field_name: field_graphql_spec_source_type
11 | entity_type: node
12 | bundle: graphql_doc
13 | label: 'Specification source type'
14 | description: 'Indicate if the GraphQL spec will be provided as a file for upload or a URL.'
15 | required: true
16 | translatable: false
17 | default_value: { }
18 | default_value_callback: ''
19 | settings: { }
20 | field_type: list_string
21 |
--------------------------------------------------------------------------------
/modules/apigee_graphql_doc/config/install/field.storage.node.field_graphql_spec.yml:
--------------------------------------------------------------------------------
1 | langcode: en
2 | status: true
3 | dependencies:
4 | enforced:
5 | module:
6 | - apigee_graphql_doc
7 | module:
8 | - file
9 | - node
10 | id: node.field_graphql_spec
11 | field_name: field_graphql_spec
12 | entity_type: node
13 | type: file
14 | settings:
15 | target_type: file
16 | display_field: false
17 | display_default: false
18 | uri_scheme: public
19 | module: file
20 | locked: false
21 | cardinality: 1
22 | translatable: false
23 | indexes: { }
24 | persist_with_no_fields: false
25 | custom_storage: false
26 |
--------------------------------------------------------------------------------
/modules/apigee_graphql_doc/config/install/field.storage.node.field_graphql_spec_file_link.yml:
--------------------------------------------------------------------------------
1 | langcode: en
2 | status: true
3 | dependencies:
4 | enforced:
5 | module:
6 | - apigee_graphql_doc
7 | module:
8 | - file_link
9 | - node
10 | id: node.field_graphql_spec_file_link
11 | field_name: field_graphql_spec_file_link
12 | entity_type: node
13 | type: file_link
14 | settings: { }
15 | module: file_link
16 | locked: false
17 | cardinality: 1
18 | translatable: true
19 | indexes: { }
20 | persist_with_no_fields: false
21 | custom_storage: false
22 |
--------------------------------------------------------------------------------
/modules/apigee_graphql_doc/config/install/field.storage.node.field_graphql_spec_source_type.yml:
--------------------------------------------------------------------------------
1 | langcode: en
2 | status: true
3 | dependencies:
4 | enforced:
5 | module:
6 | - apigee_graphql_doc
7 | module:
8 | - node
9 | - options
10 | id: node.field_graphql_spec_source_type
11 | field_name: field_graphql_spec_source_type
12 | entity_type: node
13 | type: list_string
14 | settings:
15 | allowed_values:
16 | -
17 | value: file
18 | label: File
19 | -
20 | value: url
21 | label: URL
22 | allowed_values_function: ''
23 | module: options
24 | locked: false
25 | cardinality: 1
26 | translatable: false
27 | indexes: { }
28 | persist_with_no_fields: false
29 | custom_storage: false
30 |
--------------------------------------------------------------------------------
/modules/apigee_graphql_doc/config/install/node.type.graphql_doc.yml:
--------------------------------------------------------------------------------
1 | langcode: en
2 | dependencies:
3 | module:
4 | - apigee_api_catalog
5 | - apigee_graphql_doc
6 | enforced:
7 | module:
8 | - apigee_graphql_doc
9 | type: graphql_doc
10 | name: 'GraphQL Doc'
11 | description: 'Use GraphQL Doc content to get GraphQL Doc.'
12 | help: ''
13 | new_revision: false
14 | display_submitted: false
15 | preview_mode: 0
16 | status: true
17 |
--------------------------------------------------------------------------------
/modules/apigee_graphql_doc/config/optional/core.entity_form_display.node.graphql_doc.default.yml:
--------------------------------------------------------------------------------
1 | langcode: en
2 | status: true
3 | dependencies:
4 | config:
5 | - field.field.node.graphql_doc.body
6 | - field.field.node.graphql_doc.field_api_product
7 | - field.field.node.graphql_doc.field_graphql_spec
8 | - field.field.node.graphql_doc.field_graphql_spec_file_link
9 | - field.field.node.graphql_doc.field_graphql_spec_source_type
10 | - node.type.graphql_doc
11 | module:
12 | - file_link
13 | - path
14 | - text
15 | id: node.graphql_doc.default
16 | targetEntityType: node
17 | bundle: graphql_doc
18 | mode: default
19 | content:
20 | title:
21 | type: string_textfield
22 | weight: 0
23 | region: content
24 | settings:
25 | size: 60
26 | placeholder: ''
27 | third_party_settings: { }
28 | body:
29 | type: text_textarea_with_summary
30 | weight: 1
31 | settings:
32 | rows: 9
33 | summary_rows: 3
34 | placeholder: ''
35 | show_summary: false
36 | third_party_settings: { }
37 | region: content
38 | field_graphql_spec_source_type:
39 | type: options_select
40 | weight: 4
41 | region: content
42 | settings: { }
43 | third_party_settings: { }
44 | field_graphql_spec:
45 | type: file_generic
46 | weight: 5
47 | region: content
48 | settings:
49 | progress_indicator: throbber
50 | third_party_settings: { }
51 | field_graphql_spec_file_link:
52 | type: file_link_default
53 | weight: 6
54 | region: content
55 | settings:
56 | placeholder_url: ''
57 | placeholder_title: ''
58 | third_party_settings: { }
59 | field_api_product:
60 | weight: 7
61 | settings:
62 | match_operator: CONTAINS
63 | match_limit: 10
64 | size: 60
65 | placeholder: ''
66 | third_party_settings: { }
67 | type: entity_reference_autocomplete
68 | region: content
69 | created:
70 | type: datetime_timestamp
71 | weight: 8
72 | region: content
73 | settings: { }
74 | third_party_settings: { }
75 | promote:
76 | type: boolean_checkbox
77 | settings:
78 | display_label: true
79 | weight: 9
80 | region: content
81 | third_party_settings: { }
82 | uid:
83 | type: entity_reference_autocomplete
84 | weight: 10
85 | settings:
86 | match_operator: CONTAINS
87 | size: 60
88 | placeholder: ''
89 | match_limit: 10
90 | region: content
91 | third_party_settings: { }
92 | sticky:
93 | type: boolean_checkbox
94 | settings:
95 | display_label: true
96 | weight: 11
97 | region: content
98 | third_party_settings: { }
99 | path:
100 | type: path
101 | weight: 12
102 | region: content
103 | settings: { }
104 | third_party_settings: { }
105 | status:
106 | type: boolean_checkbox
107 | settings:
108 | display_label: true
109 | weight: 13
110 | region: content
111 | third_party_settings: { }
112 | hidden: { }
113 |
114 |
--------------------------------------------------------------------------------
/modules/apigee_graphql_doc/config/optional/core.entity_view_display.node.graphql_doc.default.yml:
--------------------------------------------------------------------------------
1 | langcode: en
2 | status: true
3 | dependencies:
4 | config:
5 | - field.field.node.graphql_doc.body
6 | - field.field.node.graphql_doc.field_api_product
7 | - field.field.node.graphql_doc.field_graphql_spec
8 | - field.field.node.graphql_doc.field_graphql_spec_file_link
9 | - field.field.node.graphql_doc.field_graphql_spec_source_type
10 | - node.type.graphql_doc
11 | module:
12 | - file_link
13 | - text
14 | - user
15 | id: node.graphql_doc.default
16 | targetEntityType: node
17 | bundle: graphql_doc
18 | mode: default
19 | content:
20 | body:
21 | label: hidden
22 | type: text_default
23 | weight: 1
24 | settings: { }
25 | third_party_settings: { }
26 | region: content
27 | field_graphql_spec_file_link:
28 | weight: 3
29 | label: hidden
30 | settings:
31 | trim_length: 80
32 | url_only: true
33 | url_plain: false
34 | rel: ''
35 | target: ''
36 | format_size: true
37 | third_party_settings: { }
38 | type: apigee_graphql_doc_graphiql
39 | region: content
40 | links:
41 | weight: 0
42 | region: content
43 | settings: { }
44 | third_party_settings: { }
45 | hidden:
46 | field_api_product: true
47 |
--------------------------------------------------------------------------------
/modules/apigee_graphql_doc/css/apigee-graphql-doc.css:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2021 Google Inc.
3 | *
4 | * This program is free software; you can redistribute it and/or modify it under
5 | * the terms of the GNU General Public License version 2 as published by the
6 | * Free Software Foundation.
7 | *
8 | * This program is distributed in the hope that it will be useful, but WITHOUT
9 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
10 | * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
11 | * License for more details.
12 | *
13 | * You should have received a copy of the GNU General Public License along
14 | * with this program; if not, write to the Free Software Foundation, Inc., 51
15 | * Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 | */
17 |
18 | .graphiql-container {
19 | height: 99vh;
20 | }
21 |
--------------------------------------------------------------------------------
/modules/apigee_graphql_doc/js/apigee-graphql-doc.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2021 Google Inc.
3 | *
4 | * This program is free software; you can redistribute it and/or modify it under
5 | * the terms of the GNU General Public License version 2 as published by the
6 | * Free Software Foundation.
7 | *
8 | * This program is distributed in the hope that it will be useful, but WITHOUT
9 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
10 | * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
11 | * License for more details.
12 | *
13 | * You should have received a copy of the GNU General Public License along
14 | * with this program; if not, write to the Free Software Foundation, Inc., 51
15 | * Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 | */
17 |
18 | /**
19 | * @file
20 | * Custom scripts to render file fields with Apigee Graphql Doc.
21 | */
22 |
23 | (function ($, window, Drupal, drupalSettings) {
24 |
25 | Drupal.behaviors.apigeeGraphiqlFormatter = {
26 | attach: function(context) {
27 | // Iterate over fields and render each field item.
28 | for (var fieldName in drupalSettings.apigeeGraphqlDocFormatter) {
29 | if (drupalSettings.apigeeGraphqlDocFormatter.hasOwnProperty(fieldName)) {
30 | var field = drupalSettings.apigeeGraphqlDocFormatter[fieldName];
31 | for (var fieldDelta = 0; fieldDelta < field.graphqlUrls.length; fieldDelta++) {
32 | ReactDOM.render(
33 | React.createElement(GraphiQL, {
34 | fetcher: GraphiQL.createFetcher({ url: field.graphqlUrls[fieldDelta] }),
35 | defaultSecondaryEditorOpen: true,
36 | headerEditorEnabled: true,
37 | shouldPersistHeaders: true,
38 | }),
39 | document.getElementById(`graphql-explorer-${fieldName}-${fieldDelta}`),
40 | );
41 | }
42 | }
43 | }
44 | }
45 | };
46 |
47 | }(jQuery, window, Drupal, drupalSettings));
48 |
--------------------------------------------------------------------------------
/modules/apigee_graphql_doc/src/ApigeeGraphqlDocBreadcrumbBuilder.php:
--------------------------------------------------------------------------------
1 | getParameter('node');
42 | return $node instanceof NodeInterface && $node->getType() === 'graphql_doc';
43 | }
44 |
45 | /**
46 | * {@inheritdoc}
47 | */
48 | public function build(RouteMatchInterface $route_match) {
49 | $breadcrumb = new Breadcrumb();
50 |
51 | $links[] = Link::createFromRoute($this->t('Home'), '');
52 |
53 | // API Catalog page is a view.
54 | $links[] = Link::createFromRoute($this->t('API Catalog'), 'view.apigee_api_catalog.page_1');
55 |
56 | $breadcrumb->setLinks($links);
57 | $breadcrumb->addCacheContexts(['route']);
58 |
59 | return $breadcrumb;
60 | }
61 |
62 | }
63 |
--------------------------------------------------------------------------------
/modules/apigee_graphql_doc/src/ApigeeGraphqlDocUninstallValidator.php:
--------------------------------------------------------------------------------
1 | entityTypeManager = $entity_manager;
54 | $this->stringTranslation = $string_translation;
55 | }
56 |
57 | /**
58 | * {@inheritdoc}
59 | */
60 | public function validate($module) {
61 | $reasons = [];
62 | if ($module == 'apigee_graphql_doc') {
63 | if ($this->hasGraphqlDocNodes()) {
64 | $reasons[] = $this->t('To uninstall Apigee GraphQL Doc, first delete all GraphQL Doc content');
65 | }
66 |
67 | // Restrict uninstall, if reference of graphql_doc is there in apigee_api_catalog view.
68 | $is_views_references = FALSE;
69 | $view = Views::getView('apigee_api_catalog');
70 | if (is_object($view)) {
71 | $display = $view->getDisplay();
72 |
73 | $filters = $view->display_handler->getOption('filters');
74 | if (isset($filters['type']['value']['graphql_doc'])) {
75 | $is_views_references = TRUE;
76 | }
77 | }
78 |
79 | // Restrict uninstall, if reference of graphql_doc is there in api_catalog_admin view.
80 | $view = Views::getView('api_catalog_admin');
81 | if (is_object($view)) {
82 | $display = $view->getDisplay();
83 |
84 | $filters = $view->display_handler->getOption('filters');
85 | if (isset($filters['type']['value']['graphql_doc'])) {
86 | $is_views_references = TRUE;
87 | }
88 | }
89 | if ($is_views_references) {
90 | $message_arguments = [
91 | ':url' => Url::fromRoute('apigee_graphql_doc.confirm_remove_views_references')->toString(),
92 | ];
93 | $reasons[] = $this->t('Click to remove GraphQL Doc references from both "apigee_api_catalog" and "api_catalog_admin" views', $message_arguments);
94 | }
95 | }
96 |
97 | return $reasons;
98 | }
99 |
100 | /**
101 | * Determines if there are any graphql_doc nodes or not.
102 | *
103 | * @return bool
104 | * TRUE if there are forum nodes, FALSE otherwise.
105 | */
106 | protected function hasGraphqlDocNodes() {
107 | $nodes = $this->entityTypeManager->getStorage('node')->getQuery()
108 | ->condition('type', 'graphql_doc')
109 | ->accessCheck(FALSE)
110 | ->range(0, 1)
111 | ->execute();
112 | return !empty($nodes);
113 | }
114 |
115 | }
116 |
--------------------------------------------------------------------------------
/modules/apigee_graphql_doc/src/Controller/ApigeeGraphqlServerController.php:
--------------------------------------------------------------------------------
1 | bundle() != 'graphql_doc' && $node->get('field_graphql_spec_source_type')->value != 'file') {
50 | return new JsonResponse('Error');
51 | }
52 | $field_graphql_spec = $node->get('field_graphql_spec');
53 |
54 | if ($field_graphql_spec && $field_graphql_spec->target_id) {
55 | /** @var \Drupal\file\Entity\File $file */
56 | $file = File::load($field_graphql_spec->target_id);
57 |
58 | if ($file) {
59 | $schema_url = $file->getFileUri();
60 | $contents = file_get_contents($schema_url);
61 | $schema = BuildSchema::build($contents);
62 |
63 | $rawInput = \Drupal::request()->getContent();
64 | $input = Json::decode((string) $rawInput);
65 | $query = $input['query'];
66 | $variableValues = $input['variables'] ?? NULL;
67 |
68 | $rootValue = ['prefix' => 'You said: '];
69 | $result = GraphQL::executeQuery($schema, $query, $rootValue, NULL, $variableValues)->toArray();
70 | return new JsonResponse($result);
71 | }
72 | }
73 |
74 | }
75 | catch (Throwable $e) {
76 | $output = [
77 | 'error' => [
78 | 'message' => $e->getMessage(),
79 | ],
80 | ];
81 | }
82 | }
83 |
84 | }
85 |
--------------------------------------------------------------------------------
/modules/apigee_graphql_doc/src/EventSubscriber/ApigeeGraphqlDocSubscriber.php:
--------------------------------------------------------------------------------
1 | messenger = $messenger;
49 | }
50 |
51 | /**
52 | * Update views on installation.
53 | *
54 | * @param \Drupal\Core\Config\ConfigCrudEvent $event
55 | * Config crud event.
56 | */
57 | public function configSave(ConfigCrudEvent $event) {
58 | $config = $event->getConfig();
59 | if ($config->getName() == 'core.entity_view_display.node.graphql_doc.default') {
60 |
61 | $view = Views::getView('apigee_api_catalog');
62 | if (is_object($view)) {
63 | $display = $view->getDisplay();
64 |
65 | $filters = $view->display_handler->getOption('filters');
66 | if ($filters['type']) {
67 | $filters['type']['value']['graphql_doc'] = 'graphql_doc';
68 | }
69 | if (isset($filters['type_1'])) {
70 | $filters['type_1']['value']['graphql_doc'] = 'graphql_doc';
71 | }
72 | $view->display_handler->overrideOption('filters', $filters);
73 |
74 | $view->save();
75 | \Drupal::messenger()->addStatus('Updating Views - API Catalog view');
76 | }
77 |
78 | $view = Views::getView('api_catalog_admin');
79 | if (is_object($view)) {
80 | $display = $view->getDisplay();
81 |
82 | $filters = $view->display_handler->getOption('filters');
83 | if ($filters['type']) {
84 | $filters['type']['value']['graphql_doc'] = 'graphql_doc';
85 | }
86 | if (isset($filters['type_1'])) {
87 | $filters['type_1']['value']['graphql_doc'] = 'graphql_doc';
88 | }
89 | $view->display_handler->overrideOption('filters', $filters);
90 |
91 | $view->save();
92 | \Drupal::messenger()->addStatus('Updating Views - API Catalog Admin');
93 | }
94 | }
95 | }
96 |
97 | /**
98 | * {@inheritdoc}
99 | */
100 | public static function getSubscribedEvents() {
101 | return [
102 | ConfigEvents::SAVE => 'configSave',
103 | ];
104 | }
105 |
106 | }
107 |
--------------------------------------------------------------------------------
/modules/apigee_graphql_doc/src/Form/ConfirmRemoveViewsReferencesForm.php:
--------------------------------------------------------------------------------
1 | t('Are you sure you want to remove references of GraphQL in views before uninstallation of the module?');
45 | }
46 |
47 | /**
48 | * {@inheritdoc}
49 | */
50 | public function getCancelUrl() {
51 | return new Url('system.modules_uninstall');
52 | }
53 |
54 | /**
55 | * {@inheritdoc}
56 | */
57 | public function submitForm(array &$form, FormStateInterface $form_state) {
58 | $view = Views::getView('apigee_api_catalog');
59 | if (is_object($view)) {
60 | $display = $view->getDisplay();
61 |
62 | $filters = $view->display_handler->getOption('filters');
63 | if ($filters['type']['value']['graphql_doc']) {
64 | unset($filters['type']['value']['graphql_doc']);
65 | }
66 | if (isset($filters['type_1']) && $filters['type_1']['value']['graphql_doc']) {
67 | unset($filters['type_1']['value']['graphql_doc']);
68 | }
69 | $view->display_handler->overrideOption('filters', $filters);
70 |
71 | $view->save();
72 |
73 | $this->messenger()->addStatus($this->t('Updating Views - API Catalog'));
74 | }
75 |
76 | $view = Views::getView('api_catalog_admin');
77 | if (is_object($view)) {
78 | $display = $view->getDisplay();
79 |
80 | $filters = $view->display_handler->getOption('filters');
81 | if ($filters['type']['value']['graphql_doc']) {
82 | unset($filters['type']['value']['graphql_doc']);
83 | }
84 | if (isset($filters['type_1']) && $filters['type_1']['value']['graphql_doc']) {
85 | unset($filters['type_1']['value']['graphql_doc']);
86 | }
87 | $view->display_handler->overrideOption('filters', $filters);
88 |
89 | $view->save();
90 |
91 | $this->messenger()->addStatus($this->t('Updating Views - API Catalog Admin'));
92 | }
93 |
94 | $form_state->setRedirectUrl($this->getCancelUrl());
95 | }
96 |
97 | }
98 |
--------------------------------------------------------------------------------
/modules/apigee_graphql_doc/src/Plugin/Field/FieldFormatter/GraphiqlFormatter.php:
--------------------------------------------------------------------------------
1 | $item) {
46 | $element[$delta] = [
47 | '#theme' => 'apigee_graphql_doc_file_link_field_item',
48 | '#field_name' => $this->fieldDefinition->getName(),
49 | '#delta' => $delta,
50 | ];
51 | }
52 |
53 | return $element;
54 | }
55 |
56 | /**
57 | * {@inheritdoc}
58 | */
59 | public function view(FieldItemListInterface $items, $langcode = NULL) {
60 | $element = parent::view($items, $langcode);
61 |
62 | $graphql_urls = [];
63 | foreach ($items as $delta => $item) {
64 | /** @var \Drupal\link\Plugin\Field\FieldType\LinkItem $item */
65 | // Not validating URLs or paths.
66 | $graphql_urls[] = $item->getUrl()->toString();
67 | }
68 |
69 | return $this->attachLibraries($element, $graphql_urls);
70 | }
71 |
72 | /**
73 | * Helper function to attach library definitions and pass JavaScript settings.
74 | *
75 | * @param array $element
76 | * A renderable array of the field element.
77 | * @param array $graphql_urls
78 | * An array of GraphQL Urls.
79 | *
80 | * @return array
81 | * A renderable array of the field element with attached libraries.
82 | */
83 | private function attachLibraries(array $element, array $graphql_urls) {
84 | if (!empty($graphql_urls)) {
85 | $element['#attached'] = [
86 | 'library' => [
87 | 'apigee_graphql_doc/reactjs',
88 | 'apigee_graphql_doc/graphiql',
89 | 'apigee_graphql_doc/apigee_graphql_doc_integration',
90 | ],
91 | 'drupalSettings' => [
92 | 'apigeeGraphqlDocFormatter' => [
93 | $this->fieldDefinition->getName() => [
94 | 'graphqlUrls' => $graphql_urls,
95 | ],
96 | ],
97 | ],
98 | ];
99 | }
100 | return $element;
101 | }
102 |
103 | }
104 |
--------------------------------------------------------------------------------
/modules/apigee_graphql_doc/templates/apigee-graphql-doc-file-link-field-item.html.twig:
--------------------------------------------------------------------------------
1 | {#
2 | /**
3 | * @file apigee-graphql-doc-file-link-field-item.html.twig
4 | * Copyright 2021 Google Inc.
5 | *
6 | * This program is free software; you can redistribute it and/or modify it under
7 | * the terms of the GNU General Public License version 2 as published by the
8 | * Free Software Foundation.
9 | *
10 | * This program is distributed in the hope that it will be useful, but WITHOUT
11 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
12 | * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
13 | * License for more details.
14 | *
15 | * You should have received a copy of the GNU General Public License along
16 | * with this program; if not, write to the Free Software Foundation, Inc., 51
17 | * Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18 | */
19 |
20 | /**
21 | * Default theme implementation for Apigee GraphQL Doc field items.
22 | *
23 | * Available variables:
24 | * - field_name: The name of the field.
25 | * - delta: The delta of the field item.
26 | *
27 | * @ingroup themeable
28 | */
29 | #}
30 |
31 |
35 |
--------------------------------------------------------------------------------
/phpcs.xml.dist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | */.git/*
5 | */config/*
6 | */css/*
7 | */js/*
8 | */vendor/*
9 | \.md
10 |
11 |
12 |
13 |
14 |
15 | 0
16 |
17 |
18 |
19 | 0
20 |
21 |
25 |
26 | 0
27 |
28 |
29 |
30 |
--------------------------------------------------------------------------------
/phpunit.core.xml.dist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
9 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 | ./tests/TestSuites/UnitTestSuite.php
46 |
47 |
48 | ./tests/TestSuites/KernelTestSuite.php
49 |
50 |
51 | ./tests/TestSuites/FunctionalTestSuite.php
52 |
53 |
54 | ./tests/TestSuites/FunctionalJavascriptTestSuite.php
55 |
56 |
57 | ./tests/TestSuites/BuildTestSuite.php
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 | ../modules/contrib/apigee_api_catalog
71 |
72 |
73 | ../modules/contrib/apigee_api_catalog/tests
74 | ../modules/contrib/apigee_api_catalog/test_modules
75 |
76 |
77 |
78 |
79 |
--------------------------------------------------------------------------------
/src/ApigeeApiCatalogBreadcrumbBuilder.php:
--------------------------------------------------------------------------------
1 | getParameter('node');
42 | return $node instanceof NodeInterface && $node->getType() === 'apidoc';
43 | }
44 |
45 | /**
46 | * {@inheritdoc}
47 | */
48 | public function build(RouteMatchInterface $route_match) {
49 | $breadcrumb = new Breadcrumb();
50 |
51 | $links[] = Link::createFromRoute($this->t('Home'), '');
52 |
53 | // API Catalog is a view.
54 | $links[] = Link::createFromRoute($this->t('API Catalog'), 'view.apigee_api_catalog.page_1');
55 |
56 | $breadcrumb->setLinks($links);
57 | $breadcrumb->addCacheContexts(['route']);
58 |
59 | return $breadcrumb;
60 | }
61 |
62 | }
63 |
--------------------------------------------------------------------------------
/src/Entity/Form/ApiDocReimportSpecForm.php:
--------------------------------------------------------------------------------
1 | messenger = $messenger;
76 | $this->specFetcher = $spec_fetcher;
77 | }
78 |
79 | /**
80 | * {@inheritdoc}
81 | */
82 | public static function create(ContainerInterface $container) {
83 | return new static(
84 | $container->get('entity.repository'),
85 | $container->get('entity_type.bundle.info'),
86 | $container->get('datetime.time'),
87 | $container->get('messenger'),
88 | $container->get('apigee_api_catalog.spec_fetcher')
89 | );
90 | }
91 |
92 | /**
93 | * Checks access for the form page.
94 | *
95 | * @param \Drupal\Core\Routing\RouteMatchInterface $route_match
96 | * The route match.
97 | * @param \Drupal\Core\Session\AccountInterface $account
98 | * Run access checks for this account.
99 | *
100 | * @return \Drupal\Core\Access\AccessResultInterface
101 | * The access result.
102 | */
103 | public function checkAccess(RouteMatchInterface $route_match, AccountInterface $account) {
104 | /** @var \Drupal\node\NodeInterface $entity */
105 | $entity = $route_match->getParameter('node');
106 |
107 | return AccessResult::allowedIf($entity->bundle() == 'apidoc' && $entity->access('update', $account));
108 | }
109 |
110 | /**
111 | * {@inheritdoc}
112 | */
113 | public function getQuestion() {
114 | return $this->t('Are you sure you want to update the OpenAPI specification file from URL on API Doc %name?', [
115 | '%name' => $this->entity->label(),
116 | ]);
117 | }
118 |
119 | /**
120 | * {@inheritdoc}
121 | */
122 | public function getCancelUrl() {
123 | return new Url('view.api_catalog_admin.page_1');
124 | }
125 |
126 | /**
127 | * {@inheritdoc}
128 | */
129 | public function getDescription() {
130 | return $this->t('This will replace the current OpenAPI specification file.
131 | This action cannot be undone.');
132 | }
133 |
134 | /**
135 | * {@inheritdoc}
136 | */
137 | public function submitForm(array &$form, FormStateInterface $form_state) {
138 | /** @var \Drupal\node\NodeInterface $entity */
139 | $entity = $this->getEntity();
140 |
141 | if ($entity->get('field_apidoc_spec_file_source')->value != SpecFetcherInterface::SPEC_AS_URL) {
142 | $this->messenger()->addStatus($this->t('API Doc %label is using a local file as the the source, there nothing to import.', [
143 | '%label' => $this->entity->label(),
144 | ]));
145 |
146 | return;
147 | }
148 |
149 | $fetch_status = $this->specFetcher->fetchSpec($entity);
150 | // If STATUS_ERROR the error is displayed already by the fetchSpec() method.
151 | if ($fetch_status != SpecFetcherInterface::STATUS_ERROR) {
152 | if ($entity->getEntityType()->isRevisionable()) {
153 | $entity->setNewRevision();
154 | }
155 | $entity->save();
156 | $this->messenger()->addStatus($this->t('API Doc %label: imported the OpenAPI specification file from URL.', [
157 | '%label' => $this->entity->label(),
158 | ]));
159 | }
160 | }
161 |
162 | }
163 |
--------------------------------------------------------------------------------
/src/EventSubscriber/PageNotFoundEventSubscriber.php:
--------------------------------------------------------------------------------
1 | pathValidator = $path_validator;
62 | $this->pathMatcher = $path_matcher;
63 | }
64 |
65 | /**
66 | * Redirects to the apidoc canonical route if we have a not found exception.
67 | *
68 | * @param \Symfony\Component\HttpKernel\Event\ExceptionEvent $event
69 | * The exception event.
70 | */
71 | public function onNotFoundException(ExceptionEvent $event) {
72 | // Check if the request uri matches an apidoc canonical route.
73 | // Also check for apidoc valid path.
74 | if ($event->getThrowable() instanceof NotFoundHttpException
75 | && ($uri = $event->getRequest()->getRequestUri())
76 | && $this->pathMatcher->matchPath($uri, '/api/*/*')
77 | && ([$apitrail, $api, $id] = explode('/', $uri))
78 | && ($path = "/api/{$id}")
79 | && $this->pathValidator->isValid($path)
80 | ) {
81 | // Redirect to the apidoc.
82 | $event->setResponse(new RedirectResponse($path));
83 | }
84 | }
85 |
86 | /**
87 | * {@inheritdoc}
88 | */
89 | public static function getSubscribedEvents() {
90 | $events[KernelEvents::EXCEPTION][] = ['onNotFoundException', 0];
91 | return $events;
92 | }
93 |
94 | }
95 |
--------------------------------------------------------------------------------
/src/Plugin/Field/FieldFormatter/SmartDocsFormatter.php:
--------------------------------------------------------------------------------
1 | loggerFactory = $loggerFactory->get('apigee_api_catalog');
73 | }
74 |
75 | /**
76 | * {@inheritdoc}
77 | */
78 | public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
79 | return new static(
80 | $plugin_id,
81 | $plugin_definition,
82 | $configuration['field_definition'],
83 | $configuration['settings'],
84 | $configuration['label'],
85 | $configuration['view_mode'],
86 | $configuration['third_party_settings'],
87 | $container->get('logger.factory')
88 | );
89 | }
90 |
91 | /**
92 | * {@inheritdoc}
93 | */
94 | public function view(FieldItemListInterface $items, $langcode = NULL) {
95 | $elements = parent::view($items, $langcode);
96 |
97 | // Add base tag for SmartDocs Angular application.
98 | $xuacompatible = [
99 | '#tag' => 'base',
100 | '#attributes' => [
101 | 'href' => base_path(),
102 | ],
103 | ];
104 | $elements['#attached']['html_head'][] = [$xuacompatible, 'base-href'];
105 | return $elements;
106 |
107 | }
108 |
109 | /**
110 | * {@inheritdoc}
111 | */
112 | public function viewElements(FieldItemListInterface $items, $langcode) {
113 |
114 | $entity = $items->getEntity();
115 | $entity_type = $entity->getEntityTypeId();
116 |
117 | // The list of OpenAPI specs to pass to SmartDocs Angular app.
118 | $openapi_files = [];
119 |
120 | /** @var \Drupal\file\Entity\File $file */
121 | foreach ($this->getEntitiesToView($items, $langcode) as $delta => $file) {
122 | $openapi_files[] = [
123 | // Get the URL of the file on server.
124 | 'fileUrl' => $file->createFileUrl(FALSE),
125 | 'fileExtension' => pathinfo($file->getFilename(), PATHINFO_EXTENSION),
126 | ];
127 | }
128 |
129 | $elements['#attached'] = [
130 | 'library' => [
131 | 'apigee_api_catalog/js_yaml',
132 | 'apigee_api_catalog/smartdocs_integration',
133 | 'apigee_api_catalog/smartdocs',
134 | ],
135 | ];
136 |
137 | $elements['#attached']['drupalSettings']['smartdocsFieldFormatter'][$this->fieldDefinition->getName()] = [
138 | 'openApiFiles' => $openapi_files,
139 | 'entityId' => $entity->id(),
140 | 'entityType' => $entity_type,
141 | ];
142 |
143 | foreach ($items as $delta => $item) {
144 | $elements[$delta] = [
145 | // Create tag on page for SmartDocs Angular app.
146 | '#type' => 'html_tag',
147 | '#tag' => 'app-root',
148 | '#value' => $this->t('Loading...'),
149 | ];
150 | }
151 |
152 | return $elements;
153 | }
154 |
155 | }
156 |
--------------------------------------------------------------------------------
/src/Plugin/Validation/Constraint/ApiDocFileLinkConstraint.php:
--------------------------------------------------------------------------------
1 | httpClient = $http_client;
51 | }
52 |
53 | /**
54 | * {@inheritdoc}
55 | */
56 | public static function create(ContainerInterface $container) {
57 | return new static($container->get('http_client'));
58 | }
59 |
60 | /**
61 | * {@inheritdoc}
62 | */
63 | public function validate($items, Constraint $constraint) {
64 | /** @var \Drupal\Core\Field\FieldItemListInterface $items */
65 | if (!isset($items)) {
66 | return;
67 | }
68 |
69 | foreach ($items as $item) {
70 | if ($item->isEmpty()) {
71 | continue;
72 | }
73 |
74 | // Try to resolve the given URI to a URL. It may fail if it's scheme-less.
75 | try {
76 | $url = Url::fromUri($item->getValue()['uri'], ['absolute' => TRUE])->toString();
77 | }
78 | catch (\InvalidArgumentException $e) {
79 | $this->context->addViolation($constraint->urlParseError, ['@error' => $e->getMessage()]);
80 | return;
81 | }
82 |
83 | try {
84 | $options = [
85 | 'allow_redirects' => [
86 | 'strict' => TRUE,
87 | ],
88 | ];
89 |
90 | // Perform only a HEAD method to save bandwidth.
91 | /** @var \Psr\Http\Message\ResponseInterface $response */
92 | $response = $this->httpClient->head($url, $options);
93 | }
94 | catch (RequestException $request_exception) {
95 | $this->context->addViolation($constraint->notValid, [
96 | '%value' => $url,
97 | ]);
98 | }
99 | }
100 | }
101 |
102 | }
103 |
--------------------------------------------------------------------------------
/src/SpecFetcherInterface.php:
--------------------------------------------------------------------------------
1 | MessengerInterface::TYPE_WARNING,
48 | LogLevel::WARNING => MessengerInterface::TYPE_WARNING,
49 | LogLevel::NOTICE => MessengerInterface::TYPE_STATUS,
50 | LogLevel::INFO => MessengerInterface::TYPE_STATUS,
51 | LogLevel::DEBUG => MessengerInterface::TYPE_STATUS,
52 | ];
53 |
54 | /**
55 | * The status when an error happened during the fetch operation.
56 | *
57 | * @var string
58 | */
59 | public const STATUS_ERROR = 'status_error';
60 |
61 | /**
62 | * The status when a spec update completed successfully.
63 | *
64 | * @var string
65 | */
66 | public const STATUS_UPDATED = 'status_updated';
67 |
68 | /**
69 | * The status when a spec update finds the remote file unchanged.
70 | *
71 | * @var string
72 | */
73 | public const STATUS_UNCHANGED = 'status_unchanged';
74 |
75 | /**
76 | * Fetch OpenAPI specification file from URL.
77 | *
78 | * Takes care of updating an ApiDoc file entity with the updated spec file. If
79 | * "spec_file_source" uses a URL, it will fetch the specified file and put it
80 | * in the "spec" file field. If it uses a "file", it won't change it.
81 | * This method only updates the file entity if it completed without error (if
82 | * it returns STATUS_UPDATED or STATUS_UNCHANGED), it does not save
83 | * the ApiDoc entity.
84 | *
85 | * @param \Drupal\node\NodeInterface $apidoc
86 | * The ApiDoc entity.
87 | *
88 | * @return string
89 | * Returns the status of the operation. If it is STATUS_UPDATED or
90 | * STATUS_UNCHANGED, the ApiDoc entity will need to be saved to store the
91 | * changes.
92 | */
93 | public function fetchSpec(NodeInterface $apidoc): string;
94 |
95 | }
96 |
--------------------------------------------------------------------------------
/tests/src/Functional/ApiDocsAdminTest.php:
--------------------------------------------------------------------------------
1 | drupalPlaceBlock('local_actions_block');
72 | $this->drupalPlaceBlock('local_tasks_block');
73 | }
74 |
75 | /**
76 | * {@inheritdoc}
77 | */
78 | protected function setUp(): void {
79 | parent::setUp();
80 |
81 | // Add the system menu blocks to appropriate regions.
82 | $this->setupMenus();
83 |
84 | $this->adminUser = $this->drupalCreateUser([
85 | 'access content',
86 | 'access content overview',
87 | 'create apidoc content',
88 | 'edit any apidoc content',
89 | 'delete any apidoc content',
90 | ]);
91 | $this->drupalLogin($this->adminUser);
92 | }
93 |
94 | /**
95 | * Tests that a user can administer API Docs.
96 | */
97 | public function testApiDocAdministration() {
98 | $assert = $this->assertSession();
99 |
100 | // Get the API Doc admin page.
101 | $this->drupalGet(Url::fromRoute('view.api_catalog_admin.page_1'));
102 |
103 | // No API docs yet.
104 | $assert->pageTextContains('There are no API docs yet.');
105 |
106 | // User can add entity content.
107 | $assert->linkExists('OpenAPI');
108 | $this->clickLink('OpenAPI');
109 |
110 | // Fields should have proper defaults.
111 | $assert->fieldValueEquals('title[0][value]', '');
112 | $assert->fieldValueEquals('body[0][value]', '');
113 |
114 | // Create a new spec in site.
115 | $file = File::create([
116 | 'uid' => $this->adminUser->id(),
117 | 'filename' => 'specA.yaml',
118 | 'uri' => 'public://specA.yaml',
119 | 'filemime' => 'application/octet-stream',
120 | 'created' => 1,
121 | 'changed' => 1,
122 | 'status' => FileInterface::STATUS_PERMANENT,
123 | ]);
124 | file_put_contents($file->getFileUri(), "swagger: '2.0'");
125 |
126 | // Save it, inserting a new record.
127 | $file->setPermanent();
128 | $file->save();
129 | $this->assertTrue($file->id() > 0, 'The file was added to the database.');
130 |
131 | $page = $this->getSession()->getPage();
132 | $random_name = $this->randomMachineName();
133 | $random_description = Random::getGenerator()->sentences(5);
134 | $page->fillField('title[0][value]', $random_name);
135 | $page->fillField('body[0][value]', $random_description);
136 | $page->fillField('field_apidoc_spec_file_source', 'file');
137 |
138 | // Can't use drupalPostForm() to set hidden fields.
139 | $this->getSession()->getPage()->find(
140 | 'css', 'input[name="field_apidoc_spec[0][fids]"]'
141 | )->setValue($file->id());
142 | $this->getSession()->getPage()->pressButton(t('Save'));
143 |
144 | $assert->statusCodeEquals(200);
145 | $assert->pageTextContains(new FormattableMarkup(
146 | 'OpenAPI Doc @name has been created.',
147 | [
148 | '@name' => $random_name,
149 | ]
150 | )
151 | );
152 |
153 | // Entity listed.
154 | $assert->linkExists($random_name);
155 | $assert->linkExists('Edit');
156 | $assert->linkExists('Delete');
157 |
158 | // Click on API Doc to edit.
159 | $this->clickLink('Edit');
160 | $assert->statusCodeEquals(200);
161 |
162 | // Edit form should have proper values.
163 | $assert->fieldValueEquals('title[0][value]', $random_name);
164 | $assert->fieldValueEquals('body[0][value]', $random_description);
165 | $assert->linkExists('specA.yaml');
166 |
167 | // Delete the entity.
168 | $this->clickLink('Delete');
169 | }
170 |
171 | }
172 |
--------------------------------------------------------------------------------
/tests/src/Functional/ApiDocsBreadcrumbTest.php:
--------------------------------------------------------------------------------
1 | drupalPlaceBlock('system_breadcrumb_block');
62 | $this->drupalPlaceBlock('page_title_block');
63 |
64 | $this->apidoc = $this->container->get('entity_type.manager')
65 | ->getStorage('node')
66 | ->create([
67 | 'type' => 'apidoc',
68 | 'title' => 'API 1',
69 | 'body' => [
70 | 'value' => 'Test API 1',
71 | 'format' => 'basic_html',
72 | ],
73 | 'field_apidoc_spec' => NULL,
74 | ]);
75 |
76 | $this->apidoc->save();
77 |
78 | $user = $this->drupalCreateUser(['access content']);
79 | $this->drupalLogin($user);
80 | }
81 |
82 | /**
83 | * Tests the route subscriber will redirect from smartdoc routes.
84 | */
85 | public function testApiDocBreadcrumb() {
86 | // Tests the normal response.
87 | $url = Url::fromRoute('entity.node.canonical', ['node' => $this->apidoc->id()]);
88 | $this->drupalGet($url);
89 |
90 | // Fetch each node title in the current breadcrumb.
91 | $links = $this->xpath('//nav[@class="breadcrumb"]/div/ol/li/a');
92 | $got_breadcrumb = [];
93 | foreach ($links as $link) {
94 | $got_breadcrumb[] = $link->getText();
95 | }
96 |
97 | $this->assertEquals($got_breadcrumb[0], 'Home');
98 | $this->assertEquals($got_breadcrumb[1], 'API Catalog');
99 |
100 | }
101 |
102 | }
103 |
--------------------------------------------------------------------------------
/tests/src/Functional/ApiDocsJsonApi.php:
--------------------------------------------------------------------------------
1 | container->get('entity_type.manager')->getStorage('node');
74 |
75 | // Create published apidoc.
76 | $this->apidocPublished = $nodeStorage->create([
77 | 'type' => 'apidoc',
78 | 'title' => 'Published',
79 | 'body' => [
80 | 'value' => 'Published API',
81 | 'format' => 'basic_html',
82 | ],
83 | 'field_apidoc_spec' => NULL,
84 | 'status' => 1,
85 | ]);
86 | $this->apidocPublished->save();
87 |
88 | // Create unpublished apidoc.
89 | $this->apidocUnpublished = $nodeStorage->create([
90 | 'type' => 'apidoc',
91 | 'title' => 'Unpublished',
92 | 'body' => [
93 | 'value' => 'Unpublished API',
94 | 'format' => 'basic_html',
95 | ],
96 | 'field_apidoc_spec' => NULL,
97 | 'status' => 0,
98 | ]);
99 | $this->apidocUnpublished->save();
100 | }
101 |
102 | /**
103 | * Test listing API Docs as an admin.
104 | *
105 | * Admin should see all API Docs.
106 | */
107 | public function testListAdminAccess() {
108 | $account = $this->drupalCreateUser([
109 | 'bypass node access',
110 | ]);
111 | $this->drupalLogin($account);
112 |
113 | $collection_url = Url::fromRoute('jsonapi.node--apidoc.collection')
114 | ->setAbsolute(TRUE)->toString();
115 |
116 | $this->verifyAccess($account, [
117 | $this->apidocPublished,
118 | $this->apidocUnpublished,
119 | ], $collection_url);
120 | }
121 |
122 | /**
123 | * Make sure admin can filter and get results back.
124 | *
125 | * @throws \Drupal\Core\Entity\EntityStorageException
126 | */
127 | public function testFilterAdminAccess() {
128 | $account = $this->drupalCreateUser([
129 | 'bypass node access',
130 | ]);
131 | $this->drupalLogin($account);
132 |
133 | $collection_url = Url::fromRoute('jsonapi.node--apidoc.collection')
134 | ->setAbsolute(TRUE)->toString();
135 | $url = "${collection_url}?filter[title]=Published";
136 | $this->verifyAccess($account, [$this->apidocPublished], $url);
137 |
138 | $url = "${collection_url}?filter[title]=Unpublished";
139 | $this->verifyAccess($account, [$this->apidocUnpublished], $url);
140 | }
141 |
142 | /**
143 | * View published permission can filter published docs.
144 | *
145 | * @throws \Drupal\Core\Entity\EntityStorageException
146 | */
147 | public function testFilterViewAccessViewPublished() {
148 | $account = $this->drupalCreateUser([
149 | 'access content',
150 | ]);
151 | $this->drupalLogin($account);
152 |
153 | $collection_url = Url::fromRoute('jsonapi.node--apidoc.collection')
154 | ->setAbsolute(TRUE)->toString();
155 | $url = "${collection_url}?filter[title]=Published";
156 | $this->verifyAccess($account, [$this->apidocPublished], $url);
157 | }
158 |
159 | /**
160 | * Returns Guzzle request options for authentication.
161 | *
162 | * @return array
163 | * Guzzle request options to use for authentication.
164 | *
165 | * @see \GuzzleHttp\ClientInterface::request()
166 | */
167 | protected function getAuthenticationRequestOptions($account) {
168 | return [
169 | 'headers' => [
170 | 'Authorization' => 'Basic ' . base64_encode($account->name->value . ':' . $account->passRaw),
171 | ],
172 | ];
173 | }
174 |
175 | /**
176 | * Verify the account has access when making JSON:API call.
177 | *
178 | * @param \Drupal\Core\Session\AccountInterface $account
179 | * The account to send the call.
180 | * @param array $apidocs_expected
181 | * An array of the expected API Docs.
182 | * @param string $url
183 | * The URL to call.
184 | * @param array $request_options
185 | * Any request parameters to pass in such as filter query params.
186 | */
187 | protected function verifyAccess(AccountInterface $account, array $apidocs_expected, string $url, array $request_options = []) {
188 |
189 | // Need this header to make calls.
190 | $request_options[RequestOptions::HEADERS]['Accept'] = 'application/vnd.api+json';
191 | // Add request options and basic auth header together.
192 | $request_options = NestedArray::mergeDeep($request_options, $this->getAuthenticationRequestOptions($account));
193 |
194 | $client = $this->getSession()->getDriver()->getClient()->getClient();
195 |
196 | // Make the API call.
197 | $response = $client->request('GET', $url, $request_options);
198 |
199 | $this->assertSame(['application/vnd.api+json'], $response->getHeader('Content-Type'));
200 | $response_document = Json::decode((string) $response->getBody());
201 |
202 | // Get the API Docs from response and create array of names fetched.
203 | $apidocs_response = $response_document['data'];
204 | $names = [];
205 | foreach ($apidocs_response as $apidoc) {
206 | $names[] = $apidoc['attributes']['title'];
207 | }
208 |
209 | // Sort expected and actual response results by name for comparison.
210 | usort($apidocs_expected, function ($a, $b) {
211 | return strcmp($a->label(), $b->label());
212 | });
213 | usort($apidocs_response, function ($a, $b) {
214 | return strcmp($a['attributes']['title'], $b['attributes']['title']);
215 | });
216 | for ($i = 0; $i < count($apidocs_response); $i++) {
217 | $this->assertEquals($apidocs_expected[$i]->label(), $apidocs_response[$i]['attributes']['title']);
218 | }
219 | // Make sure the count is the same.
220 | $this->assertCount(count($apidocs_expected), $apidocs_response, 'Count of API Docs returned does not match count of expected.');
221 | }
222 |
223 | }
224 |
--------------------------------------------------------------------------------
/tests/src/Functional/SmartdocRoutingTest.php:
--------------------------------------------------------------------------------
1 | apidoc = $this->container->get('entity_type.manager')
61 | ->getStorage('node')
62 | ->create([
63 | 'type' => 'apidoc',
64 | 'title' => 'API 1',
65 | 'body' => [
66 | 'value' => 'Test API 1',
67 | 'format' => 'basic_html',
68 | ],
69 | 'field_apidoc_spec' => NULL,
70 | ]);
71 |
72 | $this->apidoc->save();
73 |
74 | $user = $this->drupalCreateUser(['access content']);
75 | $this->drupalLogin($user);
76 | }
77 |
78 | /**
79 | * Tests the route subscriber will redirect from smartdoc routes.
80 | */
81 | public function testNotFoundSubscriber() {
82 | $this->assertEquals($this->apidoc->id(), 1);
83 |
84 | // This needs to run before the alias can be picked up?
85 | $this->apidoc->toUrl()->toString();
86 | $alias = \Drupal::service('path_alias.manager')->getAliasByPath('/node/1', $this->apidoc->language()->getId());
87 | $this->assertEquals($alias, '/api/1');
88 |
89 | $assert = $this->assertSession();
90 |
91 | // Tests the normal response.
92 | $url = Url::fromRoute('entity.node.canonical', ['node' => $this->apidoc->id()]);
93 | $this->drupalGet($url);
94 | $assert->statusCodeEquals(200);
95 | static::assertEmpty($this->getSession()->getResponseHeader('location'));
96 |
97 | // Test the canonical route uses the /api/* path alias.
98 | $this->assertEquals(parse_url($this->getSession()->getCurrentUrl(), PHP_URL_PATH), '/api/1');
99 |
100 | // Tests the node alias response.
101 | $this->drupalGet('/api/1');
102 | $assert->statusCodeEquals(200);
103 | static::assertEmpty($this->getSession()->getResponseHeader('location'));
104 |
105 | // Test that the smartdoc routes redirect to the canonical route.
106 | $url = Url::fromUserInput('/api/1/1/overview')->setAbsolute();
107 | $response = $this->getHttpClient()->request('GET', $url->toString(), [
108 | 'allow_redirects' => FALSE,
109 | ]);
110 | $this->assertEquals($response->getStatusCode(), 302);
111 | $this->assertEquals($response->getHeader('location')[0], '/api/1');
112 | }
113 |
114 | }
115 |
--------------------------------------------------------------------------------
/tests/src/Kernel/ApidocEntityTest.php:
--------------------------------------------------------------------------------
1 | installSchema('user', ['users_data']);
72 | $this->installSchema('node', ['node_access']);
73 | $this->installEntitySchema('user');
74 | $this->installEntitySchema('node');
75 | $this->installEntitySchema('node_type');
76 | $this->installEntitySchema('path_alias');
77 | $this->installConfig(static::$modules);
78 |
79 | $this->entityTypeManager = $this->container->get('entity_type.manager');
80 | $this->nodeStorage = $this->entityTypeManager->getStorage('node');
81 | }
82 |
83 | /**
84 | * Basic CRUD operations on a ApiDoc node entity.
85 | */
86 | public function testEntity() {
87 | $entity = $this->nodeStorage->create([
88 | 'type' => 'apidoc',
89 | 'title' => 'API 1',
90 | 'body' => [
91 | 'value' => 'Test API 1',
92 | 'format' => 'basic_html',
93 | ],
94 | 'field_apidoc_spec' => NULL,
95 | ]);
96 |
97 | $this->assertNotNull($entity);
98 | $this->assertEquals(SAVED_NEW, $entity->save());
99 | $this->assertEquals(SAVED_UPDATED, $entity->set('title', 'API 1a')->save());
100 | $entity_id = $entity->id();
101 | $this->assertNotEmpty($entity_id);
102 |
103 | // Test path alias.
104 | // This needs to run before the alias can be picked up?
105 | $entity->toUrl()->toString();
106 | $alias = \Drupal::service('path_alias.manager')->getAliasByPath('/node/' . $entity->id(), $entity->language()->getId());
107 | $this->assertEquals($alias, '/api/' . $entity->id());
108 |
109 | $entity->delete();
110 | $this->assertNull($this->nodeStorage->load($entity_id));
111 | }
112 |
113 | /**
114 | * Test revisioning functionality on an apidocs entity.
115 | */
116 | public function testRevisions() {
117 | /** @var \Drupal\node\NodeInterface $entity */
118 |
119 | $description_v1 = 'Test API';
120 | $entity = $this->nodeStorage->create([
121 | 'type' => 'apidoc',
122 | 'title' => 'API 1',
123 | 'body' => [
124 | 'value' => $description_v1,
125 | 'format' => 'basic_html',
126 | ],
127 | 'field_apidoc_spec' => NULL,
128 | ]);
129 |
130 | // Test saving a revision.
131 | $entity->setNewRevision();
132 | $entity->setRevisionLogMessage('v1');
133 | $entity->save();
134 | $v1_id = $entity->getRevisionId();
135 | $this->assertNotNull($v1_id);
136 |
137 | // Test saving a new revision.
138 | $new_log = 'v2';
139 | $entity->body = [
140 | 'value' => 'Test API v2',
141 | 'format' => 'basic_html',
142 | ];
143 | $entity->setNewRevision();
144 | $entity->setRevisionLogMessage($new_log);
145 | $entity->save();
146 | $v2_id = $entity->getRevisionId();
147 | $this->assertLessThan($v2_id, $v1_id);
148 |
149 | // Test saving without a new revision.
150 | $entity->body = [
151 | 'value' => 'Test API v3',
152 | 'format' => 'basic_html',
153 | ];
154 | $entity->save();
155 | $this->assertEquals($v2_id, $entity->getRevisionId());
156 |
157 | // Test that the revision log message wasn't overriden.
158 | $this->assertEquals($new_log, $entity->getRevisionLogMessage());
159 |
160 | // Revert to the first revision.
161 | $entity_v1 = $this->nodeStorage->loadRevision($v1_id);
162 | $entity_v1->setNewRevision();
163 | $entity_v1->isDefaultRevision(TRUE);
164 | $entity_v1->setRevisionLogMessage('Copy of revision ' . $v1_id);
165 | $entity_v1->save();
166 |
167 | // Load and check reverted values.
168 | $this->nodeStorage->resetCache();
169 | $reverted = $this->nodeStorage->load($entity->id());
170 | $this->assertLessThan($reverted->getRevisionId(), $v1_id);
171 | $this->assertTrue($reverted->isDefaultRevision());
172 | $this->assertEquals($description_v1, $reverted->body->value);
173 | }
174 |
175 | }
176 |
--------------------------------------------------------------------------------