Skip to content

Commit

Permalink
workbooks (#926), and sharepoint alm namespaces enhancements
Browse files Browse the repository at this point in the history
  • Loading branch information
vgrem committed Jan 4, 2025
1 parent dd92579 commit 7243f97
Show file tree
Hide file tree
Showing 18 changed files with 246 additions and 23 deletions.
Binary file not shown.
Binary file not shown.
3 changes: 3 additions & 0 deletions examples/sharepoint/alm/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Application Lifecycle Management (ALM) API

### ALM APIs provide simple APIs to manage deployment of your SharePoint Framework solutions and add-ins across your tenant.
Binary file added examples/sharepoint/alm/react-banner.sppkg
Binary file not shown.
11 changes: 11 additions & 0 deletions office365/directory/authentication/methods/root.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
from office365.directory.authentication.methods.details import UserRegistrationDetails
from office365.entity import Entity
from office365.entity_collection import EntityCollection
from office365.reports.userregistration.feature_summary import (
UserRegistrationFeatureSummary,
)
from office365.reports.userregistration.method_summary import (
UserRegistrationMethodSummary,
)
Expand All @@ -12,6 +15,14 @@
class AuthenticationMethodsRoot(Entity):
"""Container for navigation properties for Azure AD authentication methods resources."""

def users_registered_by_feature(self):
"""Get the number of users capable of multi-factor authentication, self-service password reset,
and passwordless authentication."""
return_type = ClientResult(self.context, UserRegistrationFeatureSummary())
qry = FunctionQuery(self, "usersRegisteredByFeature", None, return_type)
self.context.add_query(qry)
return return_type

def users_registered_by_method(self):
"""Get the number of users registered for each authentication method."""
return_type = ClientResult(self.context, UserRegistrationMethodSummary())
Expand Down
33 changes: 29 additions & 4 deletions office365/onedrive/workbooks/charts/chart.py
Original file line number Diff line number Diff line change
@@ -1,21 +1,46 @@
from office365.entity import Entity
from office365.onedrive.workbooks.charts.axes import WorkbookChartAxes
from office365.onedrive.workbooks.charts.data_labels import WorkbookChartDataLabels
from office365.runtime.client_result import ClientResult
from office365.runtime.paths.resource_path import ResourcePath
from office365.runtime.queries.function import FunctionQuery
from office365.runtime.queries.service_operation import ServiceOperationQuery


class WorkbookChart(Entity):
"""Represents a chart object in a workbook."""

def set_position(self, startCell, endCell):
def image(self, width=None, height=None):
"""Renders the chart as a base64-encoded image by scaling the chart to fit the specified dimensions.
:param int width: Specifies the width of the rendered image in pixels.
:param int height: Specifies the height of the rendered image in pixels.
"""
return_type = ClientResult(self.context)
params = {"width": width, "height": height}
qry = FunctionQuery(self, "image", params, return_type)
self.context.add_query(qry)
return return_type

def set_data(self, source_data, series_by):
"""
Updates the data source of a chart
:param dict source_data:
:param str series_by:
"""
payload = {"sourceData": source_data, "seriesBy": series_by}
qry = ServiceOperationQuery(self, "setData", None, payload)
self.context.add_query(qry)
return self

def set_position(self, start_cell, end_cell):
"""Positions the chart relative to cells on the worksheet.
:param str startCell: The start cell. It is where the chart is moved to. The start cell is the top-left or
:param str start_cell: The start cell. It is where the chart is moved to. The start cell is the top-left or
top-right cell, depending on the user's right-to-left display settings.
:param str endCell: The end cell. If specified, the chart's width and height is set to fully cover up
:param str end_cell: The end cell. If specified, the chart's width and height is set to fully cover up
this cell/range.
"""
payload = {"startCell": startCell, "endCell": endCell}
payload = {"startCell": start_cell, "endCell": end_cell}
qry = ServiceOperationQuery(self, "setPosition", None, payload)
self.context.add_query(qry)
return self
Expand Down
17 changes: 17 additions & 0 deletions office365/onedrive/workbooks/ranges/range.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,12 @@
class WorkbookRange(Entity):
"""Range represents a set of one or more contiguous cells such as a cell, a row, a column, block of cells, etc."""

def __repr__(self):
return self.address or self.entity_type_name

def __str__(self):
return self.address or self.entity_type_name

def cell(self, row, column):
"""
Gets the range object containing the single cell based on row and column numbers. The cell can be outside
Expand Down Expand Up @@ -55,6 +61,17 @@ def visible_view(self):
self.context.add_query(qry)
return return_type

def used_range(self, values_only=False):
"""Return the used range of the given range object.
:param bool values_only: Optional. Considers only cells with values as used cells.
"""
return_type = WorkbookRange(self.context)
params = {"valuesOnly": values_only}
qry = FunctionQuery(self, "usedRange", params, return_type)
self.context.add_query(qry)
return return_type

@property
def address(self):
# type: () -> Optional[str]
Expand Down
11 changes: 11 additions & 0 deletions office365/onedrive/workbooks/worksheets/worksheet.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,17 @@ def range(self, address=None):
self.context.add_query(qry)
return return_type

def used_range(self, values_only=False):
"""Return the used range of the given range object.
:param bool values_only: Optional. Considers only cells with values as used cells.
"""
return_type = WorkbookRange(self.context)
params = {"valuesOnly": values_only}
qry = FunctionQuery(self, "usedRange", params, return_type)
self.context.add_query(qry)
return return_type

@property
def charts(self):
# type: () -> EntityCollection[WorkbookChart]
Expand Down
5 changes: 5 additions & 0 deletions office365/reports/userregistration/feature_summary.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
from office365.runtime.client_value import ClientValue


class UserRegistrationFeatureSummary(ClientValue):
""""""
13 changes: 12 additions & 1 deletion office365/sharepoint/marketplace/app_metadata.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@
class CorporateCatalogAppMetadata(Entity):
"""App metadata for apps stored in the corporate catalog."""

def __str__(self):
return self.title or self.entity_type_name

def deploy(self, skip_feature_deployment):
"""This method deploys an app on the app catalog. It MUST be called in the context of the tenant app
catalog web or it will fail.
Expand All @@ -18,6 +21,13 @@ def deploy(self, skip_feature_deployment):
self.context.add_query(qry)
return self

def remove(self):
"""This is the inverse of the add step above. One removed from the app catalog,
the solution can't be deployed."""
qry = ServiceOperationQuery(self, "Remove")
self.context.add_query(qry)
return self

def install(self):
"""This method allows an app which is already deployed to be installed on a web."""
qry = ServiceOperationQuery(self, "Install")
Expand Down Expand Up @@ -68,7 +78,8 @@ def id(self):

@property
def property_ref_name(self):
return "AadAppId"
# return "AadAppId"
return "ID"

@property
def entity_type_name(self):
Expand Down
8 changes: 8 additions & 0 deletions office365/sharepoint/marketplace/app_metadata_collection.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,11 @@ def get_by_id(self, app_id):
return CorporateCatalogAppMetadata(
self.context, ServiceOperationPath("GetById", [app_id], self.resource_path)
)

def get_by_title(self, title):
"""
Get app metadata by title.
:param str title: The title of the app to retrieve.
"""
return self.first("title eq '{0}'".format(title))
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
from office365.runtime.client_value import ClientValue


class SPStoreAppRequestInformation(ClientValue):
""""""

@property
def entity_type_name(self):
return "Microsoft.SharePoint.Marketplace.CorporateCuratedGallery.SPStoreAppRequestInformation"
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
from office365.runtime.client_value import ClientValue


class SPStoreAppResponseInformation(ClientValue):
""""""

@property
def entity_type_name(self):
return "Microsoft.SharePoint.Marketplace.CorporateCuratedGallery.SPStoreAppResponseInformation"
44 changes: 41 additions & 3 deletions office365/sharepoint/marketplace/tenant/appcatalog/accessor.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import os

from office365.runtime.client_result import ClientResult
from office365.runtime.paths.resource_path import ResourcePath
from office365.runtime.paths.service_operation import ServiceOperationPath
Expand All @@ -8,6 +10,12 @@
from office365.sharepoint.marketplace.app_metadata_collection import (
CorporateCatalogAppMetadataCollection,
)
from office365.sharepoint.marketplace.corporatecuratedgallery.app_request_information import (
SPStoreAppRequestInformation,
)
from office365.sharepoint.marketplace.corporatecuratedgallery.app_response_information import (
SPStoreAppResponseInformation,
)
from office365.sharepoint.marketplace.corporatecuratedgallery.app_upgrade_availability import (
AppUpgradeAvailability,
)
Expand All @@ -22,7 +30,7 @@
class TenantCorporateCatalogAccessor(Entity):
"""Accessor for the tenant corporate catalog."""

def add(self, content, overwrite, url):
def add(self, content, overwrite, url=None):
"""
Adds a file to the corporate catalog.
Expand All @@ -32,8 +40,27 @@ def add(self, content, overwrite, url):
:param str url: Specifies the URL of the file to be added.
"""
return_type = File(self.context)
payload = {"Content": content, "Overwrite": overwrite, "Url": url}
qry = ServiceOperationQuery(self, "Add", None, payload, None, return_type)
params = {"Overwrite": overwrite, "Url": url}
qry = ServiceOperationQuery(self, "Add", params, content, None, return_type)
self.context.add_query(qry)
return return_type

def app_from_path(self, path, overwrite):
"""
Adds a file to the corporate catalog.
"""
with open(path, "rb") as f:
content = f.read()
url = os.path.basename(path)
return self.add(content=content, overwrite=overwrite, url=url)

def app_requests(self):
""""""
return_type = ClientResult(self.context, SPStoreAppResponseInformation())
payload = {"AppRequestInfo": SPStoreAppRequestInformation()}
qry = ServiceOperationQuery(
self, "AppRequests", None, payload, None, return_type
)
self.context.add_query(qry)
return return_type

Expand Down Expand Up @@ -70,6 +97,17 @@ def is_app_upgrade_available(self, _id):
self.context.add_query(qry)
return return_type

def upload(self, content, overwrite, url, xor_hash=None):
payload = {
"Content": content,
"Overwrite": overwrite,
"Url": url,
"XorHash": xor_hash,
}
qry = ServiceOperationQuery(self, "Upload", None, payload)
self.context.add_query(qry)
return self

def send_app_request_status_notification_email(self, request_guid):
"""
:param str request_guid:
Expand Down
40 changes: 40 additions & 0 deletions tests/onedrive/test_excel_charts.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
from office365.onedrive.driveitems.driveItem import DriveItem
from office365.onedrive.workbooks.charts.chart import WorkbookChart
from office365.onedrive.workbooks.worksheets.worksheet import WorkbookWorksheet
from tests.graph_case import GraphTestCase


class TestExcelCharts(GraphTestCase):
excel_file = None # type: DriveItem
worksheet = None # type: WorkbookWorksheet
chart = None # type: WorkbookChart

@classmethod
def setUpClass(cls):
super(TestExcelCharts, cls).setUpClass()
path = "../../examples/data/templates/Weight loss tracker.xlsx"
cls.excel_file = cls.client.me.drive.root.upload_file(path).execute_query()
assert cls.excel_file.resource_path is not None
cls.worksheet = (
cls.excel_file.workbook.worksheets["Weight loss tracker"]
.get()
.execute_query()
)
assert cls.worksheet.resource_path is not None

@classmethod
def tearDownClass(cls):
cls.excel_file.delete_object().execute_query_retry()

def test1_list_charts(self):
result = self.__class__.worksheet.charts.get().execute_query()
self.assertIsNotNone(result.resource_path)

def test2_get_chart_by_name(self):
result = self.__class__.worksheet.charts["Weight Tracker"].get().execute_query()
self.assertIsNotNone(result.resource_path)
self.__class__.chart = result

def test3_get_image(self):
result = self.__class__.chart.image().execute_query()
self.assertIsNotNone(result.value)
8 changes: 6 additions & 2 deletions tests/onedrive/test_excel_ranges.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ def test1_name_create(self):
self.__class__.named_item = result

def test2_names_get(self):
result = self.__class__.named_item.get().execute_query()
result = self.__class__.named_item.get().execute_query_retry(2)
self.assertIsNotNone(result.resource_path)

def test3_list_range(self):
Expand All @@ -42,6 +42,10 @@ def test3_list_range(self):
# result = self.__class__.range.insert("Right").execute_query()
# self.assertIsNotNone(result.address)

def test5_clear_range(self):
def test5_used_range(self):
result = self.__class__.range.used_range().execute_query()
self.assertIsNotNone(result.address)

def test6_clear_range(self):
result = self.__class__.range.clear().execute_query()
self.assertIsNotNone(result.address)
6 changes: 5 additions & 1 deletion tests/onedrive/test_excel_worksheets.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,11 @@ def test2_list_worksheets(self):
self.assertGreaterEqual(len(result), 1)
self.__class__.worksheet = result[0]

def test3_protect_worksheet(self):
def test3_used_range(self):
result = self.__class__.worksheet.used_range().execute_query()
self.assertIsNotNone(result.address)

def test4_protect_worksheet(self):
ws = self.__class__.worksheet
options = WorkbookWorksheetProtectionOptions(allowDeleteRows=False)
ws.protection.protect(options).execute_query()
Expand Down
Loading

0 comments on commit 7243f97

Please sign in to comment.